一、并发编程
1.Goroutine(协程)
goroutine 是一种轻量级的线程(线程是栈mb级别的, 而协程是栈kb级别的), 可以与其他 goroutine 运行在相同的空间地址,易于创建而且可以在几纳秒内启动和销毁。
一个goroutine的示例:
package main
import "time"
func hello(i int) {
println("goroutine: ", i)
}
func main() {
for i := 0; i < 5; i++ {
go hello(i)
}
time.Sleep(time.Second)
}
我们使用go语句将后面的函数在一个新创建的goroutine中运行, go语句本身会立即完成。
当一个程序启动时,其主函数会在一个单独的goroutine中运行,我们叫它main goroutine。主函数返回时,所有的goroutine都会被直接打断,程序退出。
goroutine的运行顺序和时间是不确定的, 执行两次程序:
会发现两次执行的结果不一样, 这也是为什么在示例中需要让
main函数调用time.Sleep(time.Second), 这保证了其他的协程都能成功运行。如果将其注释掉再次运行, 可能会出现下面的情况:
main函数提前退出了, 有一些goroutine没有执行就被直接中断了。
2.Channel
goroutine是一种轻量级的线程, 所以也可以共享资源。在go中提倡使用通信来共享内存而不是通过共享内存来实现通信
在
go中使用channel来实现不同goroutine之间的数据共享
创建channel, go中提供了两种类型的通道
make(chan int) // 创建一个无缓冲的int类型通道
make(chan int, 3) // 创建一个有缓冲的int类型的通道, 一次最多可以存储3个数据
使用channel的例子:
package main
func main() {
src := make(chan int)
dest := make(chan int, 3)
// 发送 0 - 9 数字
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
// 从src中接收数据, 计算数字的平方
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
// 打印结果
for i := range dest {
println(i)
}
}
运行结果:
当一个值传入到一个chan中而没有被其他goroutine接收时, 就会进入阻塞状态, 阻止当前goroutine代码的进一步运行, 直到其值被接收。当一个goroutine从chan中接收值,而该chan中没有数据时也会进入阻塞状态
在案例中src和dest可以看作生产者和消费者, 而消费者的逻辑一般比生产者复杂, 运行时间慢, 为了避免生产者(src)发生阻塞, 消费者(dest)中使用的是有缓冲的chan, 一次可以存储多个数据, 这样就可以减少src发生阻塞的时间, 提高执行效率
3. 并发安全 Lock
chan是并发安全的, 在go也可以使用互斥锁(Mutex)来实现并发安全
package main
import (
"fmt"
"sync"
"time"
)
var (
lock sync.Mutex
x int64
)
func addWithLock() {
for i := 0; i < 1000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func main() {
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
fmt.Println("x = ", x)
}
上面程序的addWithLock()对x进行1000次+1操作, 启动五个协程并发执行
结果:
结果说明使用Mutex可以保证并发安全
4.WaitGroup
在第一个例子中, 因为goroutine的执行顺序和执行时间都不确定, 所以我们在main中要调用time.Sleep, 但不是每次我们设置的时间都能保证所有的goroutine执行完毕, 这时我们使用WaitGroup就可以解决这个问题
WaitGroup的三个方法:
Add- 设置计数器的数目 (goroutine的数量)Done- 当一个goroutine执行完毕时, 计数器 - 1Wait- 阻塞直到计数器为0
修改第一个例子:
package main
import (
"sync"
)
var wg sync.WaitGroup
func hello(i int) {
println("goroutine: ", i)
}
func main() {
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
运行结果:
二、总结
以上就是今天学习的go中并发编程的基础知识。本人是初学者, 所以文章中一些表述可能会有错误。如果您发现了文章中的错误或有任何改进建议,欢迎在评论区留言,我会认真考虑并及时修改, 非常感谢🥰
最后, 如果本文写得还可以, 帮我点个免费的赞👍(关注更好🥰