zoukankan      html  css  js  c++  java
  • 使goroutine同步的方法总结

    前言:

    在前面并发性能对比的文章中,我们可以看到Golang处理大并发的能力十分强劲,而且开发也特别方便,只需要用go关键字即可开启一个新的协程。

    但当多个goroutine同时进行处理的时候,就会遇到同时抢占一个资源的情况(并发都会遇到的问题),所以我们希望某个goroutine等待另一个goroutine处理完某一个步骤之后才能继续。sync包就是为了让goroutine同步而出现的。当然还可以使用channel实现,这个后面会介绍到。

    锁:

    锁有两种:互斥锁(mutex)和读写锁(RWMutex)

    互斥锁: 当数据被加锁了之后,除次外的其他协程不能对数据进行读操作和写操作。 这个当然能解决并发程序对资源的操作。但是,效率上是个问题,因为当加锁后,其他协程只有等到解锁后才能对数据进行读写操作。

    读写锁: 读数据的时候上读锁,写数据的时候上写锁。有写锁的时候,数据不可读不可写。有读锁的时候,数据可读,不可写。

    两种锁的使用方式相同,这里就只列出互斥锁的代码:

    package main
    
    import (
      "sync"
      "time"
      "fmt"
    )
    
    var num = 0
    
    func main ()  {
      mu := &sync.Mutex{}
      for i:=0;i<10000;i++ {
        go func(){
          mu.Lock()
          defer mu.Unlock()
          num += 1
        }()
      }
      time.Sleep(time.Second)
      fmt.Println("num:", num)  // 如果不加锁这里的num的值会是一个随机数而不是10000
    }
    

    Once:

    有的时候,我们启动多个相同goroutine,但是里面的某个操作我只希望被执行一次,这个时候Once就上场了。

    package main
    
    
    import (
      "fmt"
      "sync"
      "time"
    )
    
    func main() {
      var once sync.Once
      one := func() {
    	fmt.Println("just once")
      }
    
      for i := 0; i < 10; i++ {
    	go func(a int) {
    	  once.Do(one)   // 只是被执行一次
    	}(i)
      }
      time.Sleep(time.Millisecond*200)
    }
    

    WaitGroup:

    当某个操作或是某个goroutine需要等待一批goroutine执行完毕以后才继续执行,那么这种多线程(go里面说的线程就是goroutine)等待的问题就可以使用WaitGroup了。

    代码如下:

    package main
    
    import (
    	"sync"
    	"fmt"
    	"time"
    )
    
    var waitGroup sync.WaitGroup
    
    func main () {
    	for i := 0; i < 10; i++ {
    		waitGroup.Add(1)  // 添加需要等待goroutine的数量
    		go func() {
    			fmt.Println("hehe")
    			time.Sleep(time.Second)
    			waitGroup.Done() // 减少需要等待goroutine的数量 相当于Add(-1)
    		} ()
    	}
    
    	waitGroup.Wait()  // 执行阻塞,直到所有的需要等待的goroutine数量变成0
    	fmt.Println("over")
    }
    

    Cond:

    sync.Cond是用来控制某个条件下,goroutine进入等待时期,等待信号到来,然后重新启动。

    代码如下:

    package main
    
    import (
    	"fmt"
    	"sync"
    	"time"
    )
    var locker = new(sync.Mutex)
    var cond = sync.NewCond(locker)
    
    func test(x int) {
    	cond.L.Lock() //获取锁
    	cond.Wait()//等待通知 暂时阻塞
    	fmt.Println(x)
    	time.Sleep(time.Second * 1)
    	cond.L.Unlock()//释放锁
    }
    func main() {
    	for i := 0; i < 40; i++ {
    		go test(i)
    	}
    	fmt.Println("start all")
    	time.Sleep(time.Second * 3)
    	fmt.Println("signal1")
    	cond.Signal()   // 下发一个通知随机给已经获取锁的goroutine
    	time.Sleep(time.Second * 3)
    	fmt.Println("signal2")
    	cond.Signal()// 下发第二个通知随机给已经获取锁的goroutine
    	time.Sleep(time.Second * 1)  // 在广播之前要等一会,让所有线程都在wait状态
    	fmt.Println("broadcast")
    	cond.Broadcast()//下发广播给所有等待的goroutine
    	time.Sleep(time.Second * 60)
    }
    

    上面代码有几个要点要特别说明一下:

    1. 每个Cond都必须有个与之关联的锁  // 见第9行

    2. 协程方法里面一开始/结束都必须加/解锁 // 见第12行和16行

    3. cond.Wait()时会自动解锁,当被唤醒时,又会加上锁。所以第2点提到必须加/解锁。

    Channel

    channel不仅可以用来goroutine之间的通信,也可以使goroutine同步完成协作。这点主要基于从channel取数据的时候,会阻塞当前goroutine这个特性。示例代码如下:

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    
    var chan1 = make(chan string, 512)
    
    var arr1 = []string{"qq","ww","ee","rr","tt"}
    
    func chanTest1() {
    	for _, v := range arr1 {
    		chan1 <- v
    	}
    	close(chan1) // 关闭channel
    }
    
    func chanTest2() {
    	for {
    		getStr, ok := <- chan1  // 阻塞,直到chan1里面有数据
    		if !ok {   // 判断channel是否关闭或者为空
    			return
    		}
    		fmt.Println(getStr) // 按数组顺序内容输出
    	}
    }
    
    func main () {
    	go chanTest1()
    	go chanTest2()
    
    	time.Sleep(time.Millisecond*200)
    }
    

  • 相关阅读:
    zabbix增加手机短信、邮件监控的注意要点,SSL邮件发送python脚本
    关于门诊保险你需要知道的事情(原创)
    儿童做家务年龄对照表,80%的父母都后悔看晚了…
    宁65年产权公寓值得投资吗?大数据帮你分析
    为什么百万医疗险越来越多,到底选哪款?
    python 爬虫 scrapy1_官网教程
    自然语言10_分类与标注
    自然语言9_NLTK计算中文高频词
    自然语言8_中文搜词
    自然语言7_NLTK中文语料库sinica_treebank
  • 原文地址:https://www.cnblogs.com/xiaoxlm/p/9753942.html
Copyright © 2011-2022 走看看