1.基础
并发:电脑同时听歌,看小说,玩游戏等。cpu会根据时间片进行划分,交替执行这些程序。我们人可以感觉是同时产生的。
并行:多个cpu(多核)同时执行
go语言里面不是线程,而是go程==>goroutine,go程是go语言原生支持的,每一个go程占用的系统资源远远小于线程,一个go程大约需要4k-5k的内存资源。
一个程序可以启动大量的go程:
- 线程==》几十个
- go程可以启动成千上百个,===》对于实现高并发,性能非常好
- 只需要在目标函数前加上go关键字即可
demo实例如下:
package main
import (
"fmt"
"time"
)
//这个用于子go程的使用
func display() {
count := 1
for {
fmt.Println("============> 这是子go程", count)
count++
time.Sleep(time.Second)
}
}
func main() {
//启动子go程
go display()
//主go程
count := 1
for {
fmt.Println("这是主go程:", count)
count++
time.Sleep(time.Second)
}
}
运行结果如下:
2,提前退出go程
- exit ====>退出当前进程
- return ====>返回当前函数
- goexit ====>提前退出当前go程
demo实例如下:
package main
import (
"fmt"
"runtime"
"time"
)
func main() {
go func() {
go func() {
func() {
fmt.Println("这是子go程内部的函数!")
//return,这是返回当前函数
//os.Exit(-1)//退出进程
runtime.Goexit() //退出当前子go程
}()
fmt.Println("子进程结束!")
fmt.Println("go 222222")
}()
time.Sleep(2 * time.Second)
fmt.Println("go 111111")
}()
fmt.Println("这是主go程!")
time.Sleep(3 * time.Second)
fmt.Println("over!")
}
3.无缓冲通道channel
demo实例如下:
package main
import (
"fmt"
"time"
)
func main() {
//此时是无缓冲的管道
numChan := make(chan int) //装数字的管道,使用管道的时候一定要make,同map一样,否则是nil
//有缓冲的管道
//numChan := make(chan int, 10)
go func() {
for i := 0; i < 50; i++ {
data := <-numChan
fmt.Println("子go程1,读取数据 ===》 data:", data)
}
}()
go func() {
for i := 0; i < 20; i++ {
//向管道中写入数据
numChan <- i
fmt.Println("====> 子go程2,写入数据:", i)
}
}()
for i := 20; i < 50; i++ {
//向管道中写入数据
numChan <- i
fmt.Println("====> 这是主go程,写入数据:", i)
}
time.Sleep(5 * time.Second)
}
程序运行如下:
4.有缓冲通道channel
注意事项:
- 当缓冲写满的时候,写阻塞,当被读取后,再恢复写入
- 当缓冲区读取完毕,读阻塞
- 如果管道没有使用make分配空间,那么管道默认是nil的,读取,写入都会阻塞
- 对于一个管道,读与写的次数必须对等
- 当管道的读写速度不一致时,如果阻塞在主go程,程序会崩溃;如果阻塞在子进程,程序就会泄露。
demo实例如下:
package main
import (
"fmt"
"time"
)
func main() {
//此时是无缓冲的管道
//numChan := make(chan int) //装数字的管道,使用管道的时候一定要make,同map一样,否则是nil
//有缓冲的管道
numChan := make(chan int, 10)
go func() {
for i := 0; i < 50; i++ {
data := <-numChan
fmt.Println("子go程1,读取数据 ===》 data:", data)
}
}()
go func() {
for i := 0; i < 20; i++ {
//向管道中写入数据
numChan <- i
fmt.Println("====> 子go程2,写入数据:", i)
}
}()
for i := 20; i < 50; i++ {
//向管道中写入数据
numChan <- i
fmt.Println("====> 这是主go程,写入数据:", i)
}
time.Sleep(5 * time.Second)
}
程序运行如下:
5.for range 遍历管道
建议经常使用for range 进行读写操作
package main
import "fmt"
func main() {
numChan2 := make(chan int, 10)
go func() {
for i := 0; i < 50; i++ {
//向管道中写入数据
numChan2 <- i
fmt.Println("写入数据:", i)
}
fmt.Println("数据全部写完毕,准备关闭管道")
close(numChan2)
}()
//遍历管道时,只返回一个值
//for range 不知道管道是否已经写完,所以会一直等待,导致程序崩溃
//解决办法:主动关闭管道,用close在写入端关闭管道
for v := range numChan2 {
fmt.Println("读取数据:", v)
}
fmt.Println("OVER!")
}
6.管道总结
-
当管道写满了,写阻塞。
-
当缓冲区读完了,读阻塞。
-
如果管道没有使用make分配空间,管道默认值时nil。
-
从nil的管道读取数据,写入数据,都会阻塞(注意:不会崩溃)。
-
从一个已经close的管道读取数据时,会返回零值(不会崩溃)。
-
向一个已经close的管道写数据时,会崩溃。
-
关闭一个已经close的管道,程序会崩溃。
-
关闭管道的动作,一定要在写管道一方执行,不应该放在读取端 ,否则写端继续写会崩溃
-
读和写一定要对等,否则:1,在多个go程中,资源泄露。2,在主go程中,程序崩溃(deadlock)
demo实例如下:
package main
import (
"fmt"
)
func main() {
numChan2 := make(chan int, 10)
go func() {
for i := 0; i < 50; i++ {
//向管道中写入数据
numChan2 <- i
fmt.Println("写入数据:", i)
}
fmt.Println("数据全部写完毕,准备关闭管道")
close(numChan2)
//close(numChan2)//重复关闭程序会崩溃
//numChan2 <- 10//关闭再写就会崩溃
}()
//遍历管道时,只返回一个值
//for range 不知道管道是否已经写完,所以会一直等待,导致程序崩溃
//解决办法:主动关闭管道,用close在写入端关闭管道
for v := range numChan2 {
fmt.Println("读取数据:", v)
}
//time.Sleep(3 * time.Second)
//i := <-numChan2
//fmt.Println("已经关闭之后继续读取数据:", i)//已经关闭管道再读取数据,会返回0值
fmt.Println("OVER!")
}
7.判断管道是否已经关闭
需要知道一个管道的状态,如果已经关闭了,读不怕,会返回零;如果再写入的话,会有崩溃的风险。
channel:==>v,ok:= <- numChan
package main
import "fmt"
func main() {
numChan := make(chan int, 10)
//写
go func() {
for i := 0; i < 10; i++ {
numChan <- i
fmt.Println("写入数据:", i)
}
close(numChan)
}()
//读数据
for {
v, ok := <-numChan//ok-idom模式
if !ok {
fmt.Println("管道已经关闭了,准备退出!")
break
}
fmt.Println("v:", v)
}
fmt.Println("OVER!")
}
8.单向通道
numChan := make(chan int,10) ===>双向通道,既可以读,也可以写
单向通道:为了明确语义,一般用于函数参数
- 单向读通道 var numChanReadOnly <- chan int
- 单项写通道 var numChanWriteOnly chan <- int