进程和线程说明
案例
goroutine
,该协程每隔一秒输出"Hello World"
"Hello golang"
,输出10次后退出程序goroutine
同时执行package main
import (
"fmt"
"time"
"strconv"
)
//在主线程(可以理解成进程)中,开启一goroutine,该协程每隔一秒输出"Hello World"
//在主线程中也隔每秒输出"Hello golang",输出10次后退出程序
//要求主线程goroutine同时执行
//编写一个函数,每隔1秒输出"hello world"
func test(){
for i:= 1; i<10; i++{
fmt.Println("test:hello world" + strconv.Itoa(i))
time.Sleep(time.Second)
}
}
func main(){
go test() //开启了一个协程
for i:= 1; i<10; i++{
fmt.Println("main:hello golang" + strconv.Itoa(i))
time.Sleep(time.Second)
}
}
func test()
main()
主函数(进程)中定义go 函数名(go test())
来进行1G
,M1的协程队列有三个;M2的协程有三个;M3的协程有俩个c/java的多线程
,往往是内核态的,比较重量级,几千个线程可能耗光CPU资源//获取当前系统cpu的数量
runtime.NumCPU()
//我们这里设置num-1的cpu运行go程序
runtime.GOMAXPROCS(num)
package main
import (
"fmt"
"runtime"
)
func main(){
//获取当前系统cpu的数量
num := runtime.NumCPU()
//我们这里设置num-1的cpu运行go程序
runtime.GOMAXPROCS(num)
fmt.Println("num=",num)
}
案例
package main
import (
"fmt"
)
//需求: 计算1-200的各各数的阶乘,并把各各数的阶乘放入map中,最后显示出来。
//要求使用goroutine完成
//1. 编写一个函数,来计算个数的阶乘并放入map中
//2. 我们启多个协程,统计的结果放入map中
//3. map应该做成全局的(因为都要访问到)
var (
myMap = make(map[int]int,200)
)
func test(n int){
res := 1
for i := 1; i <= n; i++{
res *= i
}
myMap[n] = res
}
func main(){
for i := 1; i < 200; i++{
go test(i)
}
for i,v := range myMap {
fmt.Printf("map[%d]=%d\n",i,v)
}
}
以上这张图说明了,我们开启了协程,但是这个协程和我们的主线程是同一时间执行的,所以,map还没有赋值,主线程就已经执行到了for i,v := range myMap
这种地方
go build -race main.go#这个也可以看到错误
package sync//锁在这个包下
package main
import (
"fmt"
"sync"
)
//需求: 计算1-200的各各数的阶乘,并把各各数的阶乘放入map中,最后显示出来。
//要求使用goroutine完成
//1. 编写一个函数,来计算个数的阶乘并放入map中
//2. 我们启多个协程,统计的结果放入map中
//3. map应该做成全局的(因为都要访问到)
var (
myMap = make(map[int]int,200)
//lock是一个全局的互斥锁;sync是一个包:synchornized 同步
//Mutex:互斥
lock sync.Mutex
)
func test(n int){
res := 1
for i := 1; i <= n; i++{
res *= i
}
//加锁
lock.Lock()
myMap[n] = res
//解锁
lock.Unlock()
}
func main(){
for i := 1; i < 200; i++{
go test(i)
}
lock.Lock()
for i,v := range myMap {
fmt.Printf("map[%d]=%d\n",i,v)
}
lock.Unlock()
}
lock.Lock()
for i,v := range myMap {
fmt.Printf("map[%d]=%d\n",i,v)
}
lock.Unlock()
这个地方加锁的原因是我们知道执行完所有的程序按理说不会有资源竞争问题,但是主线程并不知道,因此底层可能仍然会出现资源竞争,一次加入互斥锁可以解决(还是这个问题的话请加入掩饰(time.Sleep(10)
))
定义/声明
var 变量名 chan 数据类型
举例
var intChan chan int //(intChan存放int数据类型)
var mapChan chan map[int]string //(mapChan 存放map[int]string数据类型)
var perChan chan Person //(perChan 存放struct数据类型)
var perChan2 chan *Person //(perChan2 存放指针数据类型)
说明
package main
import (
"fmt"
)
func main(){
//创建一个可以存放3个int类型的管道
var intChan chan int
intChan = make(chan int, 3)
//看看intChan是什么
fmt.Printf("intChan的值:%v,intChan本身:%v",intChan,&intChan)
}
在我们内存里面(
0x00000a028
)还存放了(0xc000020080
)
var 变量名 chan int
变量名<-int类型的数据
var intChan chan int
intChan <- 10
num := 211
intChan <- num
len()
cap()
var intChan chan int
intChan = make(chan int, 3)
//看看intChan是什么
// fmt.Printf("intChan的值:%v,intChan本身:%v",intChan,&intChan)
//向管道写入数据
intChan <- 10
num := 211
intChan <- num
//查看管道的长度和cap(容量)
fmt.Printf("channel len==%v,cap=%v\n",len(intChan),cap(intChan))
变量名 := <- 管道变量(比如intChan)
var intChan chan int
intChan = make(chan int, 3)
//向管道写入数据
intChan <- 10
num := 211
intChan <- num
num3 := <- intChan
num4 := <- intChan
deadlock
num3 := <- intChan
num4 := <- intChan
num5 := <- intChan//多余,管道中只有俩个数据
fmt.Println("num3=",num3,"num4=",num4,"num5=",num5)
num3 := <- intChan
num4 := <- intChan
intChan <- 20
num5 := <- intChan
fmt.Println("num3=",num3,"num4=",num4,"num5=",num5)
<- 管道名
例如
var intChan chan int
intChan <- 10
num := 211
intChan <- num
<-intChan //取出,但是数据没有变量接收
var intChan chan int
只能存放int类型)make(chan int,3)
只能放3个,不然会报deadlock
错误)管道名<-数据
存入)(<-管道名
取出而且数据丢弃))deadlock
错误var intChan chan int
intChan = make(chan int,3)
intChan <- 10
intChan <- 20
intChan <- 10
num1 := <- intChan
num2 := <- intChan
num3 := <- intChan
fmt.Printf("num1=%v,num2=%v,num3=%v",num1,num2,num3)//10,20,10
var mapChan chan map[string]string
mapChan = make(chan map[string]string,10)
m1 := make(map[string]string,20)
m1["city"] = "浙江"
m1["name"] = "admin1"
m2 := make(map[string]string,20)
m2["sex"] = "保密"
m2["addr"] = "成都"
mapChan <- m1
mapChan <- m2
type Cat struct{
Name string
Age int
}
func main(){
var catChan chan Cat
catChan = make(chan Cat,10)
cat1 := Cat{Name:"tom",Age:18,}
cat2 := Cat{Name:"tom~",Age:30,}
catChan <- cat1
catChan <- cat2
//取出
cat11 := <- catChan
cat22 := <- catChan
fmt.Println(cat11,cat22)
}
type Cat struct{
Name string
Age int
}
func main(){
var catChan chan *Cat
catChan = make(chan *Cat,10)
cat1 := Cat{Name:"tom",Age:18,}
cat2 := Cat{Name:"tom~",Age:30,}
catChan <- &cat1
catChan <- &cat2
//取出
cat11 := <- catChan
cat22 := <- catChan
fmt.Println(cat11,cat22)
}
var allChan chan interface{}
allChan = make(chan interface{}, 10)
cat1 := Cat{Name:"tom",Age:18,}
cat2 := Cat{Name:"tom~",Age:30,}
allChan <- cat1
allChan <- cat2
allChan <- 10
allChan <- "jack"
//取出
cat11 := <- allChan
cat22 := <- allChan
v1 := <- allChan
v2 := <- allChan
fmt.Println(cat11.Name)//会报错,建议类型断言
cat11 := <- allChan
<- allChan
<- allChan
<- allChan
newCat := cat11.(Cat)//使用类型断言
fmt.Println(newCat.Name)
使用内置函数
close
可以关闭channel,当channel关闭后,就不能再向channel写数据了,但是仍然可以从channel读取数据
package builtin//close在此包里
package main
import "fmt"
func main(){
intChan := make(chan int,3)
intChan <- 100
intChan <- 200
close(intChan)//close
//这是不能够再写入数据到channel
intChan <- 300
fmt.Println("OK")
}
读是可以的
package main
import "fmt"
func main(){
intChan := make(chan int,3)
intChan <- 100
intChan <- 200
close(intChan)//close
//读取数据是可以
n1 := <- intChan
fmt.Println("n1=",n1)
}
channel支持
for-range
方式进行遍历,但是要注意俩点
- 在遍历时,如果channel没关闭,则会出现**
deadlock
错误**- 在遍历时,如果channel已经关闭,则会正常遍历数据,遍历后就会退出遍历
package main
import "fmt"
func main(){
intChan := make(chan int,20)
for i:= 1; i <= 20; i++ {
intChan <- i * 2
}
close(intChan)
for v := range intChan {
fmt.Println("v=",v)
}
}
writeData
协程,向管道intChan
中写入50个整数readData
协程,从管道intChan
中读取writeData写入的值writeData
和readData
操作的是同一个管道writeData
和readData
协程都完成工作才退出package main
import "fmt"
//WriteData
func WriteData(intChan chan int) {
for i := 0; i < 50; i++ {
//放入数据
intChan <- i
}
close(intChan) //关闭channel,并不会影响读取数据
}
//ReadData
func ReadData(intChan chan int, exitChan chan bool){
for {
v,ok := <- intChan //从管道里面读
if !ok {
break
}
fmt.Printf("readData 读到的数据=%v\n",v)
}
//readData读取完数据(任务完成)
exitChan <- true
close(exitChan)
}
func main(){
//创建俩管道
intChan := make(chan int,50)
//exitChan: 像是标志位
exitChan := make(chan bool,1)
//go func(intChan chan int){}
go WriteData(intChan)
go ReadData(intChan,exitChan)
}
以上这样的代码,会秒闪,主线程就退出了,主线程一旦退出,俩个协程也会跟着结束
所以我们可以使用time.Sleep(time.Second* 10)
,但是这不是我们所希望的
package main
import "fmt"
//WriteData
func WriteData(intChan chan int) {
for i := 0; i < 50; i++ {
//放入数据
intChan <- i
}
close(intChan) //关闭channel,并不会影响读取数据
}
//ReadData
func ReadData(intChan chan int, exitChan chan bool){
for {
v,ok := <- intChan //从管道里面读
if !ok {
break
}
fmt.Printf("readData 读到的数据=%v\n",v)
}
//readData读取完数据(任务完成)
exitChan <- true
close(exitChan)
}
func main(){
//创建俩管道
intChan := make(chan int,50)
//exitChan: 像是标志位
exitChan := make(chan bool,1)
//go func(intChan chan int){}
go WriteData(intChan)
go ReadData(intChan,exitChan)
for {
_, ok := <-exitChan
if !ok{
break
}
}
}
以上代码如果把
ReadData
注释后,你一直写,你又不取,这个管道会因为你的数据多了没有取,所以会阻塞(管道的容量<你放的数据)
如果编译器(运行)发现一个管道只有写没有读,那么该管道就会阻塞。
写管道和读管道的频率不一致(无所谓)
//声明为只写
var chan1 chan<-int
chan1 = make(chan<-int,3)
//声明只读
var chan2 <- chan int
num2 := <-chan2 //会报错因为没有make
最佳案例
//只写
func send(ch chan<-int){}
//只读
func recv(ch <- chan int){}
select
可以解决从管道取数据的阻塞问题select {
case v := 管道:
语句
...
default:
语句
}
intChan := make(chan int,10)
for i := 0; i<10; i++ {
intChan <- i
}
//传统方法在遍历管道时,如果不关闭会阻塞导致deadlock
//要是遇到的需求不确定什么时候关闭管道
//可以用select方式解决
for {
select {
//注意:这里如果管道一直没有关闭,不会一直阻塞而死锁
//会自动到下一个case匹配
case v:=<-intChan:
fmt.Printf("从intChan读取的数据%d\n",v)
default:
//可以加入任务逻辑
break
}
}
recover
,解决协程出现的pina
,导致程序崩溃问题func test(){
defer func(){
if err := recover();err != nil {
fmt.Println("test()发生了错误",err)
}
}
}