1、创建两个goroutine,以并发的形式分别显示大写和小写字母
// 该程序展示如何创建 goroutine ,以及调度器的行为
package main
import (
"fmt"
"runtime"
"sync"
)
func main() {
// 分配一个逻辑处理器给调度器使用
runtime.GOMAXPROCS(1)
// wg 用来等待程序完成
// 计数加2,表示要等待两个 goroutine
var wg sync.WaitGroup
wg.Add(2)
fmt.Println("Start Groutine")
// 声明一个匿名函数,并创建一个 goroutine
go 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
go 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 Groutine
Waiting To Finish
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 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 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
Terminating Program
*/
2、goroutine 并发求素数
// 求素数
// 该程序展示 goroutine 调度器是如何在单个线程上切分时间片的
package main
import (
"fmt"
"runtime"
"sync"
)
// wg 用来等待程序完成
var wg sync.WaitGroup
func main() {
// 分配一个逻辑处理器给调度器使用
runtime.GOMAXPROCS(1)
// 计数加2,表示要等待两个 goroutine
wg.Add(2)
// 创建两个 goroutine
fmt.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 main
import (
"fmt"
"runtime"
"sync"
"time"
)
func main() {
// 分配 2 个逻辑处理器给调度器使用
runtime.GOMAXPROCS(2)
// wg 用来等待程序完成
// 计数加2,表示要等待两个 goroutine
var wg sync.WaitGroup
wg.Add(2)
fmt.Println("Start Groutine")
// 声明一个匿名函数,并创建一个 goroutine
go 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)
}
}
}()
// 声明一个匿名函数,并创建一个 goroutine
go 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 Groutine
Waiting To Finish
a 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 L
M 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 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
Terminating Program
*/
4、协程竞争状态
// 该程序展示竞争状态(实际中不希望出现这种状态)
package main
import (
"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 的值, 并将值保存回 counter
value++
counter = value
}
}
// Final Counter: 2
go build -race // 用竞争检测器标志来编译程序
./main.exe // 运行程序
PS D:\go-project\src\go_code\channel\channel09\main> go build -race
PS D:\go-project\src\go_code\channel\channel09\main> .\main.exe
==================
WARNING: DATA RACE
Read at 0x000000a29390 by goroutine 8:
main.incCounter()
D:/go-project/src/go_code/channel/channel09/main/main.go:34 +0x8d
main.main·dwrap·2()
D:/go-project/src/go_code/channel/channel09/main/main.go:21 +0x39
Previous write at 0x000000a29390 by goroutine 7:
main.incCounter()
D:/go-project/src/go_code/channel/channel09/main/main.go:41 +0xaa
main.main·dwrap·1()
D:/go-project/src/go_code/channel/channel09/main/main.go:20 +0x39
Goroutine 8 (running) created at:
main.main()
D:/go-project/src/go_code/channel/channel09/main/main.go:21 +0xbc
Goroutine 7 (finished) created at:
main.main()
D:/go-project/src/go_code/channel/channel09/main/main.go:20 +0x7a
==================
Final Counter: 4
Found 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 main
import (
"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 的值, 并将值保存回 counter
value++
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 main
import (
"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 <- 0
wg.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 := <-counter
if !ok {
fmt.Println("err")
return
}
// 当前 goroutine 从线程退出,并放回到队列
runtime.Gosched()
// 增加本地 value 的值, 写入 通道 counter
value++
counter <- value
}
}
// Final Counter:= 4
// Final Counter: 0xc00001a0e0
6、无缓冲的通道模拟网球比赛
// 无缓冲的通道模拟网球比赛
package main
import (
"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 := <-court
if !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
}
// 显示击球数,并将击球数 +1
fmt.Printf("Player %s Hit %d \n", name, ball)
ball++
// 将球击向对手
court <- ball
}
}
/*
Player Dav Hit 1
Player Nadal Hit 2
Player Dav Missed❗
Player Nadal Won
*/
7、无缓冲的通道模拟4个 goroutine 间的接力比赛
// 无缓冲的通道模拟4个 goroutine 间的接力比赛
package main
import (
"fmt"
"sync"
"time"
)
var (
// wg 用来等待程序结束
wg sync.WaitGroup
)
func main() {
// 创建一个无缓冲的通道
baton := make(chan int)
// 为最后一位跑步者将计数 +1
wg.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 + 1
fmt.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 Baton
Runner 2 To The Line🏃♂️
Runner 1 Exchange Width Runner 2👉
Runner 2 Running Width Baton
Runner 3 To The Line🏃♂️
Runner 2 Exchange Width Runner 3👉
Runner 3 Running Width Baton
Runner 4 To The Line🏃♂️
Runner 3 Exchange Width Runner 4👉
Runner 4 Running Width Baton
Runner 4 Finished, Race Over🎈
*/
8、固定数目的有缓冲的通道
// 有缓冲的通道 固定数目
package main
import (
"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 := <-tasks
if !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 : 1
Worker: 1 : Started Task : 2
Worker: 0 : Started Task : 3
Worker: 2 : Started Task : 4
Worker: 3 : Completed Task : 1
Worker: 3 : Started Task : 5
Worker: 0 : Completed Task : 3
Worker: 0 : Started Task : 6
Worker: 1 : Completed Task : 2
Worker: 1 : Started Task : 7
Worker: 2 : Completed Task : 4
Worker: 2 : Started Task : 8
Worker: 2 : Completed Task : 8
Worker: 2 : Started Task : 9
Worker: 1 : Completed Task : 7
Worker: 1 : Started Task : 10
Worker: 2 : Completed Task : 9
Worker: 2 : Shutting Down✔
Worker: 1 : Completed Task : 10
Worker: 1 : Shutting Down✔
Worker: 3 : Completed Task : 5
Worker: 3 : Shutting Down✔
Worker: 0 : Completed Task : 6
Worker: 0 : Shutting Down✔
*/