Golang学习之路6-goroutine并发
@
目录
前言
什么是 goroutine?简称可以使:go程、并发
- goroutine是与其他函数或方法同时运行的函数或方法。
- goroutine可以被认为是轻量级线程,天生支持多并发。
- 与线程相比,创建goroutine的成本很小,因此Go 应用程序通常会同时运行数千个goroutine。
一、goroutine用法
go语言天生支持多并发,使用方式:
- 方式一:go func()
- 方式二(匿名):go func(){}()
package main
import (
"fmt"
"time"
)
func display(count int) {
for {
fmt.Println("这是子go程======>:", count)
time.Sleep(1)
count++
}
}
func main() {
go display(1)
// 匿名 子go程 函数
//go func () {
// count := 1
// for {
// fmt.Println("这是子go程======>:", count)
// time.Sleep(1)
// count++
// }
//}()
// 主go程
count := 1
for {
fmt.Println("这是主go程:", count)
count++
time.Sleep(1)
if count > 2 {
break
}
}
}
可以看到,主进程没有打印第3次,而子进程打印了第3次
二、goroutine循环
比如起3个进程跑goroutine,cpu资源会被竞争不确定谁先谁后:
for i := 0; i <= 3; i++ {
go func() {}()
}
三、goroutine提前退出
三种退出go程方式:
- return:只退出当前子go程函数
- runtime.Goexit():只退出当前go程
- os.Exit(-1):退出所以的主子go程
package main
import (
"fmt"
"os"
"time"
)
func main() {
go func() {
func() {
fmt.Println("这是子go程 内部函数111")
//return // 只结束此函数,会执行到:子go程222
//runtime.Goexit() // 退出当前go程,不会执行到:子go程222
os.Exit(-1) // 退出所以主子进程
}()
fmt.Println("子go程222")
}()
time.Sleep(1)
fmt.Println("结束主go程!")
}
四、goroutine双向管道
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
ch <- value // 把 value 发送到通道 ch
value := <-ch // 从 ch 接收数据,并把值赋给 value
chan双向管道:
- 使用通讯时不需要上锁解锁;
- 未创建容量时是无缓冲的,反之是有缓冲的;
- 当缓冲区写满时或读取完成时会阻塞,当被读取后再恢复写入;
- 管道的读与写次数必须对等,否则会被堵塞,①如果阻塞主go程那么程序被锁死至崩溃,②如果阻塞在子go程会出现内存泄漏;
- 需要使用make分配空间,否则管道默认是nil,读写都会堵塞(和map一样所以建议都是make);
- 使用goroutine管道时推荐使用for range,当管道写入完成后应关闭close(chan)
package main
import (
"fmt"
"time"
)
func main() {
// 创建一个容量为10的int有缓冲管道
numChan := make(chan int, 10)
// 写“管道”数据
go func() {
for i := 1; i < 21; i++ {
numChan <- i
fmt.Println("11-->子go程写入数据:", i)
}
// 写入结束,关闭管道
close(numChan)
}()
// 读“管道”数据
go func() {
for num := range numChan {
fmt.Println("22-->子go程读取数据data:", num)
}
}()
time.Sleep(1 * time.Second)
// 判断管道是否关闭
for {
v, ok := <-numChan
if !ok {
fmt.Println("管道已经关闭,准备退出!!")
break
}
fmt.Println("v:", v)
}
}
五、goroutine单向管道
一般用于函数参数,比如只读、只写单向管道,看看下面和双向管道有何区别?
生产者:chan<-
消费者:<-chan
双向管道时都是:<-numChan
package main
import (
"fmt"
"time"
)
// 生产者:只写入数据
func producer(out chan<- int) {
for i := 1; i < 11; i++ {
out <- i
fmt.Println("写入数据:", i)
}
close(out)
}
// 消费者:只读数据
func consumer(in <-chan int) {
for v := range in {
fmt.Println("读取数据:", v)
}
}
func main() {
// 创建一个双向通道
numChan := make(chan int, 5)
// 创建生产者
go producer(numChan)
// 创建消费者
go consumer(numChan)
time.Sleep(200)
}
六、监听管道
在多go程并发时,我们可以通过select进行监听到对应数据,突然觉得类似消息事件一样,一个负责推、一个负责监听...
select:监听多管道 (写入/读写数据、关闭管道)
package main
import (
"fmt"
"time"
)
func main() {
// 创建两个管道,用来测试监听
chan1 := make(chan int)
chan2 := make(chan int)
// 监听go程
go func() {
for {
select {
case data1 := <-chan1:
fmt.Println("监听到chan111:", data1)
case data2 := <-chan2:
fmt.Println("监听到chan222:", data2)
default:
fmt.Println("正常监听中...")
time.Sleep(2 * time.Second)
}
}
}()
// go程:两个管道分别写入数据
go func() {
for i := 0; i < 5; i++ {
chan1 <- i
i++
chan2 <- i
}
}()
count := 1
for {
time.Sleep(10 * time.Second)
if count == 6 {
break
}
count++
}
}
如下图,可以看到当我们监听到有写入数据时会得到对应的类型数据,当没有写入时 default 一直在负责监听!
总结
以上就是今天学习的内容,本文仅仅简单介绍了goroutine的使用,而在项目中如何实践还请大家多多查阅资料了解!