什么是协程?协程和线程的区别

前置知识

  • 在了解协程前,我们先理解一些相关的基本知识。

应用程序和内核

  • 内核具有最高权限,可以访问受保护的内存空间,可以访问底层的硬件设备。而这些是应用程序所不具备的,但应用程序可以通过调用内核提供的接口来间接访问或操作。
  • 以一次网络 IO 操作为例,请求的数据会先被拷贝到系统内核的缓冲区(内核空间),然后再从内核缓冲区拷贝到应用程序的地址空间(用户空间)。这个过程包括两个阶段:
txt 复制代码
1、等待数据准备: 数据从网络接口读取并放入内核缓冲区。
2、拷贝数据: 数据从内核缓冲区复制到应用程序的用户空间。

阻塞和非阻塞

  • 从上面我们可以清楚的知道, 一次 IO 操作 操作流程分为两步:等待数据准备、拷贝数据,若等待数据准备过程是阻塞的,则我们称为阻塞操作;若不必等待数据准备完成,而是返回是否就绪标志,则称为非阻塞。

同步和异步

  • 用户线程发起 IO 操作,阻塞等待 IO 操作完成,则操作是同步的;若用户发起 IO 操作,不必等待操作完成,等待内核完成 IO 操作后通知用户线程,则为异步,如常见的 aio_read 函数

并发和并行

  • 并发(concurrency):逻辑上具备同时处理多个任务的能力。
  • 并行(parallesim):物理上在同一时刻执行多个并发任务,依赖多核处理器等物理设备。

IO 发展历史

  • 在没有协程的时代,处理 IO 操作我们一般使用下面三种方式:

同步编程

  • 应用程序阻塞等待IO结果(比如等待打开一个大的文件,或者等待远端服务器的响应)。
Java 复制代码
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class SynchronousIO {
    public static void main(String[] args) {
        try (BufferedReader reader = new BufferedReader(new FileReader("example.txt"))) {
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
txt 复制代码
优点:编程简单,方便理解
缺点:阻塞读取,效率低下,与 IO 无关的操作也需要等待 IO 完成

异步多线程/进程

  • 将IO操作频繁的逻辑、或者单纯的IO操作独立到一/多个线程中,业务线程与IO线程间靠通信/全局变量来共享数据。
Java 复制代码
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AsynchronousMultiThreadIO {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(4);

        executor.submit(() -> {
            try (BufferedReader reader = new BufferedReader(new FileReader("example.txt"))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });

        executor.shutdown();
    }
}
txt 复制代码
优点:多线程处理,提高系统响应速度;充分利用 CPU 资源、避免阻塞其它业务
缺点:上下文切换成本较高,编程复杂度较高,需要管理大量线程

异步消息 + 回调函数(响应式编程)

  • 在响应式编程中,IO 操作是非阻塞的,并且通过回调函数来处理结果。

  • 示例代码(使用 CompletableFuture)

Java 复制代码
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;

public class AsynchronousCallbackIO {
    public static void main(String[] args) {
        CompletableFuture.runAsync(() -> {
            try (BufferedReader reader = new BufferedReader(new FileReader("example.txt"))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).thenRun(() -> System.out.println("File reading completed."));
    }
}
  • 示例代码(使用 Reactor):
Java 复制代码
import reactor.core.publisher.Mono;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class ReactiveIO {
    public static void main(String[] args) {
        Path path = Paths.get("example.txt");
        Mono.fromCallable(() -> Files.readString(path))
            .subscribe(content -> System.out.println(content),
                       error -> error.printStackTrace(),
                       () -> System.out.println("File reading completed."));
    }
}
txt 复制代码
优点:
1、相比多线程 IO,响应式编程的资源开销更低,能够更好地利用系统资源
2、响应式编程模型适合处理高并发、高吞吐量的应用,便于扩展和维护

缺点:
1、学习曲线陡峭:响应式编程需要理解异步编程和回调机制,对于初学者来说可能比较困难
2、调试复杂:由于异步操作的非顺序执行,调试和错误处理变得更加复杂

协程

协程基本概念

  • 维基百科定义:Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing multiple entry points for suspending and resuming execution at certain locations. Coroutines are well-suited for implementing more familiar program components such as cooperative tasks, exceptions, event loops, iterators, infinite lists and pipes.
  • 中文翻译:协程是一种计算机程序组件,它通过允许多个入口点在特定位置暂停和恢复执行,将非抢占式多任务的子程序进行了一般化。协程非常适合实现更熟悉的程序组件,如协作任务、异常、事件循环、迭代器、无限列表和管道。
  • 简而言之:协程(Goroutines)是一种轻量级的并发编程模型,由编程语言或运行时环境管理,用于执行并发任务。与传统的操作系统线程相比,协程更轻量级,切换开销更小,因此在高并发场景中非常高效。协程在许多现代编程语言中都有实现,包括 Go、Python、JavaScript(在某种程度上通过异步函数和生成器)等。
  • 协程从一定程度来讲,可以说是"用同步的语义解决异步问题",即业务逻辑看起来是同步的,但实际上并不阻塞当前线程(一般是靠事件循环处理来分发消息)。

go 示例代码

  • 下面是一个使用 Go 协程协作的示例,这个示例展示了如何使用 sync.WaitGroup 和 channel 来实现协程之间的协作:
Go 复制代码
package main

import (
	"fmt"
	"sync"
	"time"
)

// 定义一个 WaitGroup 以等待所有协程完成
var wg sync.WaitGroup

// 定义两个 channel 用于协程间的通信
var ch1 = make(chan int)
var ch2 = make(chan int)

func worker1() {
	defer wg.Done() // 在函数结束时减少 WaitGroup 计数
	for i := 0; i < 5; i++ {
		fmt.Println("Worker 1: Sending", i)
		ch1 <- i // 将数据发送到 ch1
		time.Sleep(500 * time.Millisecond)
	}
	close(ch1) // 关闭 channel,通知 worker2 没有更多数据
}

func worker2() {
	defer wg.Done() // 在函数结束时减少 WaitGroup 计数
	for {
		val, ok := <-ch1 // 从 ch1 接收数据
		if !ok {
			break // 如果 ch1 已关闭,退出循环
		}
		fmt.Println("Worker 2: Received", val)
		fmt.Println("Worker 2: Sending", val*val)
		ch2 <- val * val // 将数据发送到 ch2
		time.Sleep(500 * time.Millisecond)
	}
	close(ch2) // 关闭 channel,通知 main 没有更多数据
}

func main() {
	// 启动 worker1 和 worker2 协程
	wg.Add(2)
	go worker1()
	go worker2()

	// 在主协程中从 ch2 接收数据
	go func() {
		for val := range ch2 {
			fmt.Println("Main: Received", val)
		}
	}()

	wg.Wait() // 等待所有 worker 协程完成
}

协程和线程的区别

  • 协程属于用户级线程,线程属于内核级线程,线程的创建、上下文切换远比协程消耗更大。
  • 协程属于非抢占式,不会被其它协程所抢占,而是由开发者自己调度;线程属于抢占式,受到操作系统调度。
  • 协程的编码相比与多线程的编码更加复杂,但是协程大多数场景下更适合大并发任务。

个人简介

👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.

🚀 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。

🧠 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。

💡 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。

🌐 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。

📖 保持关注我的博客,让我们共同追求技术卓越。

相关推荐
Moonbit1 小时前
MoonBit 作者寄语 2025 级清华深圳新生
前端·后端·程序员
前端的阶梯1 小时前
开发一个支持支付功能的微信小程序的注意事项,含泪送上
前端·后端·全栈
咕噜分发企业签名APP加固彭于晏1 小时前
腾讯元器的优点是什么
前端·后端
AAA修煤气灶刘哥2 小时前
Swagger 用着糟心?试试 Knife4j,后端开发狂喜
后端·面试
bobz9652 小时前
MCP on windows
后端
泡海椒2 小时前
jquickexcel 全功能指南:从数据导入到精美导出的完整流程
后端
iOS开发上架哦3 小时前
移动端网页调试实战,键盘弹出与视口错位问题的定位与优化
后端
百度Geek说3 小时前
PaddleMIX推出扩散模型推理加速Fast-Diffusers:自研蒸馏加速方法FLUX-Lightning实现4步图像生成
后端
gopher_looklook3 小时前
Go并发实战:singleflight 源码解读与二次封装
数据结构·后端·go
用户833810251223 小时前
我为什么做PmMock:让接口设计不再头疼
前端·后端