Golang非CSP并发模型外的其他并行方法总结

栏目: Go · 发布时间: 5年前

内容简介:Golang最为让人熟知的并发模型当属CSP并发模型,也就是由goroutine和channel构成的GMP并发模型,具体内容不在赘述了,可以翻回之前的文章查看。在这里,要讲讲Golang的其他并发方式。Golang不仅可以使用CSP并发模式,还可以使用传统的共享数据的并发模式。这是传统语言比较常用的的方式,即加锁。加锁使其线程同步,每次只允许一个goroutine进入某个代码块,此代码块区域称之为”

Golang最为让人熟知的并发模型当属CSP并发模型,也就是由goroutine和channel构成的GMP并发模型,具体内容不在赘述了,可以翻回之前的文章查看。在这里,要讲讲Golang的其他并发方式。

Golang不仅可以使用CSP并发模式,还可以使用传统的共享数据的并发模式。

临界区(critical section)

这是传统语言比较常用的的方式,即加锁。加锁使其线程同步,每次只允许一个goroutine进入某个代码块,此代码块区域称之为” 临界区(critical section) ”。

Golang为 临界区(critical section) 提供的是互斥锁的包和条件变量的包。

互斥锁

就是通常使用的锁,用来让线程串行用的。Golang提供了互斥锁 sync.Mutex 和读写互斥锁 sync.RWMutex ,用法极其简单:

var s sync.Mutex
    
s.Lock()
    
// 这里的代码就是串行了,吼吼吼。。。
    
s.Unlock()

Lock和Unlock

sync.Mutexsync.RWMutex 的区别

没啥大的区别,只不过 sync.RWMutex 更加细腻,可以将“读操作”和“写操作”区别对待。

sync.RWMutex 中的Lock和unLock针对写操作

var s sync.RWMutex

s.Lock()

// 上写锁了,吼吼

s.Unlock()

sync.RWMutex 中的RLock和RUnLock针对读操作

var s sync.RWMutex

s.RLock()

// 上读锁了,吼吼..

s.RUnlock()

读写锁有以下规则:

  • 写锁被锁定,(再试图进行)读锁和写锁都阻塞
  • 读锁被锁定,(再试图进行)写锁阻塞,(再试图进行)读锁不阻塞

即:多个写操作不能同时进行,写操作和读操作也不能同时进行,多个读操作可以同时进行

注意事项:

  • 不要重复锁定互斥锁;因为代码写起来麻烦,容易出错,万一死锁(deadlock)了就废了。Go语言运行时系统自己抛出的panic都属于致命错误,都是无法恢复的,调用 recover 函数对它们起不到任何作用。一旦产生死锁,程序必然崩溃。
  • 锁定和解锁一定要成对出现,如果怕忘记解锁,最好是使用 defer 语句来解锁;但是,一定不要对未锁定的或者已经锁定的互斥锁解锁,因为会触发 panic ,而且此 panic 和死锁一样,属于致命错误,程序肯定崩溃
  • sync.Mutex 是个结构体,尽量不要其当做参数,在多个函数直接传播。因为没啥意义,Golang的参数都是副本,多个副本之间都是相互独立的。

条件变量Cond

互斥锁是用来锁住资源,“创造”临界区的。而条件变量Cond可以认为是用来自行调度线程(在此即为groutine)的,当某个状态时,阻塞等待,当状态改变时,唤醒。

Cond的使用,离不开互斥锁,即离不开 sync.Mutexsync.RWMutex

Cond初始化都需要有个互斥锁。(ps:哪怕初始化不需要,就应用场景而言,也得需要个互斥锁)

Cond 提供Wait、Signal、Broadcast 三种方法。

Wait表示线程(groutine)阻塞等待;

Signal表示唤醒等待的groutine;

Broadcast表示唤醒等待的所有groutine;

初始化:

cond := sync.NewCond(&sync.Mutex{})

在其中一个groutine中:

cond.L.Lock()
for status == 0 {
     cond.Wait()
}
//状态改变,goroutine被唤醒后,干点啥。。。
cond.L.Unlock()

以上算是模板

在另外一个groutine中:

cond.L.Lock()
status = 1
cond.Signal() // 或者使用cond.Broadcast()来唤醒以上groutine中沉睡的groutine
cond.L.Unlock()

原子操作(atomicity)

原子操作是硬件芯片级别的支持,所以可以保证绝对的线程安全。而且执行效率比其他方式要高出好几个数量级。

Go语言的原子操作当然也是基于CPU和操作系统的,Go语言提供的原子操作的包是 sync/atomic ,此包提供了加(Add)、CAS(交换并比较 compare and swap)、成对出现的存储(store)和加载(load)以及交换(swap)。

此包提供的大多数函数针对的数据类型也非常的单一:只有整型!使用方式十分的简单,看着函数直接调用就好。

var a int32
a = 1
a = atomic.AddInt32(&a, 2) //此处是原子操作,就这么简单,吼吼

在此特别强调一下CAS,CAS对应的函数前缀是“CompareAndSwap”,含义和用法正如英文翻译:比较并交换。在进行CAS操作的时候,函数会先判断被操作变量的当前值是否与我们预期的旧值相等,如果相等,它就把新值赋给该变量,并返回true,反之,就忽略此操作,并返回false。

可能是Golang提供的原子操作的数据类型实在是有限,Go又补充了一个结构体 atomic.Value ,此结构体相当于一个小容器,可以提供原子操作的存储 store 和提取 load

var atomicVal atomic.Value
str := "hello"

atomicVal.Store(str) //此处是原子操作哦

newStr := atomicVal.Load() //此处是原子操作哦

其他

为了能更好的调度goroutine,Go提供了 sync.WaitGroupsync.Once 两个包。

sync.WaitGroup

sync.WaitGroup 的作用就是在多goroutine并发程序中,让主goroutine等待所有goroutine执行结束。(直接查看代码注释)

sync.WaitGroup 提供了三个函数 AddDoneWait 三者用法如下:

  • Add 写在主goroutine中,参数为将要运行的goroutine的数量
  • Done 写在各个非主goroutine中,表示运行结束
  • Wait 写在主goroutine中,block主goroutine,等待所有其他goroutine运行结束
var wait sync.WaitGroup

    wait.Add(2) //必须是运行的goroutine的数量

    go func() {
        //TODO 一顿小操作
        defer wait.Done() // done函数用在goroutine中,表示goroutine操作结束
    }()

    go func() {
        //TODO 一顿小操作
        defer wait.Done() // done函数用在goroutine中,表示goroutine操作结束
    }()

    wait.Wait() // block住了,直到所有goroutine都结束

注意

sync.WaitGroup 中有一个计数器,记录的是需要等待的goroutine的数量,默认值是0,可以通过Add方法来增加或者减少值,但是切记,千万不能让计数器的值小于零,会触发panic!

sync.WaitGroup 调用Wait方法的时候, sync.WaitGroup 中计数器的值一定要为0。因此Add中的值一定要等于非主goroutine的数量!

且不要把Add和Wait方法放到不同的goroutine中执行!

sync.Once

真真正正的只执行一次。

sync.Once 只要一个方法: Do ,里面就一个参数: func 。多说无益,复制下面代码,猜猜执行结果就知道了。

package main

import (
    "fmt"
    "sync"
)

func main() {
    var once sync.Once
    onceBody := func() {
        fmt.Println("Only once")
    }
    done := make(chan bool)
    for i := 0; i < 10; i++ {
        go func() {
            once.Do(onceBody)
            done <- true
        }()
    }
    for i := 0; i < 10; i++ {
        <-done
    }
}

执行结果

Only once

没错,只有一行。真只执行了一次。


以上所述就是小编给大家介绍的《Golang非CSP并发模型外的其他并行方法总结》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Go程序设计语言

Go程序设计语言

艾伦 A. A. 多诺万 / 李道兵、高博、庞向才、金鑫鑫、林齐斌 / 机械工业出版社 / 2017-5 / 79

本书由《C程序设计语言》的作者Kernighan和谷歌公司Go团队主管Alan Donovan联袂撰写,是学习Go语言程序设计的指南。本书共13章,主要内容包括:Go的基础知识、基本结构、基本数据类型、复合数据类型、函数、方法、接口、goroutine、通道、共享变量的并发性、包、go工具、测试、反射等。 本书适合作为计算机相关专业的教材,也可供Go语言爱好者阅读。一起来看看 《Go程序设计语言》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具