当前位置:首页 > Golang > 正文内容

关于go协程同步的几种方法

周伯通2021-01-10Golang140

这里咋们简要介绍下关于go中协程的几种同步方法。先说下概念性的:

协程概念简要理解

协程有点类似线程,是一种更为轻量级的调度单位,但协程还是不同于线程的,线程是系统级实现的,常见的调度方法是时间片轮转法,比如每隔10s切换一个线程执行。

协程则是应用软件级实现,它和线程的原理差不多,当一个协程调度到另一个协程时,将上一个协程的上下文信息压入堆栈,来回切换。一个线程可以跑很多个协程,由这个线程来调度协程的切换,如果是C/C++的话底层就能通过select/poll/epoll来做,比如微信后台的开源libco库。

go协程底层不是C,纯go实现的,go的协程应该是目前各类有协程概念的语言中实现的最完整和成熟的,调度是基于GPM模型实现的,有兴趣可以去了解下,这里我也不扯远了,下面看看协程的同步。

为什么要做同步

至于为什么需要同步呢,类似线程要做同步差不多,现在的cpu都是多核,如果一核一个线程同时一起访问同一块内存中的数据吗?那么可能上一个线程(第一个线程)刚把数据从寄存器拷贝到内存,第二个线程马上又把此数据用它修改的值给覆盖了,这样共享数据变会乱套。

举个例子 :

用3个协程序并发各自增一个全局变量10000次

package main

import (
   "fmt"
   "time"
)

var share_num uint64 = 0

func incrNum() {
   for i := 0; i < 10000; i++ {
      share_num++
   }

   fmt.Println(share_num)
}

func main() {

   for i := 0; i < 3; i++ {
      go incrNum()
   }

   time.Sleep(5 * time.Second)

}

输出:

10000
13351
23351

运行多次 , 可以看到我们虽然自增了10000次,但没有一个输出30000(3个协程自增)的结果。那么本文重点来了:

协程的常见同步方法

Mutex

互斥锁,可以创建为其他结构体的字段;零值为解锁状态。Mutex类型的锁和线程无关,可以由不同的线程加锁和解锁。

package main

import (
   "fmt"
   "sync"
   "time"
)

var share_num uint64 = 0

var lck sync.Mutex

func incrNum() {
   for i := 0; i < 10000; i++ {
      lck.Lock()
      share_num++
      lck.Unlock()
   }

   fmt.Println(share_num)
}

func main() {

   for i := 0; i < 3; i++ {
      go incrNum()
   }

   time.Sleep(5 * time.Second)

}

输出:

22073
27091
30000

可以看到其中一个协程最后输出了30000,下面我们通过管道channel实现。

channel

使用go的channel, 下面一个典型的生产消费模型:

package main

import (
   "fmt"
   "strconv"
   "time"
)

func main() {

   msg_chan := make(chan string)
   done := make(chan bool)

   i := 0

   go func() {
      for {
         i++
         time.Sleep(1 * time.Second)
         msg_chan <- "消息发送:在吗?"
         <-done // 这里从管道done 取值,必须要有发送的地方,不然阻塞
      }
   }()

   go func() {
      for {
         select {
         case msg := <-msg_chan:
            i++
            fmt.Println(msg + " 当前循环i:" + strconv.Itoa(i))
            time.Sleep(2 * time.Second)
            done <- true // 发送 true 到done
         }
      }

   }()

   time.Sleep(20 * time.Second)
}

输出:

消息发送:在吗? 当前循环i:2
消息发送:在吗? 当前循环i:4
消息发送:在吗? 当前循环i:6
消息发送:在吗? 当前循环i:8
消息发送:在吗? 当前循环i:10
消息发送:在吗? 当前循环i:12
消息发送:在吗? 当前循环i:14

WaitGroup

sync包中的WaitGroup可用等待一组协程的结束,父协程通过Add方法来设定应等待的线程的数量,每个被等待的协程在结束时调用Done方法,
同时,主协程里调用Wait方法阻塞直至所有线程结束。

package main

import (
   "fmt"
   "net/http"
   "sync"
)

var wg sync.WaitGroup
var urls = []string{
   "https://www.mi.com/",
   "http://www.baidu.com/",
   "https://www.jd.com/",
}

func main() {

   for _, url := range urls {
      wg.Add(1)
      go func(url string) {
         defer wg.Done()
         http.Get(url)
      }(url)
   }
   wg.Wait()
   fmt.Print("结束")
}

以上是关于go协程同步的常用处理方式。关于更多的协程同步技巧,欢迎诸位拍砖留言。

    扫描二维码推送至手机访问。

    版权声明:本文由周伯通的博客发布,如需转载请注明出处。

    本文链接:http://zhoubotong.site/post/20.html

    分享给朋友:

    相关文章

    Go slice初始化转换json

            切片slice的声明和初始化 , 如果我们只是单纯的声明 var list []string ...

    Go map定义的几种方式以及修改技巧

    直入正题,我们看下以下代码:package main import (    "encoding/json"  ...

    Go 安装配置golint

    Go 安装配置golint

    一. Golint介绍Golint 是一个源码检测工具用于检测代码规范Golint 不同于gofmt, Gofmt用于代码格式化Golint会对代码做以下几个方面检查package注释 必须按照 “P...

    关于Go的内存对齐

    关于Go的内存对齐

    今天看到群里有人提到内存对齐的东西,网上查阅了golang相关的内存对齐资料,特意整理了下,希望对大家有帮助。看完这篇介绍。我们将获得以下知识点:1.什么是内存对齐?2.为什么需要内存对齐?3.如何进...

    Go channel 协程为什么是安全的

        Channel跟java/php的 thread不一样,首先channel是协程不是线程。channel不会产生新的线程,自然不会涉及到新的进程或者线程...

    发表评论

    访客

    看不清,换一张

    ◎欢迎参与讨论,请在这里发表您的看法和观点。