Go笔记(十五):并发编程

一、协程的创建

  Go 语言支持并发,只需要通过 go 关键字来开启 goroutine(协程) 即可。

  goroutine(协程) 是轻量级线程,goroutine(协程) 的调度是由 Golang 运行时进行管理的。

goroutine 语法格式(创建协程):

go 函数名( 参数列表 )

示例代码如下:

 1 package main
 2 import (
 3     "fmt"
 4     "time"
 5 )
 6 func rest(msg string) {
 7     for i := 0; i < 4; i++ {
 8         fmt.Println(msg)
 9         time.Sleep(100 * time.Millisecond)
10     }
11 }
12 func main() {
13     // 开启一个goroutine 协程运行
14     go rest(" go rest ")
15     // main主线程运行
16     rest(" main rest ")
17 }

执行结果如下:

  

二、协程间的同步

2.1、WaitGroup

  WaitGroup,和Java中的CountDownLatch实现原理相似,WaitGroup持有当前正在执行的协程数量,当某个协程执行完后,WaitGroup持有正在执行的协程数量减1,当WaitGroup中协程正执行的数量为0,wait()方法便不再阻塞。

示例代码如下:

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "sync"
 6 )
 7 
 8 var wg sync.WaitGroup
 9 
10 func routine01(num int) {
11     // goroutine结束登记-1
12     defer wg.Done()
13     fmt.Printf("goroutine runing, %v\n", num)
14 }
15 
16 func main() {
17     for i := 0; i < 8; i++ {
18         // 启动一个goroutine就加1
19         wg.Add(1)
20         go routine01(i)
21     }
22     // 阻塞,等待所有登记的goroutine结束
23     wg.Wait()
24 }

执行结果如下: 

  

2.2、Mutex互斥同步

 1 package main
 2 import (
 3    "fmt"
 4    "sync"
 5    "time"
 6 )
 7 var m = 100
 8 var n = 100
 9 var lock sync.Mutex
10 var wagp sync.WaitGroup
11 // 增量函数
12 func add() {
13    defer wagp.Done()
14    n += 1
15    time.Sleep(time.Millisecond * 8)
16 }
17 // 减量函数
18 func sub() {
19    time.Sleep(time.Millisecond * 3)
20    defer wagp.Done()
21    n -= 1
22 }
23 // 未用mutex同步锁
24 func nomutex() {
25    for i := 0; i < 100; i++ {
26       go add()
27       wagp.Add(1)
28       go sub()
29       wagp.Add(1)
30    }
31 
32    wagp.Wait()
33 }
34 // 用mutex同步锁
35 func addMetux() {
36    defer wagp.Done()
37    lock.Lock()
38    m += 1
39    time.Sleep(time.Millisecond * 10)
40    lock.Unlock()
41 }
42 // 用mutex同步锁
43 func subMetux() {
44    defer wagp.Done()
45    lock.Lock()
46    time.Sleep(time.Millisecond * 3)
47    m -= 1
48    lock.Unlock()
49 }
50 // 使用mutex同步锁
51 func mutex01() {
52    for i := 0; i < 100; i++ {
53       go addMetux()
54       wagp.Add(1)
55       go subMetux()
56       wagp.Add(1)
57    }
58 
59    wagp.Wait()
60 }
61 func main() {
62    nomutex()
63    mutex01()
64    fmt.Printf("end n: %v\n", n)
65    fmt.Printf("end m: %v\n", m)
66 }

执行结果如下:

 

  mutex,和Java中的Lock实现原理相似。当执行lock()方法时,基于 CAS 操作,将Mutex的 state 设置为Metux为加锁状态,同时设置当前线程持有锁资源;执行Unlock()方法时,对state的值做调整,释放锁资源,唤醒等待线程。 

  

  

三、协程管理相关包

  协程管理在api-runtime的相关包下。

1、runtime.Gosched()

  让出CPU时间片,重新等待安排任务。

 1 package main
 2 
 3 import (
 4    "fmt"
 5    "runtime"
 6 )
 7 
 8 func show(msg string) {
 9    for i := 0; i < 3; i++ {
10       fmt.Printf("msg: %v\n", msg)
11    }
12 }
13 
14 func main() {
15    go show("php")
16    // 主协程
17    for i := 0; i < 2; i++ {
18       // 让出cpu给子协程
19       runtime.Gosched() // 若此处注释,则子协程可能无法执行
20       fmt.Printf("%v\n", "golang")
21    }
22 }

  执行结果如下:

  

2、runtime.Goexit()

  退出子协程。

示例如下:

 1 package main
 2 
 3 import (
 4    "fmt"
 5    "runtime"
 6 )
 7 
 8 func show01(msg string) {
 9    for i := 0; i < 8; i++ {
10       if i == 3 {
11          // 退出子协程
12          runtime.Goexit()
13       }
14       fmt.Printf("msg: %v\n", msg)
15    }
16 }
17 
18 func main() {
19    go show01("C")
20    for i := 0; i < 1; i++ {
21       runtime.Gosched()
22       fmt.Printf("%v\n", "golang")
23    }
24 }

执行结果如下:

  

四、select

  select是Go中并发编程的控制语句,用于处理异步IO操作。select会监听case语句中channel的读写操作,当channel为非阻塞状态(可读写),会触发select中的case。

1、select的语法结构

select {
    case 读:
        // do something
    case 写:
        // do something
    default:
         // do something
}

  若多个case都可运行,select随机选择一个执行,其他不会执行。没有可运行的case语句,有default语句,会执行default。

2、示例

 1 package main
 2 
 3 import "fmt"
 4 
 5 // 创建通道
 6 var chint = make(chan int, 5)
 7 var chstr = make(chan string, 5)
 8 
 9 func main() {
10    go func() {
11       // 关闭通道
12       defer close(chint)
13       defer close(chstr)
14       // 往通道里写数据
15       chint <- 10
16       chstr <- "chstr"
17    }()
18 
19    for i := 0; i < 10; i++ {
20       // 监听case语句中channel的读写操作,当channel为非阻塞状态(可读写),会触发select中的case
21       select {
22          case i := <-chint:
23             fmt.Printf("i: %v\n", i)
24          case s := <-chstr:
25             fmt.Printf("s: %v\n", s)
26          default:
27             fmt.Printf("default...\n")
28          }
29    }
30 }

  执行结果如下:

  

3、select注意事项

  没有可运行的case语句,没有default语句,select会阻塞直到某个case通信可运行;

  select中的case语句必须是一个channel操作;

  select的default总是可运行的。

五、定时器Timer

  定时器Timer只执行一个。

1、timer的创建

timer := time.NewTimer(time.Second)
timer.C // 阻塞,直到指定时间到了

示例如下:

 1 package main
 2 import (
 3    "fmt"
 4    "sync"
 5    "time"
 6 )
 7 var wait sync.WaitGroup
 8 // 使用定时器
 9 func time01() {
10    defer wait.Done()
11    fmt.Printf("before: %v\n", time.Now())
12    timer := time.NewTimer(time.Second * 2)
13    <-timer.C // 阻塞,直到指定时间到达
14    fmt.Printf("after: %v\n", time.Now())
15 }
16 func main() {
17    wait.Add(1)
18    go time01()
19    // 同步等待协程执行完成
20    wait.Wait()
21 }

执行结果如下:

  

2、time.After

  time.After,和Java中的Sleep功能相似,阻塞指定时间再运行。

示例如下:

 1 package main
 2 import (
 3    "fmt"
 4    "sync"
 5    "time"
 6 )
 7 var wait sync.WaitGroup
 8 
 9 // 使用定时器
10 func time02() {
11    defer wait.Done()
12    fmt.Printf("before: %v\n", time.Now())
13    // 阻塞2s
14    <-time.After(time.Second * 2)
15    fmt.Printf("after: %v\n", time.Now())
16 }
17 
18 func main() {
19    wait.Add(1)
20    go time02()
21    // 同步等待协程执行完成
22    wait.Wait()
23 }

执行结果如下:

  

3、timer.stop

  停止定时器事件。

示例如下:

 1 package main
 2 import (
 3    "fmt"
 4    "time"
 5 )
 6 
 7 func main() {
 8    // 创建定时器
 9    timer := time.NewTimer(time.Second * 2)
10    // 协程,匿名函数
11    go func() {
12       <-timer.C
13       fmt.Println("timer func...")
14    }()
15 
16    // 停止定时器,阻止timer事件发生
17    stopFlag := timer.Stop()
18    if stopFlag {
19       fmt.Println("timer stoped...")
20    }
21 }

执行结果如下:

  

4、timer.Reset

  重置定时器。

示例如下:

 1 package main
 2 import (
 3    "fmt"
 4    "time"
 5 )
 6 
 7 func main() {
 8    // 重置定时器 reset
 9    fmt.Printf("before: %v\n", time.Now())
10    timer := time.NewTimer(time.Second * 2)
11    timer.Reset(time.Second * 1)
12    <-timer.C
13    fmt.Printf("after: %v\n", time.Now())
14 }

执行结果如下:

  

六、周期执行器Ticker

  与timer只执行一次相比,Ticker可周期执行。

示例如下:

 1 package main
 2 import (
 3    "fmt"
 4    "time"
 5 )
 6 func main() {
 7    // 创建周期执行器
 8    ticker := time.NewTicker(time.Second * 2)
 9 
10    // 周期执行
11    for _ = range ticker.C {
12       fmt.Printf("time: %v\n", time.Now())
13    }
14 }

执行结果如下:

  

 

 1 package main
 2 import (
 3    "fmt"
 4    "time"
 5 )
 6 
 7 func ticker() {
 8    // 创建一个无缓冲的整型channel
 9    chint := make(chan int)
10    // 关闭通道
11    defer close(chint)
12    // 创建定时器
13    ticker := time.NewTicker(time.Second * 2)
14    // 创建一个协程,利用周期定时器每2s向通道中写入数据
15    go func() {
16       for _ = range ticker.C {
17          select {
18             case chint <- 2:
19             case chint <- 4:
20             case chint <- 6:
21          }
22       }
23    }()
24    sum := 0
25    // 遍历通道,若通道无数据,会阻塞
26    for v := range chint {
27       fmt.Printf("receive: %v\n", v)
28       sum += v
29       if sum > 20 {
30          fmt.Printf("sum: %v\n", sum)
31          break
32       }
33    }
34 }
35 
36 func main() {
37    ticker()
38 }

执行结果如下:

  

七、atomic

  atomic的原子操作可以保证任一时刻只有一个goroutine协程对变量做修改。

1、加减

 1 package main
 2 import (
 3    "fmt"
 4    "sync"
 5    "sync/atomic"
 6 )
 7 var i int32 = 100
 8 var waitg sync.WaitGroup
 9 // 原子加操作
10 func atomicAdd() {
11    atomic.AddInt32(&i, 1)
12    waitg.Done()
13 }
14 // 原子减操作
15 func atomicSub() {
16    atomic.AddInt32(&i, -1)
17    waitg.Done()
18 }
19 func main() {
20    for i := 0; i < 100; i++ {
21       // 子线程
22       waitg.Add(1)
23       go atomicAdd()
24       // 子线程
25       waitg.Add(1)
26       go atomicSub()
27    }
28    waitg.Wait()
29    fmt.Printf("i = %v\n", i)
30 }

  执行结果如下:

  

2、载入 -> 读取操作、存储 -> 写入操作

 1 package main
 2 import (
 3    "fmt"
 4    "sync/atomic"
 5 )
 6 // 载入与存储
 7 func loadAndStore() {
 8    var i int64 = 64
 9    // 载入 -> 读取,原子操作
10    atomic.LoadInt64(&i)
11    fmt.Printf("i: %v\n", i)
12 
13    // 存储 -> 写入,原子操作
14    atomic.StoreInt64(&i, 128)
15    fmt.Printf("i: %v\n", i)
16 }
17 func main() {
18    loadAndStore()
19 }

  执行结果如下:

  

3、CAS -> 比较并交换

  进行交换前变量的值未被修改,与参数old记录的值一致,满足此前提下才会进行交换。

 1 package main
 2 import (
 3    "fmt"
 4    "sync/atomic"
 5 )
 6 // 比较和交换
 7 func cas() {
 8    var i int64 = 256
 9    // 旧的值与变量i的值相同,则交换
10    result := atomic.CompareAndSwapInt64(&i, 256, 64)
11    if result {
12       fmt.Println("cas success")
13    } else {
14       fmt.Println("cas fail")
15    }
16    fmt.Printf("i: %v\n", i)
17 
18    // 旧的值与变量i的值不同,则不交换
19    result01 := atomic.CompareAndSwapInt64(&i, 256, 8)
20    if result01 {
21       fmt.Println("cas success")
22    } else {
23       fmt.Println("cas fail")
24    }
25    fmt.Printf("i: %v\n", i)
26 }
27 func main() {
28    cas()
29 }

  执行结果如下:

  

 

posted @ 2023-05-06 08:42  无虑的小猪  阅读(15)  评论(0编辑  收藏  举报