golang goroutine与channel

并发与并行

并发:同一时间段内执行多个任务
并行:同一时刻执行多个任务

golang的并发使用goroutine来实现。goroutine类似于线程,属于用户态的线程,可以根据需要创建成千上万个goroutine并发工作。

Go还提供channel在多个goroutine间通信。goroutine和channel是GO CSP 并发模式的重要实现基础。

启动goroutine

开启goroutine 只需要在函数前面加go 关键字即可。

并发实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)

func hello(){

fmt.Println("HELLO")
}

func main(){ //开启一个主goroutine去执行main函数
go hello() //开启一个goroutine去执行hello函数 有可能不输出Hello
fmt.Println("start")
//time.Sleep(time.Second * 1) 不建议使用
}

匿名函数goroutine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"sync"
)

var wg sync.WaitGroup

func main(){ // 开启一个主goroutine去执行main函数

wg.Add(100) //计数
// 启动100个线程
for i:=0;i<100;i++ {
go func(i int){
fmt.Println("hello",i)
wg.Done()
}(i)
}
fmt.Println("start")
wg.Wait() //等待所有线程都执行完才结束 是阻塞状态
}

通过GOMAXPROCS可以设置当前GO占用CPU的核心数

Go语言中的操作系统线程和goroutine的关系:

1
2
3
1.一个操作系统线程对应用户态多个goroutine.
2.go程序可以同时使用多个操作系统线程.
3.goroutine和OS线程是多对多的关系

channel

channel是一种类型

不同的goroutine之间的数据交互采用channel(通道)。通道像一个传送带或者队列,遵循先入先出(FIFO)的规则,保证数据的顺序。

声明channel类型

1
2
3
4
5
6
7
8
9
10
11
12
package main

import "fmt"

func main(){
// var 变量 chan 元素类型
var ch1 chan int

fmt.Println(ch1) //默认是nil


}

创建通道使用make方法

1
2
ch2 := make(chan []string,20)  //20为缓存区大小
fmt.Println(ch2)

channel操作

发送

1
ch1<-10

接收

1
x:= <- ch1

实例:

1
2
3
4
5
6
7
8
9
10
11

func main(){
// var 变量 chan 元素类型
var ch1 chan int
ch1 = make(chan int,10) //ch1 = make(chan int) 无缓冲区通道
ch1 <- 20 //将20发送到ch1
x := <- ch1
fmt.Println(x)
close(ch1) //关闭通道

}

如果是无缓冲区通道发送时会阻塞,只有当另一个通道去获取通道数据后才可以。
带缓存区通道可以存放缓冲区大小的数据,并不会阻塞,是个异步通道。

实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

package main

import "fmt"

func f1(ch1 chan int){
for i:=0;i<100;i++{

ch1 <- i
}

close(ch1)
}


func f2(ch1,ch2 chan int){
//从通道中取值1
for{
tmp,ok := <-ch1
if !ok{
break
}else{
ch2 <- tmp * tmp
}
}
close(ch2)

}


func main(){
// var 变量 chan 元素类型
ch1 := make(chan int,100)
ch2 := make(chan int,100)

go f1(ch1)
go f2(ch1,ch2)

//从通道中取值2

for ret:=range ch2{
fmt.Println(ret)
}

}

单向通道

一般用在给函数传递参数

1
2
3
4
5
6
7
8
func f1(ch1 chan <- int){
for i:=0;i<100;i++{

ch1 <- i
}

close(ch1)
}

worker pool(goroutine池)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import (
"fmt"
"time"
)

func worker(id int,jobs<-chan int,results chan<- int){
for job := range jobs{
fmt.Printf("worker:%d,start job:%d\n",id,job)
results <- job*2

time.Sleep(time.Millisecond* 500)
}

}


func main(){

jobs :=make(chan int,100)
results :=make(chan int,100)

//3个goroutine
for j:=0;j<3;j++{
go worker(j,jobs,results)
}

//5个任务
for i:=0;i<5;i++{
jobs <- i
}

close(jobs)

for i:=0;i<5;i++{
ret := <- results
fmt.Println(ret)
}

}

select 多路复用

select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。

1
2
3
4
5
6
7
8
9
每个 case 都必须是一个通信
所有 channel 表达式都会被求值
所有被发送的表达式都会被求值
如果任意某个通信可以进行,它就执行,其他被忽略。
如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。

否则:
1.如果有 default 子句,则执行该语句。
2.如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

package main

import "fmt"

func main(){

ch :=make(chan int,10)
for i:=0;i<10;i++{

select {
case x:=<-ch:
fmt.Println(x)
case ch<-i:
fmt.Println(i,"232323")

default:
fmt.Println("不做操作")
}
}
}