Go&Python协程比较

前言

一个应用程序是运行在机器上的一个进程,进程是一个运行在自己内存地址空间里的独立执行体。一个进程由一个或多个操作系统系统线程组成,这些线程其实是共享同一个内存地址空间的一起工作的执行体。一个并发程序可以在一个处理器使用多线程来执行任务,但只有同一个程序在某个时间点同时运行在多核或处理器上的才真正的并行。所以并发可以是并行,也可以不是。
使用多线程,使用的是共享数据,会出现竞态,要对数据加锁,但加锁对业务处理来说,会带来更多的复杂度。
使用多核处理器成本又太高。
所以出现协程,来实现单核或少量的处理器来实现大量的并发,而且不要处理多线程竞态问题。

Go

Go中,应用程序并发处理的部分叫goroutines,它是根据一个或多个线程的可用性,映射(多路复用)在线程之上的,工作在相同的地址空间,所以共享内存的方式一定是同步的,go中使用channels来同步协程。协程是很轻量的,可以运行在多个线程之间,也可以运行在线程之内。

Go中,使用GOMAXPROCS设置并行,最好等同于核心数。
go1.5开始,默认使用等于CPU数。

1
2
3
func init() {
runtime.GOMAXPROCS(2)
}

Go协程可以使用共享变量来通信,但不提倡,因为这种方式给所有的共享内存的多线程都带来了困难。go中使用通道通信。
通道实际上是类型化消息队列,FIFO。

1
2
3
4
5
6
7
8
9
func main() {
ch := make(chan string)
int1 := 1

ch <- int1 // 发送
// 流出
int2 := <- ch
<- ch // 单独调用获取通道的下一个值
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main 

import ("fmt", "time")

func main() {
ch := make(chan string)
go sendData(ch)
go getData(ch)
time.Sleep(1e9)
}

func senddata(ch chan string) {
ch <- "w"
ch <- "c"
ch <- "g"
}

func getData(ch chan string) {
for {
input := <-ch
fmt.Prinf("%s", input)
}
}
  • go协程使用go关键字
  • 协程通信使用同一个通道
  • 默认情况,通道是同步且无缓冲的,通道的发送/接收操作在对方准备好之前都是阻塞的
  • 带缓冲的通道,在缓冲满载之前,是不会阻塞的,从通道读取数据也不会阻塞,直到缓冲空了,首要使用无缓冲,只有在不确定的情况下使用缓冲
  • 信号量模式,在通道内放置一个值来处理结束的信号
  • for循环中,使用协程,实现并行
  • 使用select case切换协程
  • 协程要注意恢复,不能影响其他协程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    func server(workChan <-chan *Work) {
    for work := range workChan {
    go safelyDo(work)
    }
    }

    func safelyDo(work *Work) {
    defer func {
    if err := recover(); err != nil {
    log.Printf("Work failed with %s in %v", err, work)
    }
    }()
    do(work)
    }

Python

Python协程有一个发展历史
生成器
带有yield的函数,称为生成器函数,是生成器工厂。
生成器函数会创建一个生成器对象,把生成器传给next(..)函数时,生成器会向前,执行函数定义体中的下一个yield语句,返回产出的值,并在函数定义体的当前位置暂停。最后抛出StopIteration异常。

  1. 把生成器当初协程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    def simple_coroutine():
    print('start')
    x = yield # 会在yield处暂停, 产出值, 赋值给x
    print('received:', x)

    my_core = simple_coroutine() # 返回生成器对象
    next(my_core) # start 协程启动, 也叫预激
    my_core.send(42) # receiced 42 Traceback StopIteration

    def simple_coro2(a):
    print('-> Start: a=', a)
    b = yield a
    print('-> Receiced: b=', b)
    c = yield a + b
    print('-> Receiced: c=', c)

    my_coro2 = simple_coro2(14)
    next(my_coro2) # Started: a = 14 产出a值 14 暂停 等待b赋值
    my_coro2.send(28) # Received: b = 28 产出a+b的值 42 等待c赋值
    my_coro2.send(99) # Received: c = 99 Traceback StopIteration

未完待续。。

主要区别

  • Go协程可以并行,Python不行
  • Go协程通过通道来通信,Python通过让出和恢复操作来通信
  • Pythond的线程和协程是严格的1:N关系,Go是M:N关系

参考

https://github.com/itswcg/Books/blob/master/Go%E5%85%A5%E9%97%A8%E6%8C%87%E5%8D%97.pdf
https://github.com/itswcg/Books/blob/master/%E6%B5%81%E7%95%85%E7%9A%84python.pdf

----------本文完,感谢您的阅读----------