多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
### 使用互斥锁线程同步 互斥锁是最简单的一种锁类型,同时也比较暴力,当一个goroutine获得了锁之后,其他goroutine就只能乖乖等到这个goroutine释放该锁。go语言使用sync.Mutex实现互斥锁。 Mutex 是最简单的一种锁类型,同时也比较暴力,当一个 goroutine 获得了 Mutex 后,其他 goroutine 就只能乖乖等到这个 goroutine 释放该 Mutex。 参考:http://c.biancheng.net/view/107.html 参考:https://blog.csdn.net/luoye4321/article/details/82433144 ``` package main import ( "fmt" "sync" ) var ( // 逻辑中使用的某个变量 count int // 与变量对应的使用互斥锁 countGuard sync.Mutex ) func GetCount() int { // 锁定 countGuard.Lock() // 在函数退出时解除锁定 defer countGuard.Unlock() return count } func SetCount(c int) { countGuard.Lock() count = c countGuard.Unlock() } func main() { // 可以进行并发安全的设置 SetCount(1) // 可以进行并发安全的获取 fmt.Println(GetCount()) } ``` ``` package main import ( "fmt" "sync" "time" ) type MutexInfo struct { mutex sync.Mutex infos []int } func (m *MutexInfo) addInfo(value int) { m.mutex.Lock() m.infos = append(m.infos, value) m.mutex.Unlock() } func main() { m := MutexInfo{} for i := 0; i < 10; i++ { go m.addInfo(i) } time.Sleep(time.Second * 5) fmt.Println(m.infos) // [0 1 2 5 3 6 7 8 9 4] } ``` 我们通过多次运行发现,输出的结果并不总是从0到9按顺序输出,说明创建的10个goroutine并不是有序的抢占线程的执行权,也就是说这种同步并不是有序的同步,我们可以让10个goroutine一个一个的同步执行,但是并不能安排执行次序。 运行到这里,假如我们注释掉同步锁的代码为发生什么? 我们将addInfo方法修改如下: ``` func (m *MutexInfo) addInfo(value int) { //m.mutex.Lock() m.infos = append(m.infos, value) //m.mutex.Unlock() } ``` 运行代码,输出:[1 0 2] 结果是不是出乎意料?为什么写了10个输入,只有3个值输入成功?这时候我们不得不解释线程的另一个概念,那就是线程安全。 我们先看下go语言中slice的append过程,使用append添加一个元素时,可能会有两步来完成:先获取当前切片数组的容量,比如当前容量是2,然后在新的存储区开辟一块新的存储单元,容量为2+1,并将原来的值和新的值存入新的存储单元。在没有同步锁的情况下,如果两个线程同时执行添加元素的操作,这时候可能只有一个被写入成功。这种情况就是非线程安全,相比之下,如果同时对一个int类型数据进行操作,就不会出现这种非线程安全的情况。 ### 线程同步(读写互斥锁) go语言提供了另一种更加友好的线程同步的方式:sync.RWMutex。相对于互斥锁的简单暴力,读写锁更加人性化,是经典的单写多读模式。在读锁占用的情况下,会阻止写,但不阻止读,也就是多个goroutine可同时获取读锁,而写锁会阻止其他线程的读写操作。 RWMutex 相对友好些,是经典的单写多读模型。在读锁占用的情况下,会阻止写,但不阻止读,也就是多个 goroutine 可同时获取读锁(调用 RLock() 方法;而写锁(调用 Lock() 方法)会阻止任何其他 goroutine(无论读和写)进来,整个锁相当于由该 goroutine 独占。从 RWMutex 的实现看,RWMutex 类型其实组合了 Mutex ``` type RWMutex struct {     w Mutex     writerSem uint32     readerSem uint32     readerCount int32     readerWait int32 } ``` ``` package main import ( "fmt" "sync" "time" ) // 创建一个结构体 type MutexInfo struct { mutex sync.Mutex infos []int } //执行读的函数 func (m *MutexInfo) addInfo(value int) { // 加锁 m.mutex.Lock() // 结束时 释放锁 去锁操作 defer m.mutex.Unlock() fmt.Println("开始读", value) fmt.Println("结束读", value) } // 执行写的函数 func (m *MutexInfo) readInfo(value int) { // 加锁 m.mutex.Lock() // 释放 defer m.mutex.Unlock() fmt.Println("开始写", value) m.infos = append(m.infos, value) fmt.Println("结束写", value) } func main() { //实例化结构体 m := MutexInfo{} // 创建10个线程 for i := 0; i < 10; i++ { go m.addInfo(i) go m.readInfo(i) } time.Sleep(time.Second * 5) // 输出 结构体接收数据 infos fmt.Println(m.infos) } ``` 开始读 1 结束读 1 ... 结束读 8 开始读 9 结束读 9 [0 7 1 2 3 4 5 6 8 9] 从结果我们可以看出,开始的时候读线程占用读锁,并且多个线程可以同时开始读操作,但是写操作只能单个进行。 ### 使用条件变量实现线程同步 go语言提供了条件变量sync.Cond,sync.Cond方法如下: Wait,Signal,Broadcast。 Wait添加一个计数,也就是添加一个阻塞的goroutine。 Signal解除一个goroutine的阻塞,计数减一。 Broadcast接触所有wait goroutine的阻塞。 ``` package main import ( "fmt" "sync" "time" ) func printIntValue(value int, cond *sync.Cond) { cond.L.Lock() if value < 5 { //value小于5时,进入等待状态 cond.Wait() } //大于5的正常输出 fmt.Println(value) cond.L.Unlock() } func main() { //条件等待 mutex := sync.Mutex{} //使用锁创建一个条件等待 cond := sync.NewCond(&mutex) for i := 0; i < 10; i++ { go printIntValue(i, cond) } time.Sleep(time.Second * 1) cond.Signal() //解除一个阻塞 time.Sleep(time.Second * 1) cond.Broadcast() //解除全部阻塞 time.Sleep(time.Second * 1) } ``` 运行后先输出满足条件的值:5 6 7 8 9 解除一个阻塞,输出0,解除全部阻塞,输出1 2 3 4 go语言多线程支持全局唯一性操作,即一个只允许goruntine调用一次,重复调用无效。