文章目录
- Go并发
-
- Go并发基础
- 主线程与go程
- 等待go程与`同步(sync)`
- go程通讯与`管道(channel)`
-
Go并发
Go的并发官方称其为goroutine
, 我们可以将它看成为一个轻量级的线程(在其他语言中常用thread
来表示线程相关的库), 他相比于其他语言的多线程更易用、更高效、更轻便。
调用Go的多线程非常简单,只需要使用关键字Go即可。但要使用好Go的多线程,实现各种功能,我们就需要Go的标准库同步(sync)与上下文(context)的配合。
同义说明
goroutine
, go程
, 轻量级线程
: 下述中我主要这些都是一个东西,都是表示go中特有的一种并发模式。
context
, ctx
, 上下文
: 都为同一种东西
Go并发基础
主线程与go程
go程是依附于主线程的, 这个依主要附体现在与主线程共存亡, 也就是说主线程一旦结束, go程无论是否执行完毕都会被结束掉。刚接触并发时非常容易出现没有调节好主线程的结束时间,导致go程还未运行结束就被迫结束的问题。
package main
import (
"fmt"
)
func 测试线程(起始, 结束 int) {
for i := 起始; i < 结束; i++ {
fmt.Println("*-*: ", i)
}
fmt.Println("--------------分割线--------------")
}
func main() {
for i := 0; i < 3; i++ {
go 测试线程(0, 2)
}
测试线程(10, 12)
}
如上代码, 从执行结果可以看出, 我们的go程只运行了两次,就会因为主进程的结束而被迫结束。(多次执行结果都会不同, 甚至可能会出现go程一次都未执行的情况)
想要解决这个问题最直接的方法,就是让主线程停下来,等等其他线程,其中最笨拙的方法无疑是直接使用睡眠函数,让线程沉睡一会。
func main() {
for i := 0; i < 3; i++ {
go 测试线程(0, 2)
}
测试线程(10, 12)
time.Sleep(1000 * time.Millisecond)
}
等待go程与同步(sync)
标准库-同步(sync),提供了一种方法可以让主线程在go程未完成前停下等待。
sync.WaitGroup
: Add(数量)
等待组添加指定等待数量; Done()
减少一个等待数量; Wait
: 停下等待等待组清零后在继续执行。
package main
import (
"fmt"
"sync"
)
var 等待组 sync.WaitGroup
func 测试线程(起始, 结束 int, go程 bool) {
if go程 {
defer 等待组.Done()
}
for i := 起始; i < 结束; i++ {
fmt.Println("*-*: ", i)
}
fmt.Println("--------------分割线--------------")
}
func main() {
for i := 0; i < 3; i++ {
等待组.Add(1)
go 测试线程(0, 2, true)
}
测试线程(10, 12, false)
等待组.Wait()
}
这样无论运行多少次, 主线程都会等待go程运行结束后在结束整个进程
go程通讯与管道(channel)
并发中还有一个让人十分头疼的问题,就是不同线程之间的变量传参问题, 解决这个问题, 主要会用到Channel(管道), 这是Go中的一个核心类型其地位类似数组(slice), 散列(map)这样的类型,使用非常便捷。
var 管道 = make(chan 类型)
var 字符管道 = make(chan string)
var 列表管道 = make(chan []string)
var 缓存整数管道 = make(chan int, 5)
使用无缓冲的管道时,必须确保在传入值时,必须先有一个以上的函数等待通道传输,否则会报如下错误fatal error: all goroutines are asleep - deadlock!
(大致意思就是说没找到相关的go程,你这样操作会产生死锁)不带缓冲的管道, 你就可以将其看为一个水管, 流入水必须要有一个以上出口, 否则是无法正常流入其中的。
func 接收管道(字符 <-chan string) {
for {
fmt.Printf("从管道传入了一个:%v\n", <-字符)
}
}
func 发送管道(字符 chan<- string) {
字符 <- "寻觅的"
}
func main() {
var 管道 = make(chan string)
go 接收管道(管道)
发送管道(管道)
管道 <- "觅库"
time.Sleep(100 * time.Millisecond)
}
都没有缓存区的管道, 如果在同一个函数内同时发送和接受就会出现报错, 因为同一个函数内执行的往往是从上到下按顺序执行的, 你在发送的同时不可能做到接收, 反之同理。如果想要缓冲池也很简单,只需要在建立通道的时候标注需要一个多大的缓冲池即可make(chan 类型, 缓冲大小)
func 双向管道(列表 chan []string) {
列表 <- []string{"测试1", "测试2"}
fmt.Println("输出双向管道", <-列表)
}
func main(){
var 列表管道 = make(chan []string, 1)
go 双向管道(列表管道)
time.Sleep(1000 * time.Millisecond)
}
关闭管道
go中的管道是可以关闭的, 使用close()
即可关闭, 不过需要注意的是go关闭管道并不是将管道彻底销毁, 而是相当于将管道的入口阀给封闭, 管道本身还是存在的, 并且如果你是一个带有缓存区的管道, 其缓存区内的数值还是能正常读取的!
func 双向管道(列表 chan []string) {
列表 <- []string{"测试3", "测试4"}
fmt.Println("输出双向管道", <-列表)
close(列表)
测试, ok := <-列表
fmt.Println(ok)
if ok {
fmt.Println(测试, ok)
}
}
func main(){
var 列表管道 = make(chan []string, 1)
go 双向管道(列表管道)
time.Sleep(1000 * time.Millisecond)
}
如上述代码所示, 关闭管道后,我们还能正常读取管道内的值, 但是无法在向管道内添加值了, 否则会报错panic: send on closed channel
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)