go程
补充: 每当有一个进程启动时,系统会自动打开三个文件:
标准输入: stdin
标准输出:stdout
标准错误:stderr
当运行结束,操作系统会自动关闭着三个文件
import (
"fmt"
"time"
)
func foo() {
fmt.Println("foo ")
time.Sleep(time.Microsecond * 100)
}
func fun() {
fmt.Println("fun ")
time.Sleep(time.Microsecond * 100)
}
func main() {
go foo()
go fun()
// main 为主go程, 若main运行结束,那么其内部产生的子go程也随即结束
for {
;
}
}
runtime.Gosched()
出让go程运行的cpu权限,当下次再拿到cpu执行权限后,接着Gosched出让的位置继续向后执行代码
import (
"fmt"
"runtime"
"time"
)
func foo() {
for {
fmt.Println("foo ")
} //time.Sleep(time.Microsecond * 100)
}
func main() {
go foo()
for {
runtime.Gosched() // 出让cpu执行权限
fmt.Println("main")
}
}
runtime.Goexit()
立即停止当前的go程,调度器确保所有已注册的defe延迟调用被执行
import (
"fmt"
"runtime"
"time"
)
func test() {
defer fmt.Println("bbb")
//return // 结束函数
runtime.Goexit() // 结束go程,这里结束的是 foo() go程
defer fmt.Println("ddd") // 与return一样,此处定义的defer不会被执行
}
func foo() {
for {
fmt.Println("aaa ")
test()
fmt.Println("cccc")
} //time.Sleep(time.Microsecond * 100)
}
func main() {
go foo()
for {
;
}
}
>>>:
aaa
bbb
runtime.GOMAXPROCS()
用来设置可以并行计算的cpu核数最大值,并返回之前的值
func main() {
n := runtime.GOMAXPROCS(1)
fmt.Println(n)
n = runtime.GOMAXPROCS(2)
fmt.Println(n)
for {
go fmt.Print(0)
fmt.Print(1)
;
}
}
更多runtime包详情
channel
管道,并发核心单元通过它就可以发送或者接收数据进行通讯,这在一定程度上进一步降低了变成的难度
特点: channel是一种数据类型,用来解决协程的同步问题,以及协程之间的数据共享问题
goroutine运行在相同的地址空间因此访问共享内存必须做好同步,goroutine奉行通过通信来共享内存,而不是共享内存来通信
channel是引用类型,是个队列,可用于多个goroutine通信,其内部实现的同步,确保并发安全
定义
与map类似,channel也对应一个make创建的地城数据结构的引用,和其他引用类型一样,channel的零值也是nil
ch := make(chan channel中传递的数据类型, channel容量)
容量 = 0: 无缓冲channel 特点:具备同步的能力,常用于同步
容量 > 0: 有缓冲channel 特点:channel内可以存储内容,达到容量上限后才阻塞,常用于异步
注意:
channel有读端和写端,少了任何一端都会死锁报错
同一个go程内只能拥有同一个channel的一端(或读端,或写端)
说明:
同步: 在两个过多个携程(线程)间,保持数据内容一致性的机制
无缓冲channel
利用channel 处理go程对于共享数据的同步问题
func main() {
ch := make(chan string)
fmt.Println(len(ch), cap(ch)) // len(ch): channel中剩余内容的数量, cap(ch):channel的容量
go func() {
for i:=0;i<5;i++{
fmt.Println(i)
}
ch <- "go程结束" // 写端
}()
str := <-ch
fmt.Println(str) // 读端
}
案例:
import (
"fmt"
"time"
)
func test(str string) {
for _, s := range str{
//fmt.Println(s) // 打印的是字节
fmt.Printf("%c",s)
time.Sleep(time.Millisecond * 100)
}
}
// c chan <- int :channel的写端
func foo(str string, c chan <- int) {
test(str)
c <- 1 // 当此处的写端写入内容后,对应的读端会立即读到内容,运行起来
}
// c chan <- int : channel的读端
func fun(str string, c <- chan int) {
<- c // 此时channel的读端是阻塞状态,只有写端写入内容,读端才会继续运行
test(str)
}
func main() {
// 主go程中定义,子go程中使用
// 利用channel的阻塞特点,当channel中没有内容时,就发生阻塞
ch := make(chan int) // 无缓冲channel
go foo("hello", ch)
go fun("world", ch)
for {
;
}
}
有缓冲channel
当容量慢了之后阻塞,多用于并发
func main() {
ch := make(chan int, 3)
fmt.Println(len(ch), cap(ch)) // channel 长度和容量
go func() {
for i:=0;i<8;i++{
ch <- i // 写端
fmt.Printf("子go程 i=%d \n", i)
}
}()
for i:=0;i<8;i++{
num := <-ch
fmt.Printf("main i=%d \n", num) // 读端
}
}
关闭channel
确定不再向对端发送或接收数据时,就可关闭channel
close(channel) // 大多数用来关闭发送端
注意:
channel不像文件一样需要经常去关闭,只有当你确实没有任何发送的数据了,或者你想显式的结束rang循环之类的,才去关不channel
-
关闭channel后,无法向channel 再发送数据(引发 panic 错误后导致接收立即返回零值);
-
关闭channel后,可以继续从channel接收数据;
-
对于nil channel,无论收发都会被阻塞
-
已经关闭的channel,不能再向其写数据,否则报错
-
写端已经关闭的channel,可以从中继续读数据,将数据读出后,再继续读,读到0
对端可以判断channel是否关闭
if num, ok := <- ch ; ok == ture{}
如果对端已经关闭, ok == false num无数据
如果对端没关闭, ok == true num是取到的数据
func main() {
ch := make(chan int, 2)
fmt.Println(len(ch), cap(ch)) // channel 长度和容量
go func() {
for i:=0;i<5;i++{
ch <- i // 写端
fmt.Printf("子go程 i=%d \n", i)
}
close(ch)
}()
time.Sleep(time.Millisecond * 100)
for {
if num, ok := <-ch; ok==true{
fmt.Printf("main i=%d \n", num) // 读端
} else {
break
}
}
}
可以使用 range 来迭代不断操作channel
package main
import (
"fmt"
)
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
//把 close(c) 注释掉,程序会一直阻塞在 for data := range c 那一行
close(c)
}()
// 类似循环 <- c , range能发现对端是否关闭,对端关闭后,退出循环
for data := range c {
fmt.Println(data)
}
fmt.Println("Finished")
}
单向channel
默认情况下,通道channel是双向的,也就是,既可以往里面发送数据也可以同里面接收数据。
但是,我们经常见一个通道作为参数进行传递而值希望对方是单向使用的,要么只让它发送数据,要么只让它接收数据,这时候我们可以指定通道的方向。
// ch1是一个正常的channel,是双向的
var ch1 chan int ch := make(chan int)
// ch2是单向channel,只用于写float64数据
var ch2 chan<- float64 ch2 := make(chan <- folat64)
// ch3是单向channel,只用于读int数据
var ch3 <-chan int ch3 := make(<- chan int)
转换:
双向channel可转换为单向channel, 前提channel内的数据类型要一致
单向channel不能转换为双向channel
c := make(chan int, 3)
var send chan<- int = c // send-only
var recv <-chan int = c // receive-only
send <- 1
//<-send //invalid operation: <-send (receive from send-only type chan<- int)
<-recv
//recv <- 2 //invalid operation: recv <- 2 (send to receive-only type <-chan int)
//不能将单向 channel 转换为普通 channel
d1 := (chan int)(send) //cannot convert send (type chan<- int) to type chan int
d2 := (chan int)(recv) //cannot convert recv (type <-chan int) to type chan int
channel死锁
- 单go程死锁: channel至少在两个go程里通信
- go程间channel访问顺序导致死锁: 读写端要同时有机会运行,并且读端要在写之前运行
- 多go程交多channel叉死锁:即 互相等待,A go程拿着ch1的时候尝试拿ch2,B go程拿着ch2的时候尝试拿ch1
- 锁(互斥锁,读写锁)与channel混用, 锁与channel互相等待-----隐性死锁
案例:
var rwMut sync.RWMutex // 锁只有一把!!!!有读写两种属性
func readGo(in <-chan int, i int) {
for{
rwMut.RLock() // 加读锁 问题原因: readGo拿到锁, 并等待channel读数据
num:= <-in
fmt.Printf("%d th , read %d \n", i, num)
rwMut.RUnlock() // 解读锁
}
}
func writeGo(out chan<- int, i int, qu chan <- int) {
for q := 1;q < 500; q++{
if q == 3{
qu <- 1
}
num := rand.Intn(1000)
rwMut.Lock() // 加写锁 问题原因: writeGo等待拿锁后向channel中写入数据
out <- num
fmt.Printf("%d th , write %d \n", i, num)
time.Sleep(time.Millisecond * 100)
rwMut.Unlock() // 解写锁
}
}
func main() {
quit := make(chan int)
ch := make(chan int)
rand.Seed(time.Now().UnixNano())
for i:=1;i < 5 ;i++ {
go readGo(ch, i+1)
}
for i:=1;i < 5 ;i++ {
go writeGo(ch, i+1, quit)
}
<- quit
}
select
用来监听channel中的数据流动
select与switch语句相似,select中每个case语句必须是一个IO操作
select {
case <- ch1:
// 如果ch1成功读到数据,则执行case代码块内的代码
case ch2 <- 5:
// 如果成功向ch2写数据,则执行case代码块内的代码
default:
//如果上面的都没有执行,就进入defer的流程
}
注意:
-
如果给出了
default
语句,那么就会执行default
语句;select本身不带循环机制 -
如果没有
default
语句,那么select
语句将被阻塞,直到至少有一个通信可以进行下去;
func main() {
ch := make(chan int)
exit := make(chan bool)
go func() {
time.Sleep(time.Millisecond * 100)
ch <- 1
exit <- true
}()
tag:for {
select {
case <- ch:
fmt.Println("select ch")
case <- exit:
fmt.Println("select exit")
break tag // 注意: break 跳出的是select,为了跳出for循环,此处设置标签
// return 也可以属于return整体跳出
default:
time.Sleep(time.Millisecond * 50)
fmt.Println("defer")
//如果上面的都没有执行,就进入defer的流程
}
}
}
>>>:
defer
defer
select ch
select exit