1、创建两个goroutine,以并发的形式分别显示大写和小写字母
// 该程序展示如何创建 goroutine ,以及调度器的行为package mainimport ("fmt""runtime""sync")func main() {// 分配一个逻辑处理器给调度器使用runtime.GOMAXPROCS(1)// wg 用来等待程序完成// 计数加2,表示要等待两个 goroutinevar wg sync.WaitGroupwg.Add(2)fmt.Println("Start Groutine")// 声明一个匿名函数,并创建一个 goroutinego func() {// 在函数退出时调用Done来通知main函数工作已经完成defer wg.Done()// 显示字母表3次for i := 0; i < 3; i++ {for char := 'a'; char < 'a'+26; char++ {fmt.Printf("%c ", char)}fmt.Println()}}()// 声明一个匿名函数,并创建一个 goroutinego func() {// 在函数退出时调用Done来通知main函数工作已经完成defer wg.Done()// 显示字母表3次for i := 0; i < 3; i++ {for char := 'A'; char < 'A'+26; char++ {fmt.Printf("%c ", char)}fmt.Println()}}()// 等待 goroutine 结束fmt.Println("Waiting To Finish")wg.Wait()fmt.Println("Terminating Program")}/*Start GroutineWaiting To FinishA B C D E F G H I J K L M N O P Q R S T U V W X Y ZA B C D E F G H I J K L M N O P Q R S T U V W X Y ZA B C D E F G H I J K L M N O P Q R S T U V W X Y Za b c d e f g h i j k l m n o p q r s t u v w x y za b c d e f g h i j k l m n o p q r s t u v w x y za b c d e f g h i j k l m n o p q r s t u v w x y zTerminating Program*/
2、goroutine 并发求素数
// 求素数// 该程序展示 goroutine 调度器是如何在单个线程上切分时间片的package mainimport ("fmt""runtime""sync")// wg 用来等待程序完成var wg sync.WaitGroupfunc main() {// 分配一个逻辑处理器给调度器使用runtime.GOMAXPROCS(1)// 计数加2,表示要等待两个 goroutinewg.Add(2)// 创建两个 goroutinefmt.Println("Create Goroutine")go printPrime("A")go printPrime("B")// 等待 goroutine 结束fmt.Println("Waiting To Finish")wg.Wait()fmt.Println("Terminating Program")}// printPrime 显示5000以内的素数func printPrime(prefix string) {// 在函数退出时调用Done来通知main函数工作已经完成defer wg.Done()next:for outer := 2; outer < 5000; outer++ {for inner := 2; inner < outer; inner++ {if outer%inner == 0 {continue next}}fmt.Printf("%s: %d \n", prefix, outer)}fmt.Println("Completed", prefix)}
3、协程并行 + 并发(设置两个逻辑处理器给调度器使用)
// 并行 + 并发// 该程序展示如何创建 goroutine ,以及调度器的行为package mainimport ("fmt""runtime""sync""time")func main() {// 分配 2 个逻辑处理器给调度器使用runtime.GOMAXPROCS(2)// wg 用来等待程序完成// 计数加2,表示要等待两个 goroutinevar wg sync.WaitGroupwg.Add(2)fmt.Println("Start Groutine")// 声明一个匿名函数,并创建一个 goroutinego func() {// 在函数退出时调用Done来通知main函数工作已经完成defer wg.Done()// 显示字母表3次for i := 0; i < 3; i++ {for char := 'a'; char < 'a'+26; char++ {fmt.Printf("%c ", char)time.Sleep(time.Millisecond * 10)}}}()// 声明一个匿名函数,并创建一个 goroutinego func() {// 在函数退出时调用Done来通知main函数工作已经完成defer wg.Done()// 显示字母表3次for i := 0; i < 3; i++ {for char := 'A'; char < 'A'+26; char++ {fmt.Printf("%c ", char)}}}()// 等待 goroutine 结束fmt.Println("Waiting To Finish")wg.Wait()fmt.Println("\nTerminating Program")}// 两个协程,时分复用,时间切片,交替执行/*Start GroutineWaiting To Finisha A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K LM N O P Q R S T U V W X Y Z b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y za b c d e f g h i j k l m n o p q r s t u v w x y zTerminating Program*/
4、协程竞争状态
// 该程序展示竞争状态(实际中不希望出现这种状态)package mainimport ("fmt""runtime""sync")var (// counter 是所有 goroutine 都要增加其值的变量counter int// wg 用来等待程序结束wg sync.WaitGroup)func main() {wg.Add(2)go incCounter(1)go incCounter(2)wg.Wait()fmt.Println("Final Counter:", counter)}// incCounter 增加报里 counter 变量的值func incCounter(id int) {defer wg.Done()for i := 0; i < 2; i++ {// 捕获 counter 的值value := counter// 当前 goroutine 从线程退出,并放回到队列runtime.Gosched()// 增加本地 value 的值, 并将值保存回 countervalue++counter = value}}// Final Counter: 2
go build -race // 用竞争检测器标志来编译程序./main.exe // 运行程序PS D:\go-project\src\go_code\channel\channel09\main> go build -racePS D:\go-project\src\go_code\channel\channel09\main> .\main.exe==================WARNING: DATA RACERead at 0x000000a29390 by goroutine 8:main.incCounter()D:/go-project/src/go_code/channel/channel09/main/main.go:34 +0x8dmain.main·dwrap·2()D:/go-project/src/go_code/channel/channel09/main/main.go:21 +0x39Previous write at 0x000000a29390 by goroutine 7:main.incCounter()D:/go-project/src/go_code/channel/channel09/main/main.go:41 +0xaamain.main·dwrap·1()D:/go-project/src/go_code/channel/channel09/main/main.go:20 +0x39Goroutine 8 (running) created at:main.main()D:/go-project/src/go_code/channel/channel09/main/main.go:21 +0xbcGoroutine 7 (finished) created at:main.main()D:/go-project/src/go_code/channel/channel09/main/main.go:20 +0x7a==================Final Counter: 4Found 1 data race(s)PS D:\go-project\src\go_code\channel\channel09\main>
5、消除竞争状态方法
5.1 锁住共享资源
对共享资源加锁,atomic 和 sync 包里的函数提供了很好的解决方案
- 原子函数能够以很底层的加锁机制来同步访问整形变量和指针。 ```go // atomic 包 原子函数加锁 package main
import ( “fmt” “runtime” “sync” “sync/atomic” )
var ( // counter 是所有 goroutine 都要增加其值的变量 counter int64 // wg 用来等待程序结束 wg sync.WaitGroup )
func main() { wg.Add(2)
go incCounter(1)go incCounter(2)wg.Wait()fmt.Println("Final Counter:", counter)
}
// incCounter 增加报里 counter 变量的值 func incCounter(id int) { defer wg.Done()
for i := 0; i < 2; i++ {// 安全的对 counter + 1// AddInt64() 同步整型值的加法atomic.AddInt64(&counter, 1)// 当前 goroutine 从线程退出,并放回到队列runtime.Gosched()}
}
// Final Counter: 4
2. 互斥锁(mutual exclusion, mutex)互斥锁用于在代码上创建一个临界区,保证同一时间只有一个 goroutine 可以执行临界区的代码。```go// 互斥锁定义同步访问资源的代码临界区package mainimport ("fmt""runtime""sync")var (// counter 是所有 goroutine 都要增加其值的变量counter int// wg 用来等待程序结束wg sync.WaitGroup// mutex 用来定义一段代码临界区, (加锁、解锁)mutex sync.Mutex)func main() {wg.Add(2)go incCounter(1)go incCounter(2)wg.Wait()fmt.Println("Final Counter:", counter)}// incCounter 增加报里 counter 变量的值func incCounter(id int) {defer wg.Done()for i := 0; i < 2; i++ {// 同一时刻,只能允许一个 goroutine 进入这个临界区mutex.Lock(){// 捕获 counter 的值value := counter// 当前 goroutine 从线程退出,并放回到队列runtime.Gosched()// 增加本地 value 的值, 并将值保存回 countervalue++counter = value}// 释放锁,允许其它正在等待的 goroutine 进入临界区mutex.Unlock()}}// Final Counter: 4
5.2 通道 channel
在 Go 语言里,你不仅可以使用原子函数和互斥锁来保证对共享资源的安全访问以及消除竞争状态,还可以使用通道,通过发送和接收需要共享的资源,在 goroutine 之间做同步
// 无缓冲的整形通道unbuffered := make(chan int)// 有缓冲的字符串通道buffered := make(chan string, 10)
// goroutine + channel 用共享的方式,消除竞争状态package mainimport ("fmt""runtime""sync")var (// counter 是所有 goroutine 都要增加其值的变量counter chan int = make(chan int, 1) // 缓冲1个数据的通道,goroutine 更新其值// wg 用来等待程序结束wg sync.WaitGroup)func main() {wg.Add(2)go incCounter(1)go incCounter(2)counter <- 0wg.Wait()close(counter)for v := range counter {fmt.Println("Final Counter:=", v)}fmt.Println("Final Counter:", counter)}// incCounter 增加报里 counter 变量的值func incCounter(id int) {defer wg.Done()for i := 0; i < 2; i++ {// 从通道取值 counter 的值value, ok := <-counterif !ok {fmt.Println("err")return}// 当前 goroutine 从线程退出,并放回到队列runtime.Gosched()// 增加本地 value 的值, 写入 通道 countervalue++counter <- value}}// Final Counter:= 4// Final Counter: 0xc00001a0e0
6、无缓冲的通道模拟网球比赛
// 无缓冲的通道模拟网球比赛package mainimport ("fmt""math/rand""sync""time")var (// wg 用来等待程序结束wg sync.WaitGroup)func init() {rand.Seed(time.Now().UnixNano())}func main() {// 创建一个无缓冲的通道court := make(chan int)wg.Add(2)// 启动两个选手go player("Nadal", court)go player("Dav", court)// 发球court <- 1// 等待游戏结束wg.Wait()}func player(name string, court chan int) {defer wg.Done()for {// 等待球被击打过来ball, ok := <-courtif !ok {// 如果通道被关闭,我们就赢了fmt.Printf("Player %s Won \n", name)return}// 选随机数,然后用这个数来判断我们是否丢球n := rand.Intn(100)if n%13 == 0 {fmt.Printf("Player %s Missed❗ \n", name)// 关闭通道,表示输了close(court)return}// 显示击球数,并将击球数 +1fmt.Printf("Player %s Hit %d \n", name, ball)ball++// 将球击向对手court <- ball}}/*Player Dav Hit 1Player Nadal Hit 2Player Dav Missed❗Player Nadal Won*/
7、无缓冲的通道模拟4个 goroutine 间的接力比赛
// 无缓冲的通道模拟4个 goroutine 间的接力比赛package mainimport ("fmt""sync""time")var (// wg 用来等待程序结束wg sync.WaitGroup)func main() {// 创建一个无缓冲的通道baton := make(chan int)// 为最后一位跑步者将计数 +1wg.Add(1)// 第一位跑步者持有接力棒go Runner(baton)// 开始比赛baton <- 1// 等待游戏结束wg.Wait()}func Runner(baton chan int) {var newRunner int// 等待接力棒runner := <-baton// 开始跑fmt.Printf("Runner %d Running Width Baton \n", runner)// 创建下一位跑步者if runner != 4 {newRunner = runner + 1fmt.Printf("Runner %d To The Line🏃♂️ \n", newRunner)go Runner(baton)}// 围绕跑道跑time.Sleep(100 * time.Microsecond)// 比赛结束了吗?if runner == 4 {fmt.Printf("Runner %d Finished, Race Over🎈\n", runner)wg.Done()return}// 将接力棒交给下一位跑步者fmt.Printf("Runner %d Exchange Width Runner %d👉 \n", runner, newRunner)baton <- newRunner}/*Runner 1 Running Width BatonRunner 2 To The Line🏃♂️Runner 1 Exchange Width Runner 2👉Runner 2 Running Width BatonRunner 3 To The Line🏃♂️Runner 2 Exchange Width Runner 3👉Runner 3 Running Width BatonRunner 4 To The Line🏃♂️Runner 3 Exchange Width Runner 4👉Runner 4 Running Width BatonRunner 4 Finished, Race Over🎈*/
8、固定数目的有缓冲的通道
// 有缓冲的通道 固定数目package mainimport ("fmt""math/rand""sync""time")const (numberGoroutines = 4 // 要使用的 goroutine 的数量taskLoad = 10 // 要处理的工作的数量)var (// wg 用来等待程序结束wg sync.WaitGroup)func init() {rand.Seed(time.Now().UnixNano())}func main() {// 创建一个有缓冲的通道来管理工作tasks := make(chan string, taskLoad)// 启动 goroutine 来处理工作wg.Add(numberGoroutines)for gr := 0; gr < numberGoroutines; gr++ {go worker(tasks, gr)}// 增加一组要完成的工作for post := 1; post <= taskLoad; post++ {tasks <- fmt.Sprintf("Task : %d", post)}// 当所有工作都处理完时关闭通道// 以便所有的 goroutine 退出close(tasks)// 等待所有工作完成wg.Wait()}func worker(tasks chan string, worker int) {defer wg.Done()for {// 等待分配工作task, ok := <-tasksif !ok {// 意味着通道已经空了,并且已被关闭fmt.Printf("Worker: %d : Shutting Down✔ \n", worker)return}// 显示我们开始工作了fmt.Printf("Worker: %d : Started %s \n", worker, task)// 随机等一段时间来模拟工作sleep := rand.Int63n(100)time.Sleep(time.Duration(sleep) * time.Millisecond)// 显示我们完成了工作fmt.Printf("Worker: %d : Completed %s \n", worker, task)}}/*Worker: 3 : Started Task : 1Worker: 1 : Started Task : 2Worker: 0 : Started Task : 3Worker: 2 : Started Task : 4Worker: 3 : Completed Task : 1Worker: 3 : Started Task : 5Worker: 0 : Completed Task : 3Worker: 0 : Started Task : 6Worker: 1 : Completed Task : 2Worker: 1 : Started Task : 7Worker: 2 : Completed Task : 4Worker: 2 : Started Task : 8Worker: 2 : Completed Task : 8Worker: 2 : Started Task : 9Worker: 1 : Completed Task : 7Worker: 1 : Started Task : 10Worker: 2 : Completed Task : 9Worker: 2 : Shutting Down✔Worker: 1 : Completed Task : 10Worker: 1 : Shutting Down✔Worker: 3 : Completed Task : 5Worker: 3 : Shutting Down✔Worker: 0 : Completed Task : 6Worker: 0 : Shutting Down✔*/
