首页 > 编程语言 >Go笔记(十五):并发编程

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

时间:2023-05-06 09:01:08浏览次数:50  
标签:Printf fmt 编程 并发 func time Go main 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 }

  执行结果如下:

  

 

标签:Printf,fmt,编程,并发,func,time,Go,main,go
From: https://www.cnblogs.com/RunningSnails/p/17375894.html

相关文章

  • go测试库之apitest
    前言使用go语言做开发差不多快一年了,主要用来写后端Web服务,从一开始吐槽他的结构体,比如创建个复杂的JSON格式数据,那是相当的痛苦。还有err处理写的巨麻烦。当然,go也有爽的地方,创建个线协程简直太简单了。到后来慢慢接受,觉得效率还行,因为是静态强类型语言,在修改完项目代码之......
  • 常用的截取字符串方法JS和Golang实现
    JS中截取字符串很简单,直接使用substr函数substr()方法可在字符串中截取从开始下标开始的指定数目的字符。下标是从0开始算例如:"21".substr(0,1)  返回2golang实现的substr//截取字符串,支持多字节字符//start:起始下标,负数从从尾部开始,最后一个为-1//length:截取长度,......
  • golang控制语句和运算符
    一、go程序基础1、文件名go语言文件名命名规范:go语言的文件名必须以.go结尾。go语言的文件名必须以小写字母开头,否则会报错。go语言的文件名不能包含空格,否则会报错。go语言的文件名不能包含特殊字符,否则会报错。go语言的文件名不能包含中文,否则会报错。go语言的文件名不......
  • go 快速入门
    1.下载并安装gin:$goget-ugithub.com/gin-gonic/gin使用Gomodules工具管理项目初始化gomod$gomodinitlearninggopackagemainimport"github.com/gin-gonic/gin"funcmain(){r:=gin.Default()r.GET("/ping",func(c*gin.Context......
  • 云原生时代崛起的编程语言Go常用标准库实战
    @目录基础标准库简述字符串-string底层结构函数长度格式化输出模版-templatetext/templatehtml/template正则表达式-regexp编码-encodingBase64JSONXML时间-time网络-netURLHTTP客户端和服务端加密IO操作读写文件环境变量命令行数据库排序-sort测试和基准测试基础标准库简述Go......
  • mongodb使用and配合or查询
    使用mongodb时,有时需要使用and配合(嵌套)or查询。实现类似以下sql的语句:select*fromMongoDbTestwherestatus=1and(userId="abc"orprice>=2)对应的mongodb语句如下:db.getCollection("mongoDbTest").find({"status":1,"$and":[{"$or&q......
  • Java并发(四)----线程运行原理
    1、线程运行原理1.1栈与栈帧  JavaVirtualMachineStacks(Java虚拟机栈JVM)我们都知道JVM中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存......
  • C++中的多线程编程和同步机制
    C++中的多线程编程和同步机制使得程序员可以利用计算机的多核心来提高程序的运行效率和性能。本文将介绍多线程编程和同步机制的基本概念和使用方法。多线程编程基础在C++中,使用<thread>库来创建和管理线程。线程可以通过函数、成员函数或者Lambda表达式来实现。以下是一个使......
  • SpringBoot配置mongodb打印日志
    在application.yml添加配置:logging:level:org.springframework.data.mongodb.core.MongoTemplate:DEBUG如果使用的是application.properties,则是:logging.level.org.springframework.data.mongodb.core.MongoTemplate=DEBUG......
  • 高并发----解决方案
     基本的解决方案集中在这样几个环节:使用高性能的服务器、高性能的数据库、高效率的编程语言、还有高性能的Web容器  1、HTML静态化通过信息发布系统来管理和实现的,信息发布系统可以实现最简单的信息录入自动生成静态页面,还能具备频道管理、权限管理、自动抓取等功能,对于一个大型......