GoLang 高并发 Goroutine(一)
- 并发和并行
- Goroutine
-
- goroutine 是如何工作的
- fork-join并发模型
- 闭包
-
- 例1
- 例2
并发和并行
并发和并行:并发属于代码,并行属于一个运行的程序;具体是我们并没有编写并行的代码,而是期待在运行时能够并行的并发代码
Goroutine
goroutine 是一个并发函数(并不是并行)
func main(){
go sayHello()
}
func sayHello(){
fmt.Println("你好")
}
/*
使用匿名函数
func main(){
go func(){
fmt.Println("你好")
}()
}
*/
goroutine 是如何工作的
goroutine 既不是os线程,也不是由语言运行时管理的线程,它是一个协程(协程是一个非抢占式的简单并发子goroutine(函数、闭包或方法))。 goroutine没有定义自己的暂停方法和在运行点,Go语言在运行时会观察goroutine的运行状态,并在它们阻塞时自动挂起它们,然后在它们不阻塞时恢复它们。
fork-join并发模型
fork指在程序中的任意一点,可以将执行的子分支与其父节点同时运行;join在将来的某个时候,这些并发的执行分支将会合并在一起。
func main(){
sayHello := func(){
fmt.Println("你好")
}
go sayHello()
}
在这个例子中,没有join点,执行sayHello()的goroutine将在未来某个不确定的时间退出,而程序的其余部分将会继续执行;因为不确定sayHello函数是否会执行,具体而言goroutine将会被创建,但是可能在还没有执行,此时main goroutine已经退出结果是:可能看不到在屏幕上打印出 你好。有一种比较笨且没有任何实际意义的方法,就是在创建goroutine之后,使用time.Sleep让main goroutine睡眠一会,这种方式并不能保证结果的正确性,只是提供了一种让结果趋近于正确的方式。
因此要创建一个join使得main goroutine 和 sayHello goroutine 同步。
var wg sync.WaitGroup
sayHello := func(){
defer wg.Done()
fmt.Println("你好")
}
wg.Add(1)
go sayHello()
wg.Wait() // 连接点,main goroutine等待直到所有wg Done()
闭包
**闭包** 可以从创建它们的作用域获取变量;思考:如果我们在goroutine上运行一个闭包,那么闭包是在这些变量的副本上运行,还是原值的引用?
例1
var wg sync.WaitGroup
strs := "你好"
wg.Add(1)
go func(){
defer wg.Done()
strs = "哈哈" //修改了变量strs的值
}()
wg.Wait()
fmt.Println(strs) //哈哈
goroutine在它们所创建的相同地址空间内执行
例2
var wg sync.WaitGroup
for _ , str := range []string{"你好1","你好2","你好3"}{
wg.Add(1)
go func(){
defer wg.Done()
fmt.Println(str)
}()
}
/*
你好3
你好3
你好3
*/
该闭包在使用str的时候,字符串的迭代已经结束了。因为goroutine可能在任何时间点运行,所以不确定在goroutine中会打印什么值。还有一个问题是,如果在迭代结束后,str的值还在不在内存中,goroutine还能不能引用已经超出范围的内容。这涉及到Go语言运行时会对变量str的值的引用仍然保留,有内存转移到堆,所以goroutine仍然可以继续访问它。
var wg sync.WaitGroup
for _ , str := range []string{"你好1","你好2","你好3"}{
wg.Add(1)
go func(s string){
defer wg.Done()
fmt.Println(s) //构建一个str的副本
}(str)
}
/*
你好3
你好1
你好2
*/
由于多个goroutine可以在同一个地址空间上运行,所以我们要考虑资源竞争(同步问题)。