go 基础之并发笔记
协程
与传统的系统级线程和进程相比,协程的大优势在于其“轻量级”,可以轻松创建上百万个而不会导致系统资源衰竭,而线程和进程通常多也不能超过1万个。这也是协程也叫轻量级线程的原因。
go + 函数名:启动一个协程执行函数体
package main
import
(
"fmt"
"time"
)
func Add(x, y int) {
z := x + y
fmt.Println(z)
}
func main() {
for i := 0; i < 10; i++ {
go Add(i,i)
}
time.Sleep(2 * time.Second)
}
12
8
10
4
6
16
14
0
18
2
channel
channel初始化必须要make
channel的读写
ch <- c 写
c := <-ch 读
在读同一个channel的时候如果没有数据,则会堵塞掉 直到写数据到这个channel里
package main
import
(
"fmt"
"strconv"
"time"
)
func Read(ch chan int) {
fmt.Println("Read channel")
value := <- ch
fmt.Println("value:" + strconv.Itoa(value))
}
func Write(ch chan int) {
fmt.Println("Write channel")
ch <- 10
}
func main() {
ch := make(chan int)
go Read(ch)
go Write(ch)
time.Sleep(10 * time.Second)
}
Read channel
Write channel
value:10
这种channel的阻塞特性可以用来同步协程,举个例子
func Add(x, y int, ch chan int) {
z := x + y
fmt.Println(z)
ch <- 1
}
func main() {
chs := make([]chan int,10)
for i := 0; i < 10; i++ {
chs[i] = make(chan int)
go Add(i,i,chs[i])
}
// time.Sleep(2 * time.Second)
for _,v := range chs{
<- v
}
}
2
8
4
6
0
12
10
14
18
16
缓存channel
c:=make(chan int,n),n为缓存区的大小,c:=make(chan int,0) 等价于c:=make(chan int)
写channel的时候如果超过它的缓存区并且没有被读取的话,写操作将堵塞掉
var ch chan int
func test_channel() {
ch <- 1
fmt.Println("ch 1")
ch <- 1
fmt.Println("ch 2")
ch <- 1
fmt.Println("come to end goroutine 1")
}
func main() {
ch = make(chan int,2)
go test_channel()
time.Sleep(2 * time.Second)
fmt.Println("running end!")
<- ch
time.Sleep(time.Second)
}
ch 1
ch 2
running end!
come to end goroutine 1
select
select{
case <- chan1: // 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1: // 如果成功向chan2写入数据,则进行该case处理语句
default: // 如果上面都没有成功,则进入default处理流程
}
超时的经典控制实现
func main() {
ch := make(chan int)
timeout := make(chan int)
go func () {
time.Sleep(time.Second)
timeout <- 1
}()
select{
case <- ch:
fmt.Println("come to ch")
case <- timeout:
fmt.Println("come to timeout")
}
}
come to timeout
利用time包的After方法实现超时控制
import
(
"fmt"
"time"
)
func main() {
ch := make(chan int)
select{
case <- ch:
fmt.Println("come to ch")
case <- time.After(time.Second):
fmt.Println("come to timeout")
come to timeout
协程与线程的区别
一般协程不会去出让cpu的使用权,不像线程是根据cpu的调度算法来切换使用的
协程切换cpu的使用主要通过以下两种方式来实现的
- 该任务的业务代码主动要求切换,即主动让出执行权
(
"fmt"
"runtime"
"strconv"
"time"
)
func main() {
// 协程1
go func () {
for i := 0; i < 10; i++ {
if i == 5 {
runtime.Gosched() // 主动要求出让cpu
}
fmt.Println("routine 1 "+ strconv.Itoa(i))
}
}()
// 协程2
go func () {
for i := 10; i < 20; i++ {
fmt.Println("routine 2 "+ strconv.Itoa(i))
}
}()
time.Sleep(time.Second)
}
routine 1 0
routine 1 1
routine 1 2
routine 1 3
routine 1 4
routine 2 10
routine 2 11
routine 2 12
routine 2 13
routine 2 14
routine 2 15
routine 2 16
routine 2 17
routine 2 18
routine 2 19
routine 1 5
routine 1 6
routine 1 7
routine 1 8
routine 1 9
- 发生了IO,导致执行阻塞
func main() {
ch := make(chan int)
// 协程1
go func () {
for i := 0; i < 10; i++ {
if i == 5 {
<- ch
}
fmt.Println("routine 1 "+ strconv.Itoa(i))
}
}()
// 协程2
go func () {
for i := 10; i < 20; i++ {
fmt.Println("routine 2 "+ strconv.Itoa(i))
}
ch <- 1
}()
time.Sleep(time.Second)
}
routine 1 1
routine 1 2
routine 1 3
routine 1 4
routine 2 10
routine 2 11
routine 2 12
routine 2 13
routine 2 14
routine 2 15
routine 2 16
routine 2 17
routine 2 18
routine 2 19
routine 1 5
routine 1 6
routine 1 7
routine 1 8
routine 1 9