主页

索引

模块索引

搜索页面

3.1.5. 并发编程

基本

语法:

go <fun>

管道chan定义:

ch chan int   // 通道建立(var <chanName> chan <ElementType>)
ch <- <data>  // 把数据传入管道
<- ch         // 把管道里的数据传出

ch := make(chan int)

// 关闭channel
close(ch)
// 判断channel是否已正常关闭
x, ok := <-ch

支持并发的元素集:

goroutines
channels
select
sync package
// 其中goroutines,channels和select 对应于实现CSP理论,即通过通信来共享内存
// 这几乎能解决Golang并发的90%问题,另外的10%场景需要通过同步原语来解决,即sync包相关的结构
runtime.NumCPU()        // 返回当前CPU内核数
runtime.GOMAXPROCS(2)   // 设置运行时最大可执行CPU数
runtime.NumGoroutine()  // 当前正在运行的goroutine 
runtime.Gosched()       // 用于让出CPU时间片

select语法

基本:

select {
    case <- ch:
        //如成功读取管道数据, 则执行
    case ch <- 1:
        // 如成功写入管道, 则执行
    default:
        // default
}

超时-实例1:

timeout := make(chan bool, 1)
go func() {
    time.Sleep(1e9)   // 等待一秒
    timeout <- true
}

select {
    case <- ch:
        // 从ch取得数据
    case <- timeout:
        // 1秒后得到数据
}

超时-实例2:

var c <-chan int, duration time.Duration
select {
    case data, isOK = <-c:
      return data, isOK, true
    case <- time.After(duration):
      return 0, true, false
}

channel相关

定义:

A channel provides a mechanism for concurrently executing functions to communicate
    by sending and receivingvalues of a specified element type.
The value of an uninitialized channel is nil.

关闭channel:

c := make(chan int)
close(c)
fmt.Println(<-c) //接收并输出chan类型的零值,这里int是0
// channel不像socket或者文件,不需要通过close来释放资源
// 需要close的唯一情况是,通过close触发channel读事件,comma,ok := <- c 中ok为false,表示channel已经关闭
// 只能在发送端close channel,因为channel关闭接收端能感知到,但是发送端感知不到,只能主动关闭
// 往已经关闭的channel发送信息将会触发panic

channel的传递:

type PipeData struct {
    value int
    handle func(int) int
    next chan int
}

func handle(queue chan *PipeData) {
    for data := range queue {
        data.next <- data.handler(data.value)
    }
}

单向channel:

var ch1 chan int    // 正常chan
var ch2 chan<- int  // 单写chan
var ch3 <-chan int  // 单读chan
原文说明:
The optional <- operator specifies the channel direction, send or receive.
If no direction is given, the channel isbidirectional.
A channel may be constrained only to send or only to receive by conversion or assignment.
实例:
chan T // can be used to send and receive values of type T
chan<- float64 // can only be used to send
float64s <-chan int // can only be used to receive ints

Channel特点:

1. goroutine safe
2. store and pass value between goroutines
3. provide FIFO semantics
4. can cause goroutines block and unblock

Channel的缺点:

1. Channel可能会导致死锁(循环阻塞)
2. Channel中传递的都是数据的拷贝,可能会影响性能
3. Channel中传递指针会导致数据竞态问题(data race/ race conditions)
说明: data race 指的是多线程并发读写一个变量,对应到Golang中就是多个goroutine同时读写一个变量,
    这种行为是未定义的,也就是说读变量出来的值很有可能不是写入的值,这个值是任意值都有可能

sync同步

golang 中的 sync 包实现了两种锁:

Mutex:互斥锁
RWMutex:读写锁,RWMutex 基于 Mutex 实现

Mutex(互斥锁):

Mutex 为互斥锁,Lock() 加锁,Unlock() 解锁
在一个 goroutine 获得 Mutex 后,其他 goroutine 只能等到这个 goroutine 释放该 Mutex
使用 Lock() 加锁后,不能再继续对其加锁,直到利用 Unlock() 解锁后才能再加锁
在 Lock() 之前使用 Unlock() 会导致 panic 异常
已经锁定的 Mutex 并不与特定的 goroutine 相关联
在同一个 goroutine 中的 Mutex 解锁之前再次进行加锁,会导致死锁
适用于读写不确定,并且只有一个读或者写的场景

RWMutex(读写锁):

RWMutex 是单写多读锁,该锁可以加多个读锁或者一个写锁
读锁占用的情况下会阻止写,不会阻止读,多个 goroutine 可以同时获取读锁
写锁会阻止其他 goroutine(无论读和写)进来,整个锁由该 goroutine 独占
适用于读多写少的场景

Lock() 和 Unlock():
Lock() 加写锁,Unlock() 解写锁
如果在加写锁之前已经有其他的读锁和写锁,则 Lock() 会阻塞直到该锁可用
    为确保该锁可用,已经阻塞的 Lock() 调用会从获得的锁中排除新的读取器,
    即写锁权限高于读锁,有写锁时优先进行写锁定
在 Lock() 之前使用 Unlock() 会导致 panic 异常

RLock() 和 RUnlock():
RLock() 加读锁,RUnlock() 解读锁
RLock() 加读锁时,如果存在写锁,则无法加读锁;当只有读锁或者没有锁时,可以加读锁,读锁可以加载多个
RUnlock() 解读锁,RUnlock() 撤销单次 RLock() 调用,对于其他同时存在的读锁则没有效果
在没有读锁的情况下调用 RUnlock() 会导致 panic 错误
RUnlock() 的个数不得多余 RLock(),否则会导致 panic 错误
// 同步锁(sync.Mutex读锁, sync.RWMutex写锁)
var l sync.Mutex

// 全局唯一性操作
var once sync.Once
once.Do(<fun>)

缓冲机制

chs := make(chan int, 1024)

for i:= range chs {
    fmt.Print("Received:", i)
}

不带buffer和带buffer的channel用途:

不带buffer的channel:用于同步通信
带buffer的channel:用于异步通信

趣味

看图识channel1

看图识channel1

看图识channel2

看图识channel2

相关

  • 链接: CSP(Communicating Sequential Processes) <index-key>

参考

主页

索引

模块索引

搜索页面