“go语言进阶与依赖管理” 并发-goroutine-channe-lock-WaitGroup
1. 并发——并行
并发:多线程程序在同一核的CPU运行
并行:多线程程序在多个核的CPU运行
协程:用户态,轻量级线程,由go调度控制,KB级的栈
线程:内核态,跑多个协程,MB级的栈
- 线程切换需要陷入内核,然后进行上下文切换,而协程在用户态由协程调度器完成
- 协程的切换时间点是由调度器决定,而不是由系统内核决定的
- 垃圾回收的必要条件是内存位于一致状态,需要暂停所有的线程。对于Go语言来说,调度器知道什么时候内存位于一致状态,所以也就没有必要暂停所有运行的线程
- Go的协程采用了动态扩张收缩的策略,初始化为2KB
1.1 goroutine
每一个并发的执行单元叫作一个goroutine
使用goroutine快速打印
func hello(i int) {
println("hello: " + fmt.Sprint(i))
}
func helloGoroutine() {
for i := 0; i < 5; i++ {
go func(input_i int) {
hello(input_i)
}(i) //i参数
}
time.Sleep(time.Second) //1s后 保证子协程执行完之前主协程不要退出
fmt.Printf("end")
} //打印乱序 并行
运行结果如下图。可以看到输出是随机顺序的,输出任务是并行的。
1.2 CSP
通过内存共享实现通信
通过通信实现内存共享(推荐)
- 不要通过共享内存来实现通信,而是通过通信来实现共享内存。
1.3 channel/chan
管道,多用于多个 goroutine 之间通信,默认情况下,通道是双向的,这意味着goroutine可以通过同一通道发送或接收数据
make(chan 元素类型,\[缓冲大小])
- 无缓冲通道
make(chan int)同步通道,发送接收同步(发送者会阻塞直到接收者接收了发送的值)。如果连续向一个无缓冲channel 发送2个元素,并且没有接收的话,第二次一定会被阻塞 - 有缓冲通道
make(chan int,2)发送方在缓冲区满的情况下阻塞,接收方在缓冲区空的情况下阻塞,所以是“异步”的。 - 关闭不再需要使用的 channel 并不是必须的。跟其他资源比如打开的文件、socket 连接不一样,这类资源使用完后不关闭后会造成句柄泄露,channel 使用完后不关闭也没有关系,channel 没有被任何协程用到后最终会被 GC 回收。关闭 channel 一般是用来通知其他协程某个任务已经完成了。
func CalSquare() {
src := make(chan int) //无缓冲通道
dest := make(chan int, 3) // 有缓冲
go func() { //子协程A发送0~9
defer close(src)
for i := 0; i < 10; i++ {
src <- i //发给src channel
}
}()
go func() { //协程B计算数字的平方
defer close(dest)
for i := range src { //遍历src
dest <- i * i
}
}()
for i := range dest { //主协程输出平方结果
println(i)
}
}
子协程A发送0~9,子协程B计算数字的平方,主协程输出平方结果。
无缓冲通道保证了输出的顺序,并发安全。有缓冲通道解决了生产消费效率问题。
1.4 lock
将变量执行2000次+1,5个协程并发执行
var (
x int
lock sync.Mutex
)
func addLock() {
for i := 0; i < 2000; i++ {
lock.Lock() //加锁
x += 1
lock.Unlock() //解锁
}
}
func add() {
for i := 0; i < 2000; i++ {
x += 1
}
}
func main() {
x = 0
for i := 0; i < 5; i++ {
go add()
}
time.Sleep(time.Second)
fmt.Println("no lock:", x)
x = 0
for i := 0; i < 5; i++ {
go addLock()
}
time.Sleep(time.Second)
fmt.Println("lock:", x)
}
线程同步时,可能会有多个线程需要使用这个资源,为了避免资源竞争,我们需要锁机制。无锁的共享内存可能产生安全问题。
1.5 WaitGroup
sync.WaitGroup结构体对象用于等待一组线程的结束,可以通过WaitGroup来表达这一组协程的任务是否完成,以决定是否继续往下走,或者取任务结果;
计数器:协程Add(delta int)内部计数器加上delta,delta可以是负数,Done()结束-1;Wait()将主协程阻塞到计数器为0
type WaitGroup struct {
noCopy noCopy
state1 [3]uint32
}
func goWait() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(input_i int) {
defer wg.Done()
hello(input_i)
}(i)
}
wg.Wait()
fmt.Println("end")
}
23/7/27