摘要:小白一枚,最近在研究,记录自己学习过程中的一些笔记,以及自己的理解。因此我们可以在函数中,将主函数挂起,增加等待协程调用的事件。中协程调度讲到并发,就要提到中的协程调度。
小白一枚,最近在研究golang,记录自己学习过程中的一些笔记,以及自己的理解。
go中协程的实现
go中协程的sync同步锁
go中信道channel
go中的range
go中的select切换协程
go中带缓存的channel
go中协程调度
原文的地址为:https://github.com/forthealll...
欢迎star
介绍go中的协程之前,首先看以下go中的defer函数,defer函数不是普通的函数,defer函数会在普通函数返回之后执行。defer函数中可以释放函数内部变量、关闭数据库连接等等操作,举例来说:
func print(){ fmt.Println(2); } func main() { defer print(); fmt.Println(1); }
上述的例子中先输出1后输出2,说明defer确实是在普通函数调用结束之后执行的。
go中使用协程的方式来处理并发,协程可以理解成更小的线程,占用空间小且线程上下文切换的成本少。
可以再为具体的描述以下协程的好处,协程比线程更加轻量,使用4K栈的内存就可以创建它们,可以用很小的内存占用就可以处理大量的任务。
在go中,携程是通过go关键字来调用,从关键字可以看出,golang的一个十分重要的特点就是协程,有句话叫“协程在手,说go就go”。
1、go中协程的实现下面我们来看一个例子:
func printOne(){ fmt.Println(1); } func printTwo(){ fmt.Println(2); } func printThree(){ fmt.Println(3); } func main() { go printOne(); go printTwo(); go printThree(); }
执行上述的main函数,我们发现并没有像我们想的那样输出有123的输出,原因在于虽然协程是并发的,但是如果在协程调用前退出了调用协程的函数后,协程会随着程序的消亡而消亡。
因此我们可以在main函数中,将主函数挂起,增加等待协程调用的事件。
func main() { go printOne(); go printTwo(); go printThree(); time.Sleep(5 * 1e9); }
这样会有相应的go关键字修饰的协程函数的调用。我们来看分别执行3次的结果。
第一次
1
3
2
第二次
3
2
1
第三次
3
1
2
我们发现因为协程是并发执行的,我们无法确定其调用的顺序,因此 每次的调用主函数的返回结果都是不确定的。
从协程的上述例子中,我们可以看出使用协程的时候必须还要考虑两个问题:
如何控制协程的调用顺序,特别是当不同的协程同时访问同一个资源。
如何实现不同协程间的通信
问题1,可以通过sync的同步锁来实现,问题2,go中提供了channel来实现不同协程间的通信。
2、go中协程的sync同步锁go中sync包提供了2个锁,互斥锁sync.Mutex和读写锁sync.RWMutex.我们用互斥锁来解决上述的同步问题,改写上述的例子:
func printOne(m *sync.Mutex){ m.Lock(); fmt.Println(1); defer m.Unlock(); } func printTwo(m *sync.Mutex){ m.Lock(); fmt.Println(2); defer m.Unlock(); } func printThree(m *sync.Mutex){ m.Lock(); fmt.Println(3); defer m.Unlock(); } func main() { m:= new(sync.Mutex); go printOne(m); go printTwo(m); go printThree(m); time.Sleep(5 * 1e9); }
通过互斥锁,可以发现每次运行,确实都依次输出了1,2,3
3、go中信道channelgo中有一种特殊的类型通道channel,可以通过channel来发送类型化的数据,实现在协程之间的通信,通过通道的通信方式也保证了同步性。
channel的声明方式很简单:
var ch1 chan string ch1 = make(chan string)
我们用ch表示通道,通道的符号包括了流向通道(发送): ch <- int1 和从通道流出(接收) int2 = <- ch。
同时go中也支持声明单向通道:
var ch1 chan int //普通的channel var ch2 chan <- int //只用于写int数据 var ch3 <- chan int //只用于读int数据
上述定义的都是不带缓存区,或者说长度为1的channel,这种channel的特点就是:
一旦有数据被放入channel,那么该数据必须被取走才能让另一条数据放入,这就是同步的channel,channel的发送者和接受者在同一时间只交流一条数据,然后必须等待另一边完成相应的发送和接受动作。
我们还是用上述的输出123的例子,用同步channel来实现同步的输出。
func printOne(cs chan int){ fmt.Println(1); cs <- 1 } func printTwo(cs chan int){ <-cs fmt.Println(2); defer close(cs); } func main() { cs := make(chan int); go printOne(cs); go printTwo(cs); time.Sleep(5 * 1e9); }
上述的例子中会依次输出12,这样我们通过同步channel的方式实现了同步的输出。
我们前面讲到用为了等待go协程执行完成,我们在main函数中用time.sleep来挂起主函数,其实main函数本身也可以看成一个协程,如果使用channel,就不用在main函数中用time.sleep来挂起。
我们改写上述的例子:
func printOne(cs chan int){ fmt.Println(1); cs <- 1 } func main() { cs := make(chan int); go printOne(cs); <-cs; close(cs); }
上述的例子中,会输出 1 ,我们并没有在主函数中通过time.sleep的方式来挂起,转而用一个等待写入的channel来代替。
注意:通道可以被显式的关闭,当需要告诉接受者不会种子提供新的值的时候,就需要关闭通道。
4、go中的range上面我们也讲到要及时的关闭channel,但是持续的访问数据源并检查channel是否已经关闭,并不高效。go中提供了range关键字。
range关键字在使用channel的时候,会自动等待channel的动作一直到channel关闭。通俗点将就是可以channel可以自动开关。
同样的来举例:
func input(cs chan int,count int){ for i:=1;i<=count;i++ { cs <- i } } func output(cs chan int){ for s:= range cs { fmt.Println(s); } } func main() { cs := make(chan int); go input(cs,5); go output(cs); time.Sleep(3*1e9) }
上述的例子会依次的输出1,2,3,4,5. 通过使用range关键字,当channel被关闭时,接受者的for循环也就自动停止了。
5、go中的select切换协程从不同的并发执行过程中获取值可以通过关键字select来完成,它和switch控制语句非常相似,也被称为通信开关。
首先要明确select做了什么??
select中存在着一种轮询机制,select监听进入通道的数据,也可以是通道发送值的时候,监听到相应的行为后就执行case里面的操作。
select的声明:
select { case u:= <- ch1: ... case v:= <- ch2; ... }
同样的来看一下具体使用select的例子:
func channel1(cs chan int,count int){ for i:=1;i<=count;i++ { cs <- i } } func channel2(cs chan int,count int){ for i:=1;i<=count;i++ { cs <- i } } func selectTest(cs1 ,cs2 chan int){ for i:=1;i<10;i++ { select { case u:=<-cs1: fmt.Println(u); case v:=<-cs2: fmt.Println(v); } } } func main() { cs1 := make(chan int); cs2 := make(chan int); go channel1(cs1,5); go channel2(cs2,3); go selectTest(cs1,cs2); time.Sleep(3*1e9) } 输出结果为:1,2,1,2,3,3,4,5 总共8个数据。且因为没有做同步控制,因此运行几次后的输出结果是不相同的。6、go中带缓存的channel
前面讲到的都是不带缓存的channel或者说长度为1的channel,实际上channel也是可以带缓存的,我们可以在声明的时候执行channel的长度。
ch = make(chan string,3)
比如上述的例子中,指定了ch这个channel的长度为3,长度不为1的channel,就可以称之为带缓存的channel.
带缓存的channel可以连续写入,直到长度占满为止。
ch <- 1 ch <- 2 ch <- 37、go中协程调度
讲到并发,就要提到go中的协程调度。go中的runtime包,提供了调度器的功能。runtime包提供了以下几个方法:
Gosched:让当前线程让出 cpu 以让其它线程运行,它不会挂起当前线程,因此当前线程未来会继续执行
NumCPU:返回当前系统的 CPU 核数量
GOMAXPROCS:设置最大的可同时使用的 CPU 核数
Goexit:退出当前 goroutine(但是defer语句会照常执行)
NumGoroutine:返回正在执行和排队的任务总数
GOOS:目标操作系统
对于多核CPU的机器,go可以显示的指定编译器将go的协程调度到多个CPU上运行
import "runtime" ... cpuNum:=runtime.NumCPU; runtime.GOMAXPROCS(cpuNum)
来聊聊GO中的调度原理,首先定义以下模型的概念:
M:内核中的线程的数目
G:go中的协程,并发的最小单元,在go中通过go关键字来创建
P:处理器,即协程G的上下文,每个P会维护一个本地的协程队列。
接着来看解释GO中协程调度的经典图:
我们来解释上图:
P是处理器的个数,我们经常将调度器的GOMAXPROCS设置成CPU的个数,因此这里P一般来说是机器CPU的个数。
M是线程,在P处理器上关联一个线程,P和M的一组配对组成了局部的协程队列
G就是协程,需要被添加到由P和M组成的局部队列中依次处理
除了局部的协程外,在全局还维护了一个协程队列。
如果局部协程队列中处理完了所有队列,且没有新队列,那么M线程会取消对于CPU的占用,M线程进入休眠
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/48344.html
摘要:协程完全有用户态程序控制,所以也被成为用户态的线程。目前支持协程的语言有很多,例如等。协程之旅前篇结束,下一篇文章我们将深入分析原生协程部分的实现。 写在最前 Swoole协程经历了几个里程碑,我们需要在前进的道路上不断总结与回顾自己的发展历程,正所谓温故而知新,本系列文章将分为协程之旅前、中、后三篇。 前篇主要介绍协程的概念和Swoole几个版本协程实现的主要方案技术; 中篇主...
摘要:所以系统的吞吐能力取决于每个线程的操作耗时。另外过多的线程,也会带来更多的开销。其代表派是以及里的新秀。类似线程也有自己的栈。线程中断的条件只有两个,一个是抛异常,另外一个就是。 什么是协程(coroutine) 这东西其实有很多名词,比如有的人喜欢称为纤程(Fiber),或者绿色线程(GreenThread)。其实最直观的解释可以定义为线程的线程。有点拗口,但本质上就是这样。 我...
摘要:主函数查询数据不手动释放的连接不会归还连接池,会在析构时丢弃执行结果为,说明是并行执行的。主函数查询数据即便抛出了异常,仍然能执行到,没有导致内的一直处于阻塞状态。主函数一次性定时持续定时停止定时 协程 Mix PHP V2 基于 Swoole 4 的 PHP Stream Hook 协程技术开发,协程使用方式与 Golang 几乎一致,包括框架封装的协程池、连接池、命令行处理都大量参...
摘要:进程线程和协程进程的定义进程,是计算机中已运行程序的实体。协程和线程的关系协程是在语言层面实现对线程的调度,避免了内核级别的上下文消耗。和都引入了消息调度系统模型,来避免锁的影响和进程线程开销大的问题。 进程、线程和协程 进程的定义: 进程,是计算机中已运行程序的实体。程序本身只是指令、数据及其组织形式的描述,进程才是程序的真正运行实例。 线程的定义: 操作系统能够进行运算调度的最小单...
摘要:事件循环是异步编程的底层基石。对事件集合进行轮询,调用回调函数等一轮事件循环结束,循环往复。协程直接利用代码的执行位置来表示状态,而回调则是维护了一堆数据结构来处理状态。时代的协程技术主要是,另一个比较小众。 Coding Crush Python开发工程师 主要负责岂安科技业务风险情报系统redq。 引言 1.1. 存储器山 存储器山是 Randal Bryant 在《深入...
阅读 2580·2021-10-08 10:04
阅读 462·2021-09-22 10:02
阅读 5454·2021-09-03 10:49
阅读 438·2021-09-02 09:47
阅读 1506·2021-02-22 17:18
阅读 1960·2019-08-30 15:53
阅读 2813·2019-08-30 15:44
阅读 716·2019-08-30 13:20