• go语言并发


    go语言并发

    1. //启动多个groutine
    2. var wg sync.WaitGroup //一个计数器一样的东西,用来统计gorountine的启动数量.
    3. func hello(i int) {
    4. defer wg.Done()
    5. fmt.Println("hello Goroutine!", i)
    6. }
    7. func main() {
    8. for i := 0; i < 10; i++ { //启动9 个groutine
    9. wg.Add(1) 没循环一次就+1个计数
    10. go hello(i) //gorountine 协成启动主体.
    11. }
    12. wg.Wait() //只有这个位置等于0 的时候才能继续向下执行剩余代码.
    13. }
    14. 输出结果
    15. C:\Users\34826\AppData\Local\Temp\___go_build_main_go__1_.exe #gosetup
    16. hello Goroutine! 9
    17. hello Goroutine! 1
    18. hello Goroutine! 5
    19. hello Goroutine! 3
    20. hello Goroutine! 4
    21. hello Goroutine! 7
    22. hello Goroutine! 0
    23. hello Goroutine! 6
    24. hello Goroutine! 8
    25. hello Goroutine! 2
    26. 说明一下:
    27. 上面的代码执行多次输出结果是不一样的.因为虽然gorountine在一个循环体内,但是启动的时机并不是按照顺序进行启动的.所以最终打印的状态是乱序的.

    指定逻辑核心数

    1. //指定逻辑核心数.
    2. func a() {
    3. for i := 0; i < 10; i++ {
    4. fmt.Println("A:", i)
    5. }
    6. }
    7. func b() {
    8. for i := 1; i < 10; i++ {
    9. fmt.Println("B:", i)
    10. }
    11. }
    12. func main() {
    13. runtime.GOMAXPROCS(2) //在goroutine启动前设置逻辑线程数量.
    14. go a()
    15. go b()
    16. time.Sleep(time.Second * 20)
    17. }
    18. 输出结果:
    19. B: 2
    20. A: 1
    21. B: 3
    22. A: 2
    23. B: 4
    24. A: 3
    25. B: 5
    26. A: 4
    27. B: 6
    28. A: 5
    29. B: 7
    30. A: 6
    31. B: 8
    32. A: 7
    33. B: 9
    34. A: 8
    35. A: 9
    36. 这里说明一下,如果cpu运行足够快的话那么即使指定了2个逻辑核心也不一定会将两个gorountine调度到两个逻辑核心上.因为协程调度也需要时间.如果执行时间小于调度时间,那么goroutine就不会做调度操作.

    channel的使用

    1. //channel
    2. func recv(ch chan int) {
    3. ret := <-ch
    4. fmt.Println(ret)
    5. }
    6. //无缓冲通道
    7. func main() {
    8. ch := make(chan int)
    9. ch <- 10 //
    10. go recv(ch) //这里说明一下.刚刚发现一个问题,虽然gorountine启动时无序的.但是在main函数中是有一定顺序的,
    11. // 比如 假设先给ch进行赋值操作,那么如果没有缓冲区的话在赋值的那一刻chan是无法将这个量装进ch里面的.
    12. //所以在使用无缓冲区的ch的时候要先1启动goroutine.有接受者在才能消费传进来的数据,但是这个问题在有缓冲通道的情况下可以随意.
    13. fmt.Println("发送成功")
    14. }
    15. 输出结果
    16. C:\Users\34826\AppData\Local\Temp\___go_build_main_go__1_.exe #gosetup
    17. fatal error: all goroutines are asleep - deadlock!
    18. goroutine 1 [chan send]:
    19. main.main()
    20. D:/Go/Go/src/code.oldboy.com/studygolang/05lesson5/xueshengguanlixit/mai
    21. n.go:50 +0x65

    1. 第二次调整一下gorountine和ch的位置.
    2. func recv(ch chan int) {
    3. ret := <-ch
    4. fmt.Println(ret)
    5. }
    6. //无缓冲通道
    7. func main() {
    8. ch := make(chan int)
    9. //
    10. go recv(ch)
    11. ch <- 10
    12. fmt.Println("发送成功")
    13. }
    14. 输出结果
    15. C:\Users\34826\AppData\Local\Temp\___go_build_main_go__1_.exe #gosetup
    16. 10
    17. 发送成功
    18. 或者声明ch的时候创建才有换成区的ch

    这里说明一下.
    刚刚发现一个问题,虽然gorountine启动时无序的.但是在main函数中是有一定顺序的,
     比如 假设先给ch进行赋值操作,那么如果没有缓冲区的话在赋值的那一刻chan是无法将这个量装进ch里面的.
    所以在使用无缓冲区的ch的时候要先1启动goroutine.有接受者在才能消费传进来的数据,但是这个问题在有缓冲通道的情况下可以随意.

     有缓冲的ch通道

    有缓冲的通道会将传到ch中的数据暂时存起来,等到后续的gorountine进行调用,但是如果ch缓冲通道满了那么就无法再向通道中发送数据.那么这时候就会出现deadlock

    1. func recv(ch chan int) {
    2. ret := <-ch
    3. fmt.Println(ret)
    4. }
    5. //无缓冲通道
    6. func main() {
    7. ch := make(chan int, 1)
    8. ch <- 10
    9. ch <- 22
    10. go recv(ch)
    11. fmt.Println("发送成功")
    12. }
    13. 输出结果
    14. C:\Users\34826\AppData\Local\Temp\___go_build_main_go__1_.exe #gosetup
    15. fatal error: all goroutines are asleep - deadlock!
    16. goroutine 1 [chan send]:
    17. main.main()
    18. D:/Go/Go/src/code.oldboy.com/studygolang/05lesson5/xueshengguanlixit/mai
    19. n.go:51 +0x7f

    如果将初始的ch缓冲值增加就可以解决死锁的问题.

    1. func recv(ch chan int) {
    2. ret := <-ch
    3. fmt.Println(ret)
    4. }
    5. //无缓冲通道
    6. func main() {
    7. ch := make(chan int, 2) //只要大于发送数量都可以.
    8. ch <- 10
    9. ch <- 22
    10. go recv(ch)
    11. fmt.Println("发送成功")
    12. }
    13. 输出结果
    14. C:\Users\34826\AppData\Local\Temp\___go_build_main_go__1_.exe #gosetup
    15. 发送成功

    这个过程就好比小区快递柜.有一定的容量限制.也可以向内部放数据,但是只要数据放满了就需要有人取出最早方的数据才可以继续往里放.


    循环取值

    1. //循环取值
    2. func main() {
    3. ch1 := make(chan int) //声明一个ch1无缓冲通道
    4. ch2 := make(chan int) //声明一个ch2无缓冲通道
    5. go func() { //创建一个goroutine匿名函数,并循环向ch1中写入数字
    6. for i := 0; i < 100; i++ {
    7. ch1 <- i
    8. }
    9. close(ch1) //数据发送完成后关闭ch1通道
    10. }()
    11. go func() { //启动一个匿名goroutine并且不断判断ch1中是否还有值,如果没有值就跳出循环.如果优质的话想ch2输出数值的平方
    12. for {
    13. i, ok := <-ch1
    14. if !ok {
    15. break
    16. }
    17. ch2 <- i * i
    18. }
    19. close(ch2) //ch2循环取值完成后关闭ch2通道
    20. }()
    21. for i := range ch2 { //这里注意一下,当range在遍历通道的时候只会输出通道中的数据.没有其他值.这里输出额是i的平方
    22. fmt.Println(i)
    23. }
    24. }
    25. 输出结果:
    26. .
    27. .
    28. .
    29. 9025
    30. 9216
    31. 9409
    32. 9604
    33. 9801

    单向channel

    1. //单向通道
    2. func counter(ch1 chan<- int) { //单向通道,只能往ch1里面写
    3. for i := 0; i < 100; i++ {
    4. ch1 <- i
    5. }
    6. close(ch1) //关闭通道
    7. }
    8. func squarer(ch2 chan<- int, ch1 <-chan int) { //从ch1往ch2里面写数据.
    9. for i := range ch1 {
    10. ch2 <- i * i
    11. }
    12. close(ch2) //这里说明下,貌似只有在函数中显示的使用到的chan才需要进行关闭,如果没有显示的使用通道的话,那么就不用进行关闭操作,这里in就不需要进行关闭.
    13. }
    14. func printer(ch2 <-chan int) { //同样是单向通道,从chan里面向ch2里面写入数据.
    15. for i := range ch2 {
    16. fmt.Println(i)
    17. }
    18. }
    19. func main() {
    20. ch1 := make(chan int)
    21. ch2 := make(chan int)
    22. go counter(ch1) //与下面的gorouting几乎同时执行.
    23. go squarer(ch2, ch1) //从ch1中向ch2中传递数据
    24. printer(ch2) //从ch2中消费数据,
    25. }
    26. 输出结果:
    27. .
    28. .
    29. .
    30. 9216
    31. 9409
    32. 9604
    33. 9801
    34. 说明一下,这里我使用的是形参实参都是相同的名称,为了好区分.

    说明一下:

    关闭已经关闭的channel也会引发panic.

    select多路复用

    1. //select 多路复用
    2. func main() {
    3. ch := make(chan int, 1)
    4. for i := 0; i < 10; i++ {
    5. time.Sleep(time.Second)
    6. select {
    7. case x := <-ch:
    8. fmt.Println(x)
    9. case ch <- i:
    10. }
    11. }
    12. }
    13. 输出结果
    14. C:\Users\34826\AppData\Local\Temp\___go_build_main_go__1_.exe #gosetup
    15. 0
    16. 2
    17. 4
    18. 6
    19. 8
    20. 因为ch通道只有1个缓冲,所以每次只能写进去一个值,当通道被占用的时候其他的值就写不进通道,那么结果就只能是打印被通道接受到的值.即使有default语句但是如果前面有任意一条满足要求了,那么default就不会执行.

    并发安全和锁

    1. //并发安全和锁
    2. var x int64
    3. var wg sync.WaitGroup
    4. func add() {
    5. for i := 0; i < 5000; i++ {
    6. x = x + 1
    7. }
    8. wg.Done()
    9. }
    10. func main() {
    11. wg.Add(2)
    12. go add()
    13. go add()
    14. wg.Wait()
    15. fmt.Println(x)
    16. }
    17. 运行结果:
    18. 说明一下这里的结果是不确定的.因为在代码中启动了两个goroutine这两个存在数据竞争关系,即一个调用直接影响另一个的值.所以这里就不给具体结果了.

    互斥锁

    1. //互斥锁
    2. var x int64
    3. var wg sync.WaitGroup
    4. var lock sync.Mutex
    5. func add() {
    6. for i := 0; i < 5000; i++ {
    7. lock.Lock() //函数内调用加锁
    8. x += 1 //执行过程
    9. lock.Unlock() //函数调用解锁
    10. }
    11. wg.Done() //函数调用完 -1 需要在执行体外进行.
    12. }
    13. func main() {
    14. wg.Add(2)
    15. go add()
    16. go add()
    17. wg.Wait()
    18. fmt.Println(x)
    19. }
    20. 输出结果
    21. C:\Users\34826\AppData\Local\Temp\___go_build_main_go__1_.exe #gosetup
    22. 10000
    23. 说明一下:
    24. 这里在执行过程中添加了互斥锁.在函数调用过程中不存在数据竞争的情况.因为只要某一个时刻有一个函数在调用x那么其他的调用就需要等待.只有等待解锁后才可以对x进行操作.

    读写锁

    1. var (
    2. x int64
    3. wg sync.WaitGroup
    4. lock sync.Locker
    5. rwlock sync.RWMutex
    6. )
    7. func write() {
    8. //添加写锁
    9. rwlock.Lock()
    10. x = x + 1
    11. time.Sleep(time.Millisecond * 10) //假设写操作耗时10毫秒
    12. //解写锁
    13. rwlock.Unlock()
    14. wg.Done()
    15. }
    16. func read() {
    17. rwlock.RLock() //添加读锁
    18. time.Sleep(time.Millisecond) //假设读操作为1毫秒
    19. fmt.Println(x)
    20. rwlock.RUnlock() //解读锁
    21. defer wg.Done()
    22. }
    23. func main() {
    24. start := time.Now()
    25. for i := 0; i < 10; i++ {
    26. wg.Add(1)
    27. go write()
    28. }
    29. for i := 0; i < 10; i++ {
    30. wg.Add(1)
    31. go read()
    32. }
    33. wg.Wait()
    34. end := time.Since(start)
    35. fmt.Println(end)
    36. }
    37. 输出结果
    38. C:\Users\34826\AppData\Local\Temp\___go_build_main_go__1_.exe #gosetup
    39. 1
    40. 1
    41. 1
    42. 1
    43. 1
    44. 1
    45. 1
    46. 1
    47. 1
    48. 读写锁适合读多写少的场景.如果读写比例比较接近那么就很难发挥其优势.

    sync.waitgroup

    (wg * WaitGroup) Add(delta int)计数器+delta
    (wg *WaitGroup) Done()计数器-1
    (wg *WaitGroup) Wait()阻塞直到计数器变为0

    代码

    1. var wg sync.WaitGroup
    2. func hello() {
    3. defer wg.Done() //这里使用defer 在函数结束之前进行wg计数器减操作
    4. fmt.Println("Hello Goroutine!")
    5. }
    6. func main() {
    7. wg.Add(1) //这里预先指定好添加的goroutine数量
    8. go hello() // 启动另外一个goroutine去执行hello函数
    9. fmt.Println("main goroutine done!")
    10. wg.Wait() //计数清零才会向下执行其他代码
    11. }
    12. 输出结果
    13. C:\Users\34826\AppData\Local\Temp\___go_build_main_go__1_.exe #gosetup
    14. main goroutine done!
    15. Hello Goroutine!
    16. 这里在函数中使用defer配合wg.waitgroup相关方法

    使用sync.once包加载一次配置信息

    1. var icons map[string]image.Image
    2. var loadIconsOnce sync.Once
    3. func loadIcons() {
    4. icons = map[string]image.Image{
    5. "left": loadIcon("left.png"),
    6. "up": loadIcon("up.png"),
    7. "right": loadIcon("right.png"),
    8. "down": loadIcon("down.png"),
    9. }
    10. }
    11. func Icon(name string) image.Image {
    12. loadIconsOnce.Do(loadIcons) //这里就指明了使用sync.once加载一次loadicons
    13. return icons[name] //返回icons[name]
    14. }
    15. 这里使用的是修改配置信息.

    并发安全的sync.map

    1. func main() {
    2. var testMap sync.Map
    3. // 获取a对应的键值应该失败
    4. valueA, ok := testMap.Load("a") //直接获取test.Map中a键的值
    5. if ok {
    6. fmt.Println("a", valueA)
    7. } else {
    8. fmt.Println("a no value") // true
    9. }
    10. // 设置a对应的键值,然后再获取
    11. testMap.Store("a", "ok1") //是通store设置键值对 test.store
    12. valueA, ok = testMap.Load("a") //从testMap中获取键值
    13. if ok {
    14. fmt.Println("a", valueA) // true
    15. } else {
    16. fmt.Println("a no value")
    17. }
    18. // 设置a对应的键值,然后再获取
    19. testMap.Store("a", "ok2") //设置键值 a ok2
    20. valueA, ok = testMap.Load("a") //获取a键的值
    21. if ok {
    22. fmt.Println("a", valueA) // true
    23. } else {
    24. fmt.Println("a no value")
    25. }
    26. // 设置a对应的键值,然后再获取
    27. // 若a存在对应键值,则只是获取,不设置新值
    28. valueA, ok = testMap.LoadOrStore("a", "ok3") //如果存在就获取,如果不存在就设置
    29. if ok {
    30. fmt.Println("a", valueA) // true
    31. } else {
    32. fmt.Println("a no value")
    33. }
    34. valueA, ok = testMap.Load("a") //获取键值
    35. if ok {
    36. fmt.Println("a", valueA) // true
    37. } else {
    38. fmt.Println("a no value")
    39. }
    40. // 若a不存在对应键值,则进行设置
    41. valueB, ok := testMap.LoadOrStore("b", "ok3") //如果不存在对应键值就进行设置,存在就获取.
    42. if ok {
    43. fmt.Println("b", valueB)
    44. } else {
    45. fmt.Println("b no value") // true
    46. }
    47. valueB, ok = testMap.Load("b")
    48. if ok {
    49. fmt.Println("b", valueB) // true
    50. } else {
    51. fmt.Println("b no value")
    52. }
    53. // 遍历
    54. testMap.Range(func(key, value interface{}) bool {
    55. keyInfo := key.(string)
    56. valueInfo := value.(string)
    57. fmt.Println("keyInfo", keyInfo, "valueInfo", valueInfo)
    58. return true
    59. })
    60. // 删除
    61. testMap.Delete("a")
    62. }
    63. 输出结果
    64. C:\Users\34826\AppData\Local\Temp\___go_build_main_go__1_.exe #gosetup
    65. a no value
    66. a ok1
    67. a ok2
    68. a ok2
    69. a ok2
    70. b no value
    71. b ok3
    72. keyInfo a valueInfo ok2
    73. keyInfo b valueInfo ok3

    atmic

    1. type Counter interface {
    2. Inc()
    3. Load() int64
    4. }
    5. // 普通版
    6. type CommonCounter struct {
    7. counter int64
    8. }
    9. func (c CommonCounter) Inc() {
    10. c.counter++
    11. }
    12. func (c CommonCounter) Load() int64 {
    13. return c.counter
    14. }
    15. // 互斥锁版
    16. type MutexCounter struct {
    17. counter int64
    18. lock sync.Mutex //添加一个互斥锁到结构体中.用来调用执行过程中前后的锁.
    19. }
    20. func (m *MutexCounter) Inc() {
    21. m.lock.Lock()
    22. defer m.lock.Unlock()
    23. m.counter++
    24. }
    25. func (m *MutexCounter) Load() int64 {
    26. m.lock.Lock()
    27. defer m.lock.Unlock()
    28. return m.counter
    29. }
    30. // 原子操作版
    31. type AtomicCounter struct {
    32. counter int64
    33. }
    34. func (a *AtomicCounter) Inc() {
    35. atomic.AddInt64(&a.counter, 1)
    36. }
    37. func (a *AtomicCounter) Load() int64 {
    38. return atomic.LoadInt64(&a.counter)
    39. }
    40. func test(c Counter) {
    41. var wg sync.WaitGroup
    42. start := time.Now()
    43. for i := 0; i < 10000000; i++ {
    44. wg.Add(1)
    45. go func() {
    46. c.Inc()
    47. wg.Done()
    48. }()
    49. }
    50. wg.Wait()
    51. end := time.Now()
    52. fmt.Println(c.Load(), end.Sub(start))
    53. }
    54. func main() {
    55. c1 := CommonCounter{} // 非并发安全
    56. test(c1)
    57. c2 := MutexCounter{} // 使用互斥锁实现并发安全
    58. test(&c2)
    59. c3 := AtomicCounter{} // 并发安全且比互斥锁效率更高 我自己策略一下,其实自己加锁和使用atomic加锁效率差不多.一千万次执行相差0.03秒.第二次执行相差0.01秒.
    60. test(&c3)
    61. }
    62. 输出结果
    63. 0 2.8134764s //结果错误
    64. 10000000 2.8563644s //结果正确
    65. 10000000 2.8244477s //结果正确
    66. 0 2.788545s
    67. 10000000 2.865339s
    68. 10000000 2.8553674s

  • 相关阅读:
    h5实现签名功能
    【SLAM论文笔记】PL-EVIO笔记(下)
    大语言模型提示工程简介
    重磅!!!监控分布式NVIDIA-GPU状态
    深入理解强化学习——多臂赌博机:基于置信度上界的动作选择
    (生物信息学)R语言绘图初-中-高级——3-10分文章必备——生存曲线(初级)
    概率论的学习和整理7:理解期望和方差还是要回到随机试验本身,期望不是平均值,方差的公式不同情况不同
    NX二次开发-UFUN CSYS坐标系转换UF_CSYS_map_point
    Arrays.asList() 使用说明
    Flink Log4j 2.x使用Filter过滤日志类型
  • 原文地址:https://blog.csdn.net/crontab_e/article/details/115094282