goroutine: goroutine 是一个更高级别的抽象,称为协程。协程是非抢占式的。 Go语言的运行时会观察gorountine的运行时行为,并在它们阻塞时自动挂起它们,然后在他们不被阻塞时恢复它们。在某种程度上,这使它们成为可抢占的(只是 在gorountine 被阻塞的情况) Go语言遵循一个称为fork-join的并发模型
func main() {
sayHello := func(){
fmt.Println("hello")
}
go sayHello()
//继续执行自己的逻辑
}
//上个例子, sayHello函数将在gorountine 上运行,而程序的其余部分将继续执行。在本例中没有join点。 //也许你会说 我在 go sayHello() 之后加上 time.Sleep 。但这种方式没有加入join点,只是增加了一个竞争条件。这只是增加了goroutine在程序退出前执行的概率,但并不能保证一定能执行。
join 点:
为了创建一个join点,可以通过多种方式实现; 其一: 使用sync 包中 sync.Waitgroup
func main() {
var wg sync.WaitGroup
sayHello := func() {
defer wg.Done()
fmt.Println("hello")
}
wg.Add(1)
go sayHello()
wg.Wait() // 一 :join点
}
gorountine 闭包:
如果gorountine 内部引用 外部变量,则这是原值的引用
func main(){
var wg sync.WaitGroup
for _, salutation := range []string{"hello", "greetings", "good day"}{
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(salutation)
}()
}
wg.Wait()
//fmt.Println(salutation) //这行会报错,正常情况下 for range 结束后 salutation 就不能访问了,超出作用域了
}
//good day
//good day
//good day
程序说明:
循环有很高的概率 在 gorountine 执行前 退出 正常情况下 for range 结束后 salutation 就不能访问了,超出作用域了; gorountine怎么能访问salutation呢? 对变量salutation值的引用仍然保留,由内存转移到堆,以便gorountine可以继续访问它。
如果想看到期望的打印结果,应该把salutation的副本传递到闭包中
func main(){
var wg sync.WaitGroup
for _, salutation := range []string{"hello", "greetings", "good day"}{
wg.Add(1)
go func(salutation string) {
defer wg.Done()
fmt.Println(salutation)
}(salutation)
}
wg.Wait()
}
//good day
//hello
//greetings
goroutine占用内存 /goroutine非常轻:
一个新创建的gorountine 只需要几千字节。当它不运行时,Go语言运行时就会自动增长(缩小)堆栈的内存
func main(){
memConsumed := func() uint64{
runtime.GC()
var s runtime.MemStats
runtime.ReadMemStats(&s)
return s.Sys
}
var c <-chan interface{}
var wg sync.WaitGroup
noop := func() {
wg.Done();
<-c //防止 goroutine 退出
}
const numGoroutines = 1e4
wg.Add(numGoroutines)
before := memConsumed()
for i := numGoroutines; i>0; i--{
go noop()
}
wg.Wait()
after := memConsumed()
fmt.Printf("%.3fkb", float64(after - before)/numGoroutines/1000) //0.177kb
}
//大致的数据: 8G内存 可以启动 2百多万 goroutine
goroutine切换上下文需要的时间:/0.268μs
func BenchmarkContextSwitch(b *testing.B){
var wg sync.WaitGroup
begin := make(chan struct{})
c := make(chan struct{})
var token struct{}
sender := func() {
defer wg.Done()
<-begin //在这里等待,直到被告知开始执行
for i:=0; i < b.N; i++{
c <- token //一个struct{} 被称为一个空结构,它没有内存占用。
}
}
receiver := func() {
defer wg.Done()
<- begin ////在这里等待,直到被告知开始执行
for i := 0; i< b.N; i++{
<- c
}
}
wg.Add(2)
go sender()
go receiver()
b.StartTimer() //开始计时
close(begin) //发送开始信号
wg.Wait() //告诉两个goroutine 开始运行
}
//268 ns/op 在操作系统级别,使用线程可能非常昂贵。大致数据 1.467μs ; gorountine切换只需要 0.268μs;