这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
一、主要内容
- 并发编程
- 协程Goroutine
- 通道Channel
- 锁和并发安全
- 并发安全Lock
二、内容详解
并发编程
- 并发和并行的直观理解
任务一和任务二并发执行
任务一和任务二并行执行
- 协程与线程的比较
协程 : 用户态,轻量级,栈KB级别
线程 : 内核态,一个线程可以跑多个协程,栈MB级别
package main
import (
"fmt"
"time"
)
func main() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(i)
}(i)
}
time.Sleep(time.Second)
}
func hello(i int) {
fmt.Printf("Hello go routine %v", i)
}
输出结果乱序
- Channel
Channel是Goroutine之间的通信机制:
创建通道实例:
通道实例 := make(chan 数据类型)
通过通道发送和接收数据:
通道变量 <- 值
使用通道做并发同步的示例程序:
package main
import (
"fmt"
)
func main() {
// 构建一个通道
ch := make(chan int)
// 开启一个并发匿名函数
go func() {
fmt.Println("start goroutine")
// 通过通道通知main的goroutine
ch <- 0
fmt.Println("exit goroutine")
}()
fmt.Println("wait goroutine")
// 等待匿名goroutine
<-ch
fmt.Println("all done")
}
- 并发安全Lock
当多个协程对同一个数据项进行操作,应当对该数据项进行加锁,否则会因为冲突产生错误。
package main
import (
"fmt"
"sync"
"time"
)
func main() {
add()
}
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x += 1
}
}
func add() {
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
fmt.Println("addWithLock result", x)
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
fmt.Println("addWithoutLock result", x)
}
上述程序分别采用加锁和不加锁的方式,创建5个协程对变量x递增2000,我们期望运行结束后x的值为10000。
运行程序十次,加锁的结果每次都是10000,而不加锁的结果是未知的,只有很小的概率成功。