Goroutine
Goroutine是Go语言中用于并发执行的轻量级线程,它由Go运行时(runtime)管理。在Go程序中,你可以通过关键字"go"来创建一个Goroutine。 Goroutine的创建非常简单,只需要在函数调用前加上"go"关键字即可。
例:
println("hello goRoutine:" + fmt.Sprint(i))
}
func HelloGoRoutine() {
for i := 0; i < 5; i++ {
go func(j int) { //开启一个协程: go+函数
hello(j)
}(i)
}
time.Sleep(time.Second)
}
func main() {
HelloGoRoutine()
}
CSP
CSP(Communicating Sequential Processes)是一种并发计算的模型,它将并发系统中的并发操作抽象为独立运行的进程(Process),这些进程通过消息传递的方式进行通信和同步。
在CSP模型中,各个进程是独立运行的,它们没有共享的内存空间,只能通过发送和接收消息来进行通信。进程之间通过通道(Channel)进行消息的传递,一个进程发送消息到通道,另一个进程从通道中接收消息。
通过通讯共享内存 
CSP模型的特点包括:
- 并行性:CSP模型适用于描述并发执行的系统,各个进程可以并行地执行。
- 通信顺序性:CSP模型中,消息的发送和接受是按照一定的顺序进行的。一个进程发送的消息必须要有另一个进程接收,否则发送操作会被阻塞。
- 同步:通过通信来实现同步,进程之间可以通过发送和接收消息来进行同步操作,确保进程之间的顺序执行。
- 无共享内存:CSP模型中的进程之间没有共享的内存空间,进程之间唯一的交互方式就是通过消息传递。
并发安全LOCK
通过使用LOCK来保证某一个协程进行完再走写一个
var (
x int64
lock sync.Mutex
)
// 使用了并发安全Lock
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 main() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println("WithoutLock:", x)
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
println("WithLock:", x)
}
//WithoutLock: 8277
//WithLock: 10000
//有lock的协程会让x每次加完才走下一步,而没有lock会出现x漏加的情况(因为是并发执行)
Channel
通道(Channel)是Go语言中用于在不同Goroutine之间进行通信和同步的一种机制。通道提供了一种安全、简单和高效的方式来传递数据。
在Go语言中,你可以通过内置的make函数来创建一个通道。通道可以指定传递的数据类型:
ch := make(chan int) // 创建一个传递int类型数据的通道 (src)
通过通道,你可以使用箭头操作符<-来发送和接收数据。发送数据的语法是 通道 <- 值,接收数据的语法是值 := <- 通道
实例测试:
//通过通信来实现共享内存
//A子协程发送0-9数字
//B子协程计算输入数字的平方
//主协程输出最后平方数
func CalSquare() {
src := make(chan int) //无缓存队列,速度快
dest := make(chan int, 3) //有缓存,解决生产和消费速度,
//A协程
go func() {
defer close(src)
for i := 0; i < 10; i++ {
src <- i
}
}()
//B协程
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest {
//复杂操作
println(i)
}
}
func main() {
CalSquare()
}
输出: 0 1 4 9 16 25 36 49 64 81
在上面的例子中,我们创建了两个整型通道 src 和 dest 。在一个Goroutine中,我们发送了值到通道 src 。然后,在主Goroutine中,我们从通道 src 接收到了值,B协程遍历 src 把值发送给 desc 通道。
通道的发送和接收操作是阻塞的,这意味着发送者和接收者在进行操作时会等待对方完成。如果发送者向一个已满的通道发送数据,发送操作会被阻塞,直到有空闲的位置。如果接收者从一个空的通道接收数据,接收操作会被阻塞,直到有可用的数据。
通道还可以通过指定缓冲区大小来创建带有缓冲区的通道。带有缓冲区的通道可以在缓冲区未满时进行发送,只有在缓冲区已满或被读取之后才会阻塞发送操作。(dest)
优点:使用通道可以实现多个Goroutine之间的同步和通信,有效地避免了竞态条件和资源争用问题,使得并发程序更加安全和可靠。同时,通道也是Go语言中实现多种并发设计模式的重要组成部分。
WaitGroup
WaitGroup是Go语言中用于等待一组Goroutine完成执行的一种同步机制。通过WaitGroup,我们可以等待一组Goroutine全部执行完毕,然后再继续执行后续的操作。
WaitGroup的使用包括三个主要步骤:
- 创建WaitGroup对象:通过调用
sync.WaitGroup的Add方法来创建一个WaitGroup对象,并初始化其计数器。
var wg sync.WaitGroup
-
增加计数器:在每个需要等待的Goroutine之前,调用WaitGroup的
Add方法,增加计数器的值。 -
减少计数器:在Goroutine执行完成后,调用WaitGroup的
Done方法,减少计数器的值。 -
等待完成:在主Goroutine中,调用WaitGroup的
Wait方法,阻塞等待所有计数器归零。
实例:
func HelloGoRoutine() {
var wg sync.WaitGroup //声明
wg.Add(5) //要开启5个协程
for i := 0; i < 5; i++ {
go func(j int) { //开启一个协程: go+函数
defer wg.Done() //计算器-1;表名这个协程已结束
hello(j)
}(i)
}
wg.Wait() //阻塞直到计算器为0
}
func main() {
HelloGoRoutine()
}
这段代码中定义了一个函数HelloGoRoutine(),该函数会开启5个Goroutine并行执行hello()函数。
在HelloGoRoutine()函数中,首先声明了一个sync.WaitGroup对象wg用来等待所有的Goroutine完成执行。然后使用wg.Add(5)将计数器设置为5,表示需要开启5个Goroutine。
接下来使用一个for循环来开启5个Goroutine。在每个Goroutine中,使用go func(j int)来定义一个匿名函数作为Goroutine的执行体。通过使用j参数将当前迭代变量的值传递给匿名函数,确保每个Goroutine都能获取到正确的索引值。
在匿名函数中,使用defer wg.Done()将计数器减一,表示当前Goroutine执行结束。然后调用hello(j)函数进行具体的逻辑处理。
主函数main()中调用了HelloGoRoutine()函数来执行。在HelloGoRoutine()函数执行的过程中,主函数会等待所有Goroutine执行完成,即wg.Wait()。
优点:通过这样的方式,可以实现多个Goroutine并行执行而不用担心它们的执行时机,同时还可以确保所有Goroutine执行完毕后再进行后续的操作。