### 使用互斥锁线程同步
互斥锁是最简单的一种锁类型,同时也比较暴力,当一个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调用一次,重复调用无效。
- 安装开发环境
- 安装开发环境
- 安装详细教程
- 引入包
- Go语言基础
- 基本变量与数据类型
- 变量
- 数据类型
- 指针
- 字符串
- 代码总结
- 常量与运算符
- 常量
- 运算符
- 流程控制
- if判断
- for循环
- switch分支
- goto跳转
- 斐波那契数列
- Go语言内置容器
- 数组
- 切片
- 映射
- 函数
- 函数(上)
- 函数(中)
- 函数(下)
- 小节
- 包管理
- 结构体
- 结构体(上)
- 结构体(中)
- 结构体(下)
- 小节
- 错误处理
- 错误处理
- 宕机
- 错误应用
- 小节
- 文件操作
- 获取目录
- 创建和删除目录
- 文件基本操作(上)
- 文件基本操作(中)
- 文件基本操作(下)
- 处理JSON文件
- 接口与类型
- 接口的创建与实现
- 接口赋值
- 接口嵌入
- 空接口
- 类型断言(1)
- 类型断言(2)
- 小节
- 并发与通道
- goroutine协程
- runtime包
- 通道channel
- 单向通道channel
- select
- 线程同步
- 多线程的深入学习
- http编程
- http简介
- Client和Request
- get请求
- post请求
- 模块函数方法
- 模块
- fmt库,模块
- 项目练习
- 爬虫:高三网
- 爬虫:快代理
- 爬虫:快代理2
- 多线程:通道思路
- 多线程爬虫:快代理