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

Posted 无虑的小猪

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go笔记(十五):并发编程相关的知识,希望对你有一定的参考价值。

一、协程的创建

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

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

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

go 函数名( 参数列表 )

示例代码如下:

 package main
 import (
     "fmt"
     "time"
 )
 func rest(msg string) 
     for i := 0; i < 4; i++ 
         fmt.Println(msg)
         time.Sleep(100 * time.Millisecond)
     
 
 func main() 
     // 开启一个goroutine 协程运行
     go rest(" go rest ")
     // main主线程运行
     rest(" main rest ")
 

执行结果如下:

  

二、协程间的同步

2.1、WaitGroup

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

示例代码如下:

 package main
 
 import (
     "fmt"
     "sync"
 )
 
 var wg sync.WaitGroup
 
 func routine01(num int) 
     // goroutine结束登记-1
     defer wg.Done()
     fmt.Printf("goroutine runing, %v\\n", num)
 
 
 func main() 
     for i := 0; i < 8; i++ 
         // 启动一个goroutine就加1
         wg.Add(1)
         go routine01(i)
     
     // 阻塞,等待所有登记的goroutine结束
     wg.Wait()
 

执行结果如下: 

  

2.2、Mutex互斥同步

 package main
 import (
    "fmt"
    "sync"
    "time"
 )
 var m = 100
 var n = 100
 var lock sync.Mutex
 var wagp sync.WaitGroup
 // 增量函数
 func add() 
    defer wagp.Done()
    n += 1
    time.Sleep(time.Millisecond * 8)
 
 // 减量函数
 func sub() 
    time.Sleep(time.Millisecond * 3)
    defer wagp.Done()
    n -= 1
 
 // 未用mutex同步锁
 func nomutex() 
    for i := 0; i < 100; i++ 
       go add()
       wagp.Add(1)
       go sub()
       wagp.Add(1)
    
 
    wagp.Wait()
 
 // 用mutex同步锁
 func addMetux() 
    defer wagp.Done()
    lock.Lock()
    m += 1
    time.Sleep(time.Millisecond * 10)
    lock.Unlock()
 
 // 用mutex同步锁
 func subMetux() 
    defer wagp.Done()
    lock.Lock()
    time.Sleep(time.Millisecond * 3)
    m -= 1
    lock.Unlock()
 
 // 使用mutex同步锁
 func mutex01() 
    for i := 0; i < 100; i++ 
       go addMetux()
       wagp.Add(1)
       go subMetux()
       wagp.Add(1)
    
 
    wagp.Wait()
 
 func main() 
    nomutex()
    mutex01()
    fmt.Printf("end n: %v\\n", n)
    fmt.Printf("end m: %v\\n", m)
 

执行结果如下:

 

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

  

  

三、协程管理相关包

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

1、runtime.Gosched()

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

 package main
 
 import (
    "fmt"
    "runtime"
 )
 
 func show(msg string) 
    for i := 0; i < 3; i++ 
       fmt.Printf("msg: %v\\n", msg)
    
 
 
 func main() 
    go show("php")
    // 主协程
    for i := 0; i < 2; i++ 
       // 让出cpu给子协程
       runtime.Gosched() // 若此处注释,则子协程可能无法执行
       fmt.Printf("%v\\n", "golang")
    
 

  执行结果如下:

  

2、runtime.Goexit()

  退出子协程。

示例如下:

 package main
 
 import (
    "fmt"
    "runtime"
 )
 
 func show01(msg string) 
    for i := 0; i < 8; i++ 
       if i == 3 
          // 退出子协程
          runtime.Goexit()
       
       fmt.Printf("msg: %v\\n", msg)
    
 
 
 func main() 
    go show01("C")
    for i := 0; i < 1; i++ 
       runtime.Gosched()
       fmt.Printf("%v\\n", "golang")
    
 

执行结果如下:

  

四、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、示例

 package main
 
 import "fmt"
 
 // 创建通道
 var chint = make(chan int, 5)
 var chstr = make(chan string, 5)
 
 func main() 
    go func() 
       // 关闭通道
       defer close(chint)
       defer close(chstr)
       // 往通道里写数据
       chint <- 10
       chstr <- "chstr"
    ()
 
    for i := 0; i < 10; i++ 
       // 监听case语句中channel的读写操作,当channel为非阻塞状态(可读写),会触发select中的case
       select 
          case i := <-chint:
             fmt.Printf("i: %v\\n", i)
          case s := <-chstr:
             fmt.Printf("s: %v\\n", s)
          default:
             fmt.Printf("default...\\n")
          
    
 

  执行结果如下:

  

3、select注意事项

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

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

  select的default总是可运行的。

五、定时器Timer

  定时器Timer只执行一个。

1、timer的创建

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

示例如下:

 package main
 import (
    "fmt"
    "sync"
    "time"
 )
 var wait sync.WaitGroup
 // 使用定时器
 func time01() 
    defer wait.Done()
    fmt.Printf("before: %v\\n", time.Now())
    timer := time.NewTimer(time.Second * 2)
    <-timer.C // 阻塞,直到指定时间到达
    fmt.Printf("after: %v\\n", time.Now())
 
 func main() 
    wait.Add(1)
    go time01()
    // 同步等待协程执行完成
    wait.Wait()
 

执行结果如下:

  

2、time.After

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

示例如下:

 package main
 import (
    "fmt"
    "sync"
    "time"
 )
 var wait sync.WaitGroup
 
 // 使用定时器
 func time02() 
    defer wait.Done()
    fmt.Printf("before: %v\\n", time.Now())
    // 阻塞2s
    <-time.After(time.Second * 2)
    fmt.Printf("after: %v\\n", time.Now())
 
 
 func main() 
    wait.Add(1)
    go time02()
    // 同步等待协程执行完成
    wait.Wait()
 

执行结果如下:

  

3、timer.stop

  停止定时器事件。

示例如下:

 package main
 import (
    "fmt"
    "time"
 )
 
 func main() 
    // 创建定时器
    timer := time.NewTimer(time.Second * 2)
    // 协程,匿名函数
    go func() 
       <-timer.C
       fmt.Println("timer func...")
    ()
 
    // 停止定时器,阻止timer事件发生
    stopFlag := timer.Stop()
    if stopFlag 
       fmt.Println("timer stoped...")
    
 

执行结果如下:

  

4、timer.Reset

  重置定时器。

示例如下:

 package main
 import (
    "fmt"
    "time"
 )
 
 func main() 
    // 重置定时器 reset
    fmt.Printf("before: %v\\n", time.Now())
    timer := time.NewTimer(time.Second * 2)
    timer.Reset(time.Second * 1)
    <-timer.C
    fmt.Printf("after: %v\\n", time.Now())
 

执行结果如下:

  

六、周期执行器Ticker

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

示例如下:

 package main
 import (
    "fmt"
    "time"
 )
 func main() 
    // 创建周期执行器
    ticker := time.NewTicker(time.Second * 2)
 
    // 周期执行
    for _ = range ticker.C 
       fmt.Printf("time: %v\\n", time.Now())
    
 

执行结果如下:

  

 

 package main
 import (
    "fmt"
    "time"
 )
 
 func ticker() 
    // 创建一个无缓冲的整型channel
    chint := make(chan int)
    // 关闭通道
    defer close(chint)
    // 创建定时器
    ticker := time.NewTicker(time.Second * 2)
    // 创建一个协程,利用周期定时器每2s向通道中写入数据
    go func() 
       for _ = range ticker.C 
          select 
             case chint <- 2:
             case chint <- 4:
             case chint <- 6:
          
       
    ()
    sum := 0
    // 遍历通道,若通道无数据,会阻塞
    for v := range chint 
       fmt.Printf("receive: %v\\n", v)
       sum += v
       if sum > 20 
          fmt.Printf("sum: %v\\n", sum)
          break
       
    
 
 
 func main() 
    ticker()
 

执行结果如下:

  

七、atomic

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

1、加减

 package main
 import (
    "fmt"
    "sync"
    "sync/atomic"
 )
 var i int32 = 100
 var waitg sync.WaitGroup
 // 原子加操作
 func atomicAdd() 
    atomic.AddInt32(&i, 1)
    waitg.Done()
 
 // 原子减操作
 func atomicSub() 
    atomic.AddInt32(&i, -1)
    waitg.Done()
 
 func main() 
    for i := 0; i < 100; i++ 
       // 子线程
       waitg.Add(1)
       go atomicAdd()
       // 子线程
       waitg.Add(1)
       go atomicSub()
    
    waitg.Wait()
    fmt.Printf("i = %v\\n", i)
 

  执行结果如下:

  

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

 package main
 import (
    "fmt"
    "sync/atomic"
 )
 // 载入与存储
 func loadAndStore() 
    var i int64 = 64
    // 载入 -> 读取,原子操作
    atomic.LoadInt64(&i)
    fmt.Printf("i: %v\\n", i)
 
    // 存储 -> 写入,原子操作
    atomic.StoreInt64(&i, 128)
    fmt.Printf("i: %v\\n", i)
 
 func main() 
    loadAndStore()
 

  执行结果如下:

  

3、CAS -> 比较并交换

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

 package main
 import (
    "fmt"
    "sync/atomic"
 )
 // 比较和交换
 func cas() 
    var i int64 = 256
    // 旧的值与变量i的值相同,则交换
    result := atomic.CompareAndSwapInt64(&i, 256, 64)
    if result 
       fmt.Println("cas success")
     else 
       fmt.Println("cas fail")
    
    fmt.Printf("i: %v\\n", i)
 
    // 旧的值与变量i的值不同,则不交换
    result01 := atomic.CompareAndSwapInt64(&i, 256, 8)
    if result01 
       fmt.Println("cas success")
     else 
       fmt.Println("cas fail")
    
    fmt.Printf("i: %v\\n", i)
 
 func main() 
    cas()
 

  执行结果如下:

  

 

《go语言高级编程》笔记

1.6 常见并发模式

以上是关于Go笔记(十五):并发编程的主要内容,如果未能解决你的问题,请参考以下文章

go语言学习笔记 — 进阶 — 并发编程:轻量级线程goroutine —— 并发与并行

go语言学习笔记 — 进阶 — 并发编程:调整并发的运行性能(GOMAXPROCS)

go语言学习笔记 — 进阶 — 并发编程:为函数创建goroutine

go语言学习笔记 — 进阶 — 并发编程:同步sync,竞态检测 —— 检测代码在并发环境下出现的问题

go语言学习笔记 — 进阶 — 并发编程:通道(channel) —— 在多个goroutine之间通信的管道

go语言学习笔记 — 进阶 — 并发编程:go语言的协程goroutine,与普通程序的协程coroutine