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
demo实例如下:
package main
import (
"fmt"
"time"
)
func main() {
//生产者消费者模型
//goroutine + channel
//1,在主函数中创建一个双向通道 numChan
numChan1 := make(chan int, 5)
//2,将 numChan 传递给生产者
go producer(numChan1) //双向通道可以赋值给同类型的单向通道,单向不能双向
//3,将 numChan 传递给消费者
go consumer(numChan1)
time.Sleep(2 * time.Second)
fmt.Println("over!")
}
//生产者 producer ===>只提供一个写的通道
func producer(out chan<- int) {
for i := 0; i < 10; i++ {
out <- i
fmt.Println("====> 向管道中写入数据:", i)
}
}
//消费者 consumer ===>只提供一个读的通道
func consumer(in <-chan int) {
for v := range in {
fmt.Println("从管道中读取数据:", v)
}
}
9.select
当程序中有多个channel协同工作时,chan1,chan2,某一个时刻,chan1或chan2触发了,程序要做响应的处理。使用select来监听多个通道,当管道被触发时(写入数据,读取数据,关闭管道)
select语法与switch case 很像,但是所有的分支条件都必须是通道io
demo实例:
package main
import (
"fmt"
"time"
)
func main() {
chan1 := make(chan int)
chan2 := make(chan int)
//启动一个go程,负责监听两个channel
go func() {
for {
fmt.Println("监听中...")
select {
case data1 := <-chan1:
fmt.Println("从chan1中读取数据成功,data1:", data1)
case data2 := <-chan2:
fmt.Println("从chan1中读取数据成功,data2:", data2)
default:
fmt.Println("select default 分支called")
time.Sleep(time.Second)
}
}
}()
//启动go1,写chan1
go func() {
for i := 0; i < 10; i++ {
chan1 <- i
time.Sleep(time.Second)
}
}()
//启动go2,写chan2
go func() {
for i := 0; i < 10; i++ {
chan2 <- i
time.Sleep(time.Second)
}
}()
for {
fmt.Println("over!")
time.Sleep(5 * time.Second)
}
}