协程
呐~今天来讲一下协程这个概念,协程相比传统的系统级的线程和进程相比,其最大的优势就在于其“轻量级”,可以轻松创建上百万个协程也不会导致系统资源的衰竭,但线程和进程通常也最多不能超过1万个。
其他的语言基本都是通过库的方式支持的,但用库的方式支持的功能也并不是很完善,因此在这里就可以体现出Go语言的强大啦~Go语言可是在语言级别上就支持轻量级的线程呢,叫goroutine
,
协程基础
goroutine
是Go语言并行设计的一个核心内容,其实说白了,goroutine
就是协程,但是他比线程要小的多,执行goroutine
只需极少的栈内存,也正因如此,程序才可以同时运行成千上万个并发任务。
goroutine
比thread
更易用,更高效,更轻便
一起来看一个简单的程序:
package main
import (
"fmt"
"runtime"
)
func say(s string) {
for i := 0; i < 5; i++ {
runtime.Gosched()
fmt.Println(s)
}
}
func main() {
go say("Coding!")
say("Hello!")
}
/** The result is:
Hello!
Hello!
Coding!
Hello!
Coding!
Hello!
Coding!
Hello!
Coding!
Coding!
*/
在这里面有一个需要注意的内容就是程序当中的runtime.Gosched()
函数,它表示让CPU把时间片让给别人,下次某个时候继续恢复执行该goroutine
。
我们还可以通过设置runtime.GOMAXPROCS(n)
告诉调度器最大可以使用多少个线程,如果n是小于1的,那么将不会改变当前的设置。
协程间的通信
刚才上面的例子这是入了一个门(但其实你门都没跨进去 ̄□ ̄||),因为并发并不是这样简简单单就可以解决的,并发编程的难度在于协调,而协调就要通过交流,这么说来,并发单元之间的通信实际上才是最为困难的。
通常有两种常见的并发通信模型:共享数据和消息
共享数据是指多个并发单元分别保持对同一个数据的引用,实现对该数据的共享,当然,在实际的工程当中,最常见的非共享内存莫属了~
喏,看下面这个例子:
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
var counter int = 0
func Count(lock *sync.Mutex) {
lock.Lock()
counter++
fmt.Println(counter)
lock.Unlock()
}
func main() {
lock := &sync.Mutex{}
for i := 0; i < 10; i++ {
go Count(lock)
}
for {
lock.Lock()
c := counter
lock.Unlock()
runtime.Gosched()
if c >= 10 {
break
}
}
}
/** The result is:
1
2
3
4
5
6
7
8
9
10
*/
在这个例子当中呢,我们在10个goroutine
中共享了变量counter
,又因为每一个goroutine
是并发执行的,因此我们引入了锁这个概念,也就是代码中的lock
。每次对counter
进行操作的时候,先要将锁锁住,操作完成后,再将锁打开。
但其实我们发现这不就是在输入从1到10嘛,直接一个for循环不就好了(⊙o⊙)…
所以啊,这个并不是Go语言的核心,真正的核心在后面呢,Go语言提供的另一种通信模型,即以消息机制而非共享内存作为通信方式。
那么消息机制又是个什么意思嘞~
消息机制认为每个并发单元是自包含的、独立的个体,并且都有自己的变量,但在不同并发单元间这些变量是不共享的。
那么,Go语言又是如何处理这种消息机制的呢,那么请看《Go并发编程(下)》,我将为大家讲解另一个好东西-channel
。
网友评论