目录:
Go 协程
Go 协程可以看作是轻量级线程。与线程相比,创建一个Go协程的成本很小。因此在Go应用中,常常会看到有数以千计的Go协程并发地运行
Go 协程相比于线程的优势
-
相比于线程而言,Go 协程的成本低。堆栈大小只有若干kb,并且可以根据应用的需求进行增减。而线程必须制定堆栈的大小,其堆栈是固定不变的。
-
Go协程会复用(Multiplex)数量更少的OS线程。及时程序有数以千计的Go协程,也可能还有一个线程。如果该线程中的某一Go协程发生了阻塞(比如等待用户输入),系统会再创建一个OS线程,并把其余Go协程都移动到这个新的OS线程
-
Go协程使用信道(Channel)来进行通信。信道用于防止多个协程访问共享内存是发生竞态条件(Race Condition)。信道可以看作是Go协程之间的通信管道
启动一个Go协程
在调用函数和方法时,在前面加上关键字go,可以让一个新的Go协程并发运行
package main
import "fmt"
func test(){
fmt.Println("go")
}
func main() {
fmt.Println("开始")
go test()
}
此时只输出了“开始”
for循环
func test(){
fmt.Println("go")
}
func main() {
fmt.Println("开始")
//go test()
//go test()
//go test()
for i:=0;i<5;i++{
go test()
}
// 睡1s来等待协程结束
time.Sleep(time.Second*1)
}
Go 信道
信道可以想象成Go协程之间通信的管道。如同管道中的水会从一段流到另一端,通过使用信道,数据也可以从一段发送,在另一端接收。
信道的声明
var a chan bool=make(chan bool)
var a =make(chan bool)
a :=make(chan bool)
信道发送和接收数据使用如下语法
// 读取信道a
data := <-a
// 写入信道a
a <- data
示例:
func test(a chan bool){
fmt.Println("go")
a<-true
}
func main() {
//定义一个信道
//var a chan bool=make(chan bool)
//var a =make(chan bool)
a :=make(chan bool)
go test(a)
//<-a
//发送与接收默认是阻塞的
b:=<-a
fmt.Print(b)
}
信道的发送和接收默认是阻塞的
更好地理解阻塞
示例:
func hello(done chan bool) {
fmt.Println("hello go routine is going to sleep")
time.Sleep(4 * time.Second)
fmt.Println("hello go routine awake and going to write to done")
done <- true
}
func main() {
done := make(chan bool)
fmt.Println("Main going to call hello go goroutine")
go hello(done)
<-done
fmt.Println("Main received data")
}
执行效果是:
- “Main going to call hello go goroutine”
- “hello go routine is going to sleep”
- 睡4s
- “hello go routine awake and going to write to done”
- “Main received data”
使用线程来实现 计算一个数的每一位的平方和与立方和相加的结果
示例:
func calcSquares(number int, squareop chan int) {
sum := 0
//589%10==9
for number != 0 {
digit := number % 10
sum += digit * digit
number /= 10
}
squareop <- sum
}
func calcCubes(number int, cubeop chan int) {
sum := 0
for number != 0 {
digit := number % 10
sum += digit * digit * digit
number /= 10
}
cubeop <- sum
}
func main() {
number := 589
sqrch := make(chan int)
cubech := make(chan int)
go calcSquares(number, sqrch)
go calcCubes(number, cubech)
squares, cubes := <-sqrch, <-cubech
fmt.Println("Final output", squares + cubes)
}
死锁
使用信道需要考虑下死锁问题。当Go协程给一个信道发送数据时,照理说会有别的协程来接受数据,如果没有的话,程序就会触发panic(相当于python异常),形成死锁。
示例:
func main() {
ch := make(chan int)
ch <- 5
}
单向信道
单向信道只能发送或者接受数据
示例:
// sendch信道 只能写入
func sendData(sendch chan<- int) {
sendch <- 10
}
func main() {
sendch := make(chan int)
go sendData(sendch)
v, ok<-sendch
}
可以添加ok来判断是否有从信道取到值
// sendch信道 只能写入
func sendData(sendch chan<- int) {
sendch <- 10
}
func main() {
sendch := make(chan int)
go sendData(sendch)
v, ok := <-sendch
if ok == true {
fmt.Println(v)
}
}
信道关闭和使用for range遍历信道
func producer(chnl chan int) {
for i := 0; i < 10; i++ {
chnl <- i
}
close(chnl)
}
func main() {
ch := make(chan int)
go producer(ch)
for {
v, ok := <-ch
if ok == false {
break
}
fmt.Println("Received ", v, ok)
}
}