进程、线程、协程与Goroutine

进程与线程(隔离与并行)

进程

进程可看作为分配资源的基本单位。比如你new出了一块内存,就是操作系统将一块物理内存映射到你的进程地址空间上(进程创建必须分配一个完整的独立地址空间),这块内存就属于这个进程,进程内的所有线程都可以访问这块内存,其他进程就访问不了,其他类型的资源也是同理。所以进程是分配资源的基本单位,也是我们说的隔离。

线程

线程作为独立运行和独立调度的基本单位,进而我们可以认为线程是进程的一个执行流,独立执行它自己的程序代码。线程上下文一般只包含CPU上下文及其他的线程管理信息,线程创建的开销主要取决于为线程堆栈的建立而分配内存的开销,这些开销并不大。线程还分为系统级别和用户级线程,用户级别线程对引起阻塞的系统调用的调用会立即阻塞该线程所属的整个进程,而内核实现线程则会导致线程上下文切换的开销跟进程一样大,所以经常的折衷的方法是轻量级进程(Lightweight)。在 Linux 中,一个线程组基本上就是实现了多线程应用的一组轻量级进程。线程的作用就在于充分使用硬件CPU,也就是我们说的并行。

协程

协程的核心机制

在实模式编程下,理论上操作系统只能加载一个进程,那个时候进程要使用系统服务的方法很简单,就是手工产生一个中断,就会触发CPU的中断处理机制,会保护好发起中断的现场,会将当前执行地址设置为对应的中断处理函数的地址,处理完以后回到刚刚保存的现场。其实这个过程,本质上就是协程的核心流程了。

为何使用中断

基于中断的方式,发起方和处理方可以使用自己的context,系统通过中断的方法来达到提供系统服务的目的,一个很重要的原因就是可以保障在很多情况下,都能让系统处理函数至少能有一个可用的context(属于系统的资源),这样当用户进程的context资源耗尽的情况下,也能调用一些系统服务。

协程与线程

  1. 协程与线程主要区别是:协程将不再被内核调度,而是交给了程序自己;而线程是将自己交给内核调度。所以也不难理解golang中调度器的存在。
  2. 从我们应用角度来说,我们一般将协程理解为用户态轻量级线程,是对内核透明的,也就是系统并不知道有协程的存在,是完全由用户的程序自己调度的,因为是由用户程序自己控制,那么就很难像抢占式调度那样做到强制的CPU控制权切换到其他进程/线程,通常只能进行协作式调度,需要协程自己主动把控制权转让出去之后,其他协程才能被执行到。

协程与Goruntine

  1. 协程是Coroutine,Go语言的协程是Goruntine,两者是不一样的。
  2. Golang语言作者Rob Pike也说,“Goroutine是一个与其他goroutines 并发运行在同一地址空间的Go函数或方法。一个运行的程序由一个或更多个goroutine组成。它与线程、协程、进程等不同。它是一个goroutine“。
  3. Go 协程通过通道来通信,而协程通过让出和恢复操作来通信。
  4. Go 协程比协程更强大。因为Golang 在 runtime、系统调用等多方面对 goroutine 调度进行了封装和处理,也就是Golang 有自己的调度器,工作方式基本上是协作式,而不是抢占式,但也不是完全的协作式调度,例如在系统调用的函数入口处会有抢占。当遇到长时间执行或者进行系统调用时,会主动把当前 goroutine 的CPU ( P) 转让出去,让其他 goroutine 能被调度并执行,也就是我们为什么说 Golang 从语言层面支持了协程。简单的说就是Golang自己实现了协程并叫做Goruntine
  5. Goruntine的优势在于并行和非常低的资源使用,体现在内存消耗方面和切换(调度)开销方面,每个 goroutine (协程) 默认占用内存远比 Java 、C 的线程少,只有2KB,而线程则需要8MB;线程切换涉及模式切换(从用户态切换到内核态)、16个寄存器、PC、SP…等寄存器的刷新等;而goroutine 只有三个寄存器的值修改:PC / SP / DX。

GOMAXPROCS

官方解释: GOMAXPROCS sets the maximum number of CPUs that can be executing simultaneously。 很清楚,就是限制CPU数,限制CPU数,本质上是什么,就是限制并行数,并行数即同时执行数量,执行单元即线程,即限制最大并行线程数量。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
当我们需要进行并发编程时,通常会使用线程进程来实现。但是,线程进程的开销比较大,而且容易发生死锁、竞条件等问题。Golang 提供了协程Goroutine)和通道(Channel)等并发机制,可以轻松实现高效的并发编程。 协程Goroutine)是一种轻量级线程,由 Golang 运行时管理。与线程相比,协程的开销非常小,可以轻松创建数以千计的协程,并发执行任务。协程之间通过通道(Channel)进行通信,以实现数据共享和同步。 通道(Channel)是一种特殊的数据类型,用于协程之间的通信。通道可以用于发送和接收数据,通道的发送和接收操作是原子性的,因此可以保证数据的同步和安全。通道有两种类型:有缓冲通道和无缓冲通道。有缓冲通道可以缓存一定数量的数据,但发送和接收操作可能会发生阻塞;无缓冲通道必须有发送和接收操作同时进行,否则会发生阻塞。 以下是一个简单的示例,演示了如何使用协程和通道实现并发处理任务: ```go func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Printf("worker %d started job %d\n", id, j) time.Sleep(time.Second) fmt.Printf("worker %d finished job %d\n", id, j) results <- j * 2 } } func main() { jobs := make(chan int, 100) results := make(chan int, 100) for w := 1; w <= 3; w++ { go worker(w, jobs, results) } for j := 1; j <= 5; j++ { jobs <- j } close(jobs) for r := 1; r <= 5; r++ { <-results } } ``` 在这个示例中,我们创建了一个有缓冲通道 `jobs` 和一个无缓冲通道 `results`。然后创建了 3 个协程 `worker`,用于处理任务。在 `main` 函数中,我们向 `jobs` 通道中发送了 5 个任务,然后关闭了 `jobs` 通道。接下来,我们从 `results` 通道中接收了 5 个结果。在 `worker` 函数中,我们使用 `range` 循环从 `jobs` 通道中接收任务,然后处理任务并将结果发送到 `results` 通道中。 通过协程和通道的组合,我们可以轻松实现并发处理任务,并且避免了线程进程的开销和安全问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值