摘要
本文内容来源于网络,个人收集整理,请勿传播
目前几种主流的并发模型:
- 多线程,每个线程一次处理一个请求,在当前请求处理完成之前不会接收其它请求;但在高并发环境下,多线程的开销比较大;
- 基于回调的异步IO,如Nginx服务器使用的epoll模型,这种模式通过事件驱动的方式使用异步IO,使服务器持续运转,但人的思维模式是串行的,大量回调函数会把流程分割,对于问题本身的反应不够自然;
- 协程,不需要抢占式调度,可以有效提高线程的任务并发性,而避免多线程的缺点;但原生支持协程的语言还很少。
协程coroutine
是Go语言中的轻量级线程实现,由Go运行时runtime
管理。
在一个函数调用前加上go
关键字,这次调用就会在一个新的goroutine
中并发执行。当被调用的函数返回时,这个goroutine
也自动结束。需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃。
协程是 golang 并发的最小单元,类似于其他语言的线程,只不过线程的实现借助了操作系统的实现,每次线程的调度都是一次系统调用,需要从用户态切换到内核态,这是一项非常耗时的操作,因此一般的程序里面线程太多会导致大量的性能耗费在线程切换上。而在 golang 内部实现了这种调度,协程在这种调度下面的切换非常的轻量级,成百上千的协程跑在一个 golang 程序里面是很正常的事情
goroutine
goroutine
是一个轻量级的执行线程。假设有一个函数调用f(s)
,要在goroutine
中调用此函数,请使用go f(s)
。 这个新的goroutine
将与调用同时执行。
1 | package main |
channel
goroutine
之间通过channel
来通讯,可以认为channel
是一个管道或者先进先出的队列。你可以从一个goroutine
中向channel
发送数据,在另一个goroutine
中取出这个值。
1 | var channel chan int = make(chan int) |
生产者消费者
1 | package main |
单向通信
1 | func producer(c chan<-int){ |
生产消费缓冲
向带缓冲的channel
发送数据时,只有缓冲区满时,发送操作才会被阻塞。当缓冲区空时,接收才会阻塞。
1 | import ( |
channle超时机制
当一个channel
被read/write
阻塞时,会被一直阻塞下去,直到channel
关闭。产生一个异常退出程序。channel
内部没有超时的定时器。但我们可以用select
来实现channel
的超时机制
1 | package main |
多个消费者
1 | package main |
结合sync
1 | type Product struct { |
sync.WaitGroup
1 | var wg sync.WaitGroup // 申明一个信号量 |
使用
1 | package main |
select
1 | package main |
1 | package main |
1 | package main |
runtime
runtime
包中有几个处理goroutine
的函数
runtime.GOMAXPROCS(1)
- 计算密集型使用多核心要快很多
- io密集型使用多核心很有可能比单核心要慢
下面是计算密集型使用多核心的例子
1 | package main |
runtime.NumCPU()
返回了cpu核数
runtime.NumGoroutine()
返回当前进程的goroutine
线程数。即便我们没有开启新的goroutine
。
1 | package main |
runtime.Gosched
Gosched()
让当前正在执行的goroutine
放弃CPU执行权限。调度器安排其他正在等待的线程运行。
1 | package main |
runtime.Goexit
runtime.Goexit()
函数用于终止当前的goroutine
,单defer
函数将会继续被调用。
1 | package main |