开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 9 天,点击查看活动详情
传统的编程语言(比如:C++、Java、Python 等)并非面向并发而生的,所以他们面对并发的逻辑多是基于操作系统的线程。并发的执行单元(线程)之间的通信,利用的也是操作系统提供的线程或进程间通信的原语,比如:共享内存、信号(signal)、管道(pipe)、消息队列、套接字(socket)等。
在这些通信原语中,使用最多、最广泛的(也是最高效的)是结合了线程同步原语(比如:锁以及更为低级的原子操作)的共享内存方式,因此,我们可以说传统语言的并发模型是基于对内存的共享的。
CSP
CSP(Communicating Sequential Processes,通信顺序进程)
Tony Hoare 的 CSP 模型旨在简化并发程序的编写,让并发程序的编写与编写顺序程序一样简单。Tony Hoare 认为输入输出应该是基本的编程原语,数据处理逻辑(也就是 CSP 中的 P)只需调用输入原语获取数据,顺序地处理数据,并将结果数据通过输出原语输出就可以了。
一个符合 CSP 模型的并发程序应该是一组通过输入输出原语连接起来的 P 的集合。 在 Go 中,与“Process”对应的是 goroutine
channel
goroutine 可以从 channel 获取输入数据,再将处理后得到的结果数据通过 channel 输出。通过 channel 将 goroutine(P)组合连接在一起,让设计和编写大型并发系统变得更加简单和清晰
sync 包
互斥锁(sync.Mutex)
- mutex不是可重入锁
- 保证 Lock/Unlock 成对出现,尽可能采用 defer mutex.Unlock 的方式,把它们成对、紧凑地写在一起。
// 创建
var loc sync.Mutex
loc := sync.Mutex{}
// 加锁
loc.Lock()
// 解锁
loc.Unlock()
// 非阻塞加锁
loc.TryLock()
var mut sync.Mutex
sum := 0
for i := 0; i < 5000; i++ {
go func() {
defer mut.Unlock()
mut.Lock() //加锁
sum++
}()
}
嵌入字段,在struct 上直接调用 Lock/Unlock 方法。还可以把获取锁、释放锁、计数加一的逻辑封装成一个方法。
// 线程安全的计数器类型
type Counter struct {
CounterType int
Name string
mu sync.Mutex
count uint64
}
// 加1的方法,内部使用互斥锁保护
func (c *Counter) Incr() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
// 得到计数器的值,也需要锁保护
func (c *Counter) Count() uint64 {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}
读写锁(sync.RWMutex)
读时并发,写时加锁
var mut sync.RWMutex
// 写锁
mut.Lock()
mut.Unlock()
mut.TryLock()
// 读锁
mut.RLock()
mut.RUnlock()
mut.TryRLock()
// 获得一个读锁
mut.RLocker()
sync.WaitGroup
wait时所有Add都Done后才结束阻塞。
var wg sync.WaitGroup
// 增加计数
wg.Add(2)
// 减少计数
wg.Done()
// 阻塞直到wg为0
wg.Wait()
- 注意所有add完成,再运行wait
- wait时不要再并发add
条件变量(sync.Cond)
一个条件变量可以理解为一个容器,这个容器中存放着一个或一组等待着某个条件成立的goroutine。当条件成立时,这些处于等待状态的goroutine将得到通知并被唤醒以继续后续的工作。
c := sync.NewCond(&sync.Mutex{})
// 加锁更改等待条件
c.L.Lock()
c.L.Unlock()
// 等待唤醒
c.Wait()
// 广播唤醒所有的等待者
c.Signal()
c.Broadcast()
- 调用 cond.Wait 方法之前一定要加锁
- waiter goroutine 被唤醒不等于等待条件被满足
Cond 和一个 Locker 关联,可以利用这个 Locker 对相关的依赖条件更改提供保护。
Once
可以多次调用 Do 方法,但是只有第一次调用 Do 方法时 f 参数才会执行,这里的 f 是一个无参数无返回值的函数。
var once sync.Once
f1 := func() {
fmt.Println("in f1")
}
once.Do(f1)
Once实现单例模式
type Singleton struct {
}
var singleInstance *Singleton
var once sync.Once
func GetSingletonObj() *Singleton {
once.Do(func() {
singleInstance = new(Singleton)
})
return singleInstance
}