0x01 goroutine
go
是在语言层面上支持并发编程,使用方法非常简单,通过go
关键字后面加入函数即可。
func main() {
for i := 0; i < 1000; i++ {
go func(i int) {
fmt.Printf("hello goroutine %d\n", i)
}(i)
}
time.Sleep(time.Microsecond)
}
上面的代码就是开了1000
个协程
。协程
是一种轻量级的线程
,非抢占式多任务处理,由协程主动交出控制权,是编译器、解释器、虚拟机层面的多任务,多个协程可能在一个或多个线程上运行。
那么怎么交出控制权呢?
IO
,select
runtime.Gosched()
channel
- 等待锁
- 函数调用
0x02 channel
channel
的概念有点类似于进程中的pipe
,不过channel
是用于goruntine
之间的交互。在go
语言中channel
的定义非常简单,我们通过chan
这个关键字定义
c := make(chan int)
我们可以通过channel
收发数据
c <- 1 // 发送
n := <-c //接收
也就是channel
在左边表示发送数据,而channel
在右边表示接收数据。例子
func chanDemo() {
c := make(chan int)
go func() {
for {
n := <-c
fmt.Println(n)
}
}()
c <- 1
}
我们也可以创建只发送或者只接收数据的channel
c := make(chan<- int) // 只发送
c := make(<-chan int) // 只接收
我们可以在创建channel
的时候设置缓冲区大小
c := make(chan int, 3) // buf size 3
channel
的关闭(表示我们发送方发完了)
close(c)
判断channel
关闭的几种方式
-
n, ok := <-c if !ok { break }
-
for n := range c { //也就是将管道当做一个容器使用 }
当我们工作协程结束后需要通知我们的主协程,可以这样做
w := worker{
in: make(chan int),
done: make(chan bool),
}
建立一个结构体,包含我们需要返回的确认结果,我们也通过goroutine
去传出我们任务结束
这个消息。
当多个协程结束的时候通知主协程,我们可以单独开一个协程通知,因为goroutine
在发送的过程中没有接收的话就会阻塞(类似于pipe
)。
func doWorker(id int, c chan int, done chan bool) {
for {
fmt.Printf("Worker %d reveived %c\n", id, <-c)
go func() {
done <- true
}()
}
}
上面是一种简单的处理思路。还有一种处理思路是使用sync.WaitGroup
func doWorker(id int, c chan int, wg *sync.WaitGroup) {
for {
fmt.Printf("Worker %d reveived %c\n", id, <-c)
wg.Done()
}
}
type worker struct {
in chan int
wg *sync.WaitGroup
}
func createWorker(id int, wg *sync.WaitGroup) worker {
w := worker{
in: make(chan int),
wg: wg,
}
go doWorker(id, w.in, wg)
return w
}
func chanDemo() {
var wg sync.WaitGroup
var channels [10]worker
for i := 0; i < 10; i++ {
channels[i] = createWorker(i, &wg)
}
wg.Add(10)
for i := 0; i < 10; i++ {
channels[i].in <- 'a' + i
}
wg.Wait()
}
0x03 select
类似于unix
网络编程中的select
网络模型。
func generator() chan int {
out := make(chan int)
go func() {
i := 0
for {
time.Sleep(time.Duration(rand.Intn(1500))*time.Microsecond)
out <- i
i++
}
}()
return out
}
var c1, c2 = generator(), generator()
w := createWorker(0)
for {
select {
case n := <-c1:
w <- n
case n := <-c2:
w <- n
}
}
但是这种写法不好,原因在于w <- n
会被阻塞。我们希望数据的收发可以并发执行,那么可以这么做
var c1, c2 = generator(), generator()
var worker = createWorker(0)
n := 0
hasValue := false
for {
var activeWorker chan<- int
if hasValue {
activeWorker = worker
}
select {
case n = <-c1:
hasValue = true
case n = <-c2:
hasValue = true
case activeWorker <- n:
hasValue = false
}
}
接着按照这种思路,我们需要建立缓冲区,存放我们生产这产生的数据。我们通过一个切片实现这个buf
var values []int
var activeWorker chan<- int
var activeValue int
if len(values) > 0 {
activeWorker = worker
activeValue = values[0]
}
select {
case n = <-c1:
values = append(values, n)
case n = <-c2:
values = append(values, n)
case activeWorker <- activeValue:
values = values[1:]
}
0x04 传统同步机制
WaitGroup
Mutex
Cond
实现一个简单的atomicInt
type atomicInt struct {
value int
lock sync.Mutex
}
func (a *atomicInt) increment() {
a.lock.Lock()
defer a.lock.Unlock()
a.value++
}
func (a *atomicInt) get() int {
a.lock.Lock()
defer a.lock.Unlock()
return a.value
}
func main() {
var a atomicInt
a.increment()
go func() {
a.increment()
}()
time.Sleep(time.Second)
fmt.Println(a.get())
}
对于临界区的保护,我们可以使用匿名函数
func (a *atomicInt) increment() {
func () {
a.lock.Lock()
defer a.lock.Unlock()
a.value++
}()
}