package main import ( "fmt" "time" ) func main() { num := 0 // 创建100000个协程,每个协程对num进行一次自增运算 for i := 0; i < 100000; i++ { go func(i int) { num++ // num自增1 fmt.Printf("协程%d将num自增1,此时num的值为%d \n", i, num) }(i) } time.Sleep(time.Second * 15) // 由于协程比较多,所以休眠15秒让所有协程都能执行 } // ========== 输出过程·开始 ========== // // …… // 协程99990将num自增1,此时num的值为99009 // 协程99992将num自增1,此时num的值为99010 <<<<< // 协程99991将num自增1,此时num的值为99010 <<<<< // 协程99993将num自增1,此时num的值为99011 // 协程99995将num自增1,此时num的值为99013 // 协程99994将num自增1,此时num的值为99012 // 协程99996将num自增1,此时num的值为99014 // 协程99998将num自增1,此时num的值为99015 // 协程99999将num自增1,此时num的值为99017 <<<<< // 协程99997将num自增1,此时num的值为99017 <<<<< // ========== 输出过程·结束 ========== // // ========== 总结 ========== // // 1、想知道代码有没有数据竞争问题可以在执行“go run”时加入“-race”参数,即:go run -race main.go, // 如果有数据竞争问题会出现“exit status 66”。
package main import ( "fmt" "sync" "time" ) func main() { num := 0 lock := sync.Mutex{} // 创建100000个协程,每个协程对num进行一次自增运算 for i := 0; i < 100000; i++ { go func(i int) { lock.Lock() // 加锁 num++ // num自增1 fmt.Printf("协程%d将num自增1,此时num的值为%d \n", i, num) lock.Unlock() // 解锁 }(i) } time.Sleep(time.Second * 15) // 由于协程比较多,所以休眠15秒让所有协程都能执行 } // ========== 输出过程·开始 ========== // // …… // 协程99990将num自增1,此时num的值为99991 // 协程99991将num自增1,此时num的值为99992 // 协程99992将num自增1,此时num的值为99993 // 协程99994将num自增1,此时num的值为99994 // 协程99993将num自增1,此时num的值为99995 // 协程99995将num自增1,此时num的值为99996 // 协程99996将num自增1,此时num的值为99997 // 协程99998将num自增1,此时num的值为99998 // 协程5967将num自增1,此时num的值为99999 // 协程99999将num自增1,此时num的值为100000 // ========== 输出过程·结束 ========== //
package main import ( "fmt" "time" ) func main() { num := 0 channel := make(chan struct{}, 0) // 创建一个无缓冲管道(容量为0的管道)用于协程之间通信 // 创建100000个协程,每个协程对num进行一次自增运算 for i := 0; i < 100000; i++ { go func(i int) { <-channel // 从管道读取数据(重要提醒:这里会发生阻塞,直到读取到数据才会解除阻塞) num++ // num自增1 fmt.Printf("协程%d将num自增1,此时num的值为%d \n", i, num) channel <- struct{}{} // 往管道写入空结构体,解除下一个协程的阻塞 }(i) } channel <- struct{}{} // 往管道写入空结构体,解除第一个协程的阻塞 time.Sleep(time.Second * 15) // 由于协程比较多,所以休眠15秒让所有协程都能执行 close(channel) // 关闭管道 } // ========== 输出过程·开始 ========== // // …… // 协程99976将num自增1,此时num的值为99991 // 协程99974将num自增1,此时num的值为99992 // 协程99985将num自增1,此时num的值为99993 // 协程99998将num自增1,此时num的值为99994 // 协程99913将num自增1,此时num的值为99995 // 协程99999将num自增1,此时num的值为99996 // 协程99993将num自增1,此时num的值为99997 // 协程99992将num自增1,此时num的值为99998 // 协程99837将num自增1,此时num的值为99999 // 协程99607将num自增1,此时num的值为100000 // ========== 输出过程·结束 ========== // // ========== 总结 ========== // // 1、在创建100000个协程后,由于有“<-channel”所以这些协程都会发生阻塞,直到“channel <- struct{}{}”解除第一个协程的阻塞, // 然后产生连锁使得协程阻塞逐个解除并执行num自增操作,从而解决数据竞争问题。 // 2、这里值得一提的是管道是无缓冲的(容量为0),且数据类型是空结构体,这里这么定义管道是基于以下两点: // (1) 协程之间只是需要通过管道传递信号,并不关心管道的数据类型和值,所以不占用内存空间的空结构体是最佳选择。 // (2) 无缓冲管道(容量为0)不需要使用内存来存储数据,能让协程之间的通信变得更加高效和低延迟。
Copyright © 2024 码农人生. All Rights Reserved