理解Go的Goroutine和channel
发布日期:2021-05-06 20:09:45 浏览次数:18 分类:技术文章

本文共 3986 字,大约阅读时间需要 13 分钟。

进程,线程的概念在操作系统的书上已经有详细的介绍。进程是内存资源管理和cpu调度的执行单元。为了有效利用多核处理器的优势,将进程进一步细分,允许一个进程里存在多个线程,这多个线程还是共享同一片内存空间,但cpu调度的最小单元变成了线程。

那协程又是什么东西,以及与线程的差异性??

协程,可以看作是轻量级的线程。但与线程不同的是,线程的切换是由操作系统控制的,而协程的切换则是由用户控制的。

最早支持协程的程序语言应该是lisp方言scheme里的continuation(续延),续延允许scheme保存任意函数调用的现场,保存起来并重新执行。Lua,C#,python等语言也有自己的协程实现。

go的goroutinue

今天要讲的goroutinue,本质上就是协程。但有两点不同:

1. goroutinue可以实现并行,也就是说,多个协程可以在多个处理器同时跑。而协程同一时刻只能在一个处理器上跑(把宿主语言想象成单线程的就好了)。

2. goroutine之间的通信是通过channel,而协程的通信是通过yield和resume()操作。

在Go里实现goroutine非常简单,只需要在函数的调用前面加关键字go即可,

go doSth()

下面的例子演示,启动10个goroutines分别打印索引。

package mainimport ("fmt""time"	)func main() {	for i:=1;i<10;i++ {		go func(i int) {			fmt.Println(i)		}(i)	}	//暂停一会,保证打印全部结束	time.Sleep(1e9)}

在分析goroutine执行的随机性和并发性,把goroutine看作是java的守护线程是完全可以的。上面的例子中,启动了10个goroutine,再加上main函数的主goroutine,总共有11个goroutines。由于goroutine类似于”守护线程“,如果主goroutine不等待片刻,可能程序就没有输出打印了。上面的例子输出如下:(输出的索引是完全随机的)

 

go的channel

在java的世界里,并发主要是靠锁住临界资源(共享内存)来保证同步的。而channel则是goroutinues之间进行通信的利器。

channel可以形象比喻为工厂里的传送带,一头的生产者goroutine往传输带放东西,另一头的消费者goroutinue则从输送带取东西。channel实际上是一个有类型的消息队列,遵循先进先出的特点。

1. channel的操作符号

ch <- ele 表示ele被发送给channel ch;

ele2 <- ch 表示从channel ch取一个值,然后赋给ele2

2. 阻塞式channel

channel默认是没有缓冲区的,也就是说,通信是阻塞的。send操作必须等到有消费者accept才算完成。

举个栗子

package mainimport "fmt"func main() {	ch1 := make(chan int)	go pump(ch1) // pump hangs	fmt.Println(<-ch1) // prints only 0}func pump(ch chan int) {	for i:= 0; ; i++ {		ch <- i	}}

上面代码pump()里的channel在接受到第一个元素后就被阻塞了,直到主goroutinue拿走了数据。最终channel阻塞在接受第二个元素,程序只打印 0

3 带有buff的channel

没有buff的channel只能容纳一个元素,而带有buff的channel则可以非阻塞容纳N个元素。发送数据到buffed channel不会被阻塞,除非channel已满;同样的,从buffed channel取数据也不会被阻塞,除非channel空了。这有点像java的ConcurrentLinkedQueue。

goroutine和channel的应用

结合goroutine和channel,可以模拟出java处理并发情况的若干情景

1. 实现future

package mainimport "fmt"import "time"func main() {	future := heavyCalculation()	fmt.Println(<-future)}func heavyCalculation() (chan int) {		future := make(chan int)	go func() {		//模拟耗时计算		time.Sleep(1e9)		future <- 666	}()		return future}

2. 实现CountDownLatch

package mainimport "fmt"func main() {	nTask := 5	ch := make(chan int)	for i:=1;i<=nTask;i++ {		go doTask(ch)	}	for i:=1;i<=nTask;i++ {		<-ch	}	fmt.Println("finished all tasks")}func doTask(ch chan<- int) {	//doSth...	ch<- 0}

3. 并发访问对象

Hashtable是线程安全的,意味着多条线程同时操作hashtable对象是不会引起状态不一致的。查看Hashtable源代码可知,其几乎全部方法都添加synchronized关键字,例如put(),remove()操作。在go里,我们可以在对象内部保存一个函数类型的channel,涉及对象状态的操作都放入channel里,对象初始化的时候开启一条goroutinue,不停地执行匿名函数。

package mainimport ("fmt""strconv""time"	)type Person struct {	Name string	salary float64	chF chan func()}func NewPerson(name string, salary float64) *Person {	p := &Person{name, salary, make(chan func())}	go p.backend()	return p}func (p *Person) backend() {	for f := range p.chF {		f()	}}func (p *Person) AddSalary(sal float64) {	p.chF <- func() { p.salary += sal }  // (ThreadSafe)		// p.salary += sal (NotThreadSafe)}func (p *Person) ReduceSalary(sal float64) {	p.chF <- func() { p.salary -= sal }  // (ThreadSafe)		// p.salary -= sal (NotThreadSafe)}func (p *Person) Salary() float64 {	fChan := make(chan float64)	p.chF <- func() { fChan <- p.salary }	return <-fChan}func (p *Person) String() string {	return p.Name + " - salary is: " + 	strconv.FormatFloat(p.Salary(), 'f', 2, 64)}func main() {	p := NewPerson("Kingston", 8888.8)	fmt.Println(p)	for i:=1;i<=500;i++ {		go func() {			p.AddSalary(1);		}()	}	for i:=1;i<=500;i++ {		go func() {			p.ReduceSalary(1);		}()	}	time.Sleep(3e9)	fmt.Println("After changed:")	fmt.Println(p)}

4. 生产者消费者模式

每次涉及到并发情景,都喜欢用生产者消费者模式,因为它太经典啦

2个面包师同时生产面包,5个顾客同时取面包(尽管以下例子的打印不能说明并发的真实情况,因为channel的操作和打印的组合不是原子操作,但不影响程序的逻辑)

package mainimport ("fmt""time")func main() {	bread := make(chan int,3)	for i:=1;i<=2;i++ {		go produce(bread)	}	for i:=1;i<=5;i++ {		go consume(bread)	}	time.Sleep(1e9)}func produce(ch chan<- int) {	for {		ch <- 1		fmt.Println("produce bread")		time.Sleep(100 * time.Millisecond)	}}func consume(ch <-chan int) {	for {		<-ch		fmt.Println("take bread")		time.Sleep(200 * time.Millisecond)	}}

 

 

 

 

上一篇:游戏服务器之防御式开发
下一篇:手游服务端框架之自定义orm持久化工具

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2025年03月27日 13时41分20秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

for循环读取数组遇问题:dexError: invalid index to scalar variable. 2019-03-03
编写测试用例的实用小技巧 2019-03-03
c语言贪吃蛇控制台版 2019-03-03
Windows10 下springboot应用无法被外部网络访问 2019-03-03
报错:在IDEA中springboot项目操作数据库,配置文件驱动com.mysql.cj.jdbc.Driver标红 2019-03-03
redis报错(error) NOAUTH Authentication required.解决办法 2019-03-03
【树形dp】P1273 有线电视网 2019-03-03
【最短路】P4408 [NOI2003]逃学的小孩 2019-03-03
2020电工(初级)考试及电工(初级)考试软件 2019-03-03
2020N1叉车司机模拟考试题库及N1叉车司机复审模拟考试 2019-03-03
2020年制冷与空调设备运行操作答案解析及制冷与空调设备运行操作考试总结 2019-03-03
2020年保育员(初级)考试资料及保育员(初级)新版试题 2019-03-03
2020年茶艺师(高级)考试内容及茶艺师(高级)考试申请表 2019-03-03
2021年重氮化工艺考试题库及重氮化工艺考试报名 2019-03-03
2021年车工(高级)考试总结及车工(高级)试题及答案 2019-03-03
2021年压力焊证考试及压力焊实操考试视频 2019-03-03
2021年低压电工考试及低压电工考试申请表 2019-03-03
2021年低压电工考试及低压电工考试申请表 2019-03-03
2021年A特种设备相关管理(电梯)考试APP及A特种设备相关管理(电梯)复审考试 2019-03-03
2021年N1叉车司机考试题及N1叉车司机复审模拟考试 2019-03-03