在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
主要内容: 1.Goroutine 1. Goroutine Go 协程(Goroutine)(轻量级的线程,开线程没有数量限制)。 例如:ngix是多进程的单线程程序 内核线程、轻量级进程、用户线程三种线程概念详见:https://blog.csdn.net/gatieme/article/details/51481863 (2)并发和并行 A. 并发是指立即处理多个任务的能力。多线程程序在一个核的cpu上运行(线程之间通过CPU轮询来执行),就是并发。go多线程的切换都是在用户态操作的,不像其他语言先切换到内核态,完成线程切换,然后返回用户态继续执行程序。 Go 编程语言原生支持并发。Go 使用 Go 协程(Goroutine) 和信道(Channel)来处理并发。
注意:并行不一定会加快运行速度,因为并行运行的组件之间可能需要相互通信。在我们浏览器的例子里,当文件下载完成后,应当对用户进行提醒,比如弹出一个窗口。于是,在负责下载的组件和负责渲染用户界面的组件之间,就产生了通信。在并发系统上,这种通信开销很小。但在多核的并行系统上,组件间的通信开销就很高了。所以,并行不一定会加快运行速度! 补充:用户线程指的是完全建立在用户空间的线程库,用户线程的建立,同步,销毁,调度完全在用户空间完成,不需要内核的帮助。因此这种线程的操作是极其快速的且低消耗的。 (3)协程和线程 Go 协程相比于线程的优势:
GO语言Goroutine与线程的区别:https://baijiahao.baidu.com/s?id=1620972759226100794&wfr=spider&for=pc (4)goroutine调度模型 M 代表内核级线程,一个M就是一个线程,goroutine就是跑在M之上的。 如果有IO操作时,会新起一个线程等待IO操作的Goroutine
Go scheduler: https://www.jianshu.com/p/1911b1229a44 启动一个go协程? 1 package main 2 3 import "fmt" 4 5 func test_go() { 6 fmt.Println("hello world") 7 } 8 9 func main() { 10 go test_go() 11 fmt.Println("main func finished") 12 } 执行结果:
分析:发现起的go协程go test_go()并没有生效,只打印出 hello world,这是由于,启动一个新的协程时,协程的调用会立即返回。与函数不同,程序控制不会去等待 Go 协程执行完毕。下面使用sleep使主线程处于睡眠之中等待go协程执行结束(实际中该方法不靠谱)。后面会介绍靠谱的方法。 1 package main 2 3 import ( 4 "fmt" 5 "time" 6 ) 7 func test_go() { 8 fmt.Println("hello world") 9 } 10 11 func main() { 12 go test_go() 13 time.Sleep(time.Second) 14 fmt.Println("main func finished") 15 } 执行结果:
启动多个go协程? 1 package main 2 3 import ( 4 "fmt" 5 "time" 6 ) 7 8 func numbers() { 9 for i := 1; i <= 5; i++ { 10 time.Sleep(250 * time.Millisecond) 11 fmt.Printf("%d ", i) 12 } 13 } 14 func alphabets() { 15 for i := 'a'; i <= 'e'; i++ { 16 time.Sleep(400 * time.Millisecond) 17 fmt.Printf("%c ", i) 18 } 19 } 20 func main() { 21 go numbers() 22 go alphabets() 23 time.Sleep(3000 * time.Millisecond) 24 fmt.Printf("\nmain terminated") 25 } 读者可以自行分析该程序的时间片打印输出。 (5)如何设置golang运行的cpu核数 1 package main 2 3 import ( 4 "fmt" 5 "runtime" 6 ) 7 8 func main() { 9 num := runtime.NumCPU() //查看有几个内核 10 fmt.Printf("cpu num:%d\n", num) 11 runtime.GOMAXPROCS(1) //设置有程序用几个内核执行 12 } (6)不同goroutine之间进行通讯 A:全局变量和锁同步 1 package main 2 3 import ( 4 "fmt" 5 "sync" 6 "time" 7 ) 8 9 var ( 10 m = make(map[int]uint64) 11 lock sync.Mutex 12 ) 13 14 type task struct { 15 n int 16 } 17 18 func calc(t *task) { 19 var sum uint64 20 sum = 1 21 for i := 1; i < t.n; i++ { 22 sum *= uint64(i) 23 } 24 25 fmt.Println(t.n, sum) 26 lock.Lock() //加锁,不然多个协程修改全局变量会存在竞争 27 m[t.n] = sum 28 lock.Unlock() 29 } 30 31 func main() { 32 for i := 0; i < 16; i++ { 33 t := &task{n: i} 34 go calc(t) 35 } 36 37 time.Sleep(10 * time.Second) 38 lock.Lock() 39 for k, v := range m { 40 fmt.Printf("%d! = %v\n", k, v) 41 } 42 lock.Unlock() 43 } B:Channel 1 package main 2 3 import ( 4 "fmt" 5 "time" 6 ) 7 8 func write(ch chan int) { 9 for i := 0; i < 100; i++ { 10 ch <- i 11 fmt.Println("put data:", i) 12 } 13 } 14 15 func read(ch chan int) { 16 for { 17 var b int 18 b = <-ch 19 fmt.Println(b) 20 time.Sleep(time.Second) 21 } 22 } 23 24 func main() { 25 intChan := make(chan int, 10) 26 go write(intChan) 27 go read(intChan) 28 29 time.Sleep(10 * time.Second) 30 } (7)goroutine中使用recover 如果某个goroutine出现panic,为了不使程序崩溃挂掉,可以在该goroutine中使用recover(类似于python中的try……except)捕获该panic。 1 package main 2 3 import ( 4 "fmt" 5 "time" 6 ) 7 8 func test() { 9 defer func() { //defer必须放置在最前面,才能捕获后面所有的panic,程序退出时执行defer 10 err := recover() //捕获goroutine错误 11 if err != nil { 12 fmt.Println(err) 13 } 14 }() 15 16 var p *int 17 *p = 20 //panic 18 } 19 20 func main() { 21 go test() 22 time.Sleep(time.Second) 23 fmt.Println("main progress exit") 24 } 1 package main 2 3 import ( 4 "fmt" 5 "runtime" 6 "time" 7 ) 8 9 func test() { 10 11 defer func() { 12 if err := recover(); err != nil { //处理panic,calc依然可以正常执行 13 fmt.Println("panic:", err) 14 } 15 }() 16 17 var m map[string]int //panic: assignment to entry in nil map 18 m["stu"] = 100 19 } 20 21 func calc() { 22 for { 23 fmt.Println("i'm calc") 24 time.Sleep(time.Second) 25 } 26 } 27 28 func main() { 29 num := runtime.NumCPU() 30 runtime.GOMAXPROCS(num - 1) 31 go test() 32 for i := 0; i < 2; i++ { 33 go calc() 34 } 35 36 time.Sleep(time.Second * 10000) 37 } 2. 信道(Channel) Channel 可以想像成 Go 协程之间通信的管道。如同管道中的水会从一端流到另一端,通过使用信道,数据也可以从一端发送,在另一端接收。 (1)channel概念
(2) channel声明 var 变量名 chan 类型,例如: var test chan int var test chan string var test chan map[string]string var test chan stu //stu是一个结构体 注意:所有信道都关联了一个类型。信道只能运输这种类型的数据,而运输其他类型的数据都是非法的。 1 package main 2 3 import "fmt" 4 5 func main() { 6 var ch chan int 7 // ch = make(chan int, 1) 8 ch<-1 9 var num int 10 num = <-ch 11 fmt.Println(num) 12 } 运行结果:出现死锁
(3)channel初始化 使用make进行初始化,例如: var test chan int test = make(chan int, 10) var test chan string test = make(chan string, 10) 上面的程序声明了信道但是未初始化,去掉上面程序的注释初始化信道,执行结果:输出1 1 package main 2 3 import "fmt" 4 5 func main() { 6 var ch chan int 7 if ch == nil { 8 fmt.Println("channel a is nil, going to define it") 9 ch = make(chan int) 10 fmt.Printf("Type of a is %T", ch) 11 } 12 } 快速声明一个信道: ch := make(chan int)
(4)channel基本操作 信道旁的箭头方向指定了是发送数据还是接收数据
var testChan chan int
var testChan chan int 对于无缓冲信道的发送和接收过程是阻塞的。而对于有缓冲信道,只在缓冲已满的情况,才会阻塞向缓冲信道(Buffered Channel)发送数据。同样,只有在缓冲为空的时候,才会阻塞从缓冲信道接收数据。 ch := make(chan type, capacity) 有缓冲信道:capacity 应该大于 0 无缓冲信道:capacity为0,或者不设置capacity则容量默认也为 0
var testChan chan int
var testChan chan int 1 package main 2 3 import ( 4 "fmt" 5 ) 6 7 func main() { 8 chStr := make(chan string, 2) 9 chStr <- "zhangsan" 10 chStr <- "lisi" 11 fmt.Println(<-chStr) 12 fmt.Println(<-chStr) 13 14 chInt := make(chan int, 2) 15 chInt <- 10 16 chInt <- 20 17 fmt.Println(<-chInt) 18 fmt.Println(<-chInt) 19 } 1 package main 2 3 import ( 4 "fmt" 5 "time" 6 ) 7 8 func write(ch chan int) { 9 for i := 0; i < 5; i++ { 10 ch <- i //当存入第三个数时会阻塞住,直到信道ch里面有数据被取走 11 fmt.Printf("Write %d to ch\n", i) 12 } 13 close(ch) //关闭信道ch 14 } 15 func main() { 16 ch := make(chan int, 2) //信道一次最多存入两个数 17 go write(ch) 18 time.Sleep(2 * time.Second) //等待 19 for v := range ch { 20 fmt.Printf("read value %d from ch\n", v) 21 time.Sleep(time.Second) 22 23 } 24 } 25 26 // 执行结果: 27 // Write 0 to ch 28 // Write 1 to ch //先往信道里面写入两个数,阻塞 29 // read value 0 from ch //取走一个 30 // Write 2 to ch //立即往信道写入一个数 31 // read value 1 from ch 32 // Write 3 to ch 33 // read value 2 from ch 34 // Write 4 to ch 35 // read value 3 from ch 36 // read value 4 from ch 1 package main 2 3 import "fmt" 4 5 type student struct { 6 name string 7 } 8 9 func main() { 10 11 var stuChan chan interface{} 12 stuChan = make(chan interface{}, 10) 13 14 stu := student{name: "stu01"} 15 16 stuChan <- &stu 17 18 var stu01 interface{} 19 stu01 = <-stuChan 20 21 var stu02 *student 22 stu02, ok := stu01.(*student) //stu01转为*student类型 23 if !ok { 24 fmt.Println("can not convert") 25 return 26 } 27 28 fmt.Println(stu02) 29 } 1 package main 2 3 import ( 4 "fmt" 5 "sync" 6 "time" 7 ) 8 9 var wg sync.WaitGroup 10 11 func consumer(goods chan string) { 12 for i := 0; i < 10; i++ { 13 g, ok := <-goods 14 if !ok { 15 fmt.Println("produce done ", g) 16 } 17 fmt.Println("consumer ", g) 18 time.Sleep(20*time.Millisecond) 19 } 20 21 wg.Done() 22 } 23 24 func produce(goods chan string) { 25 for i := 0; i < 10; i++ { 26 g := fmt.Sprintf("baozi%d", i) 27 goods <- g 28 fmt.Println("produce ", g) 29 time.Sleep(10*time.Millisecond) 30 } 31 close(goods) //生产完毕 32 33 wg.Done() 34 } 35 36 func main() { 37 var goods chan string 38 goods = make(chan string, 10) 39 40 wg.Add(2) 41 go produce(goods) 42 go consumer(goods) 43 44 wg.Wait() 45 } 1 package main 2 3 import ( 4 "fmt" 5 "time" 6 ) 7 8 func write(ch chan int) { 9 for i := 0; i < 100; i++ { 10 ch <- i 11 fmt.Println("put data:", i) 12 } 13 } 14 15 func read(ch chan int) { |
请发表评论