在Go语言中,实现并发访问内存主要依赖于goroutine和channel。Go的并发模型是基于CSP(Communicating Sequential Processes)理论的,通过goroutine来执行并发任务,并通过channel来进行goroutine之间的通信。以下是一些关键的概念和方法:
1. 创建和启动goroutine
func main() {
go someFunction()
}
func someFunction() {
// 并发执行的代码
}
在上面的代码中,go someFunction()
创建并启动了一个新的goroutine,它将与主goroutine并发执行。
2. 使用channel进行通信
func main() {
done := make(chan bool) // 创建一个channel
go func() {
// 做一些工作
done <- true // 通过channel发送一个信号
}()
<-done // 从channel接收信号,直到goroutine完成工作
}
在这个例子中,我们创建了一个channel done
,然后在一个goroutine中向它发送一个值。主goroutine通过从done
channel接收值来等待goroutine完成。
3. 并发执行多个任务
func someFunction() {
// 任务代码
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1) // 增加等待组的计数
go func() {
someFunction()
wg.Done() // 任务完成后减少计数
}()
}
wg.Wait() // 等待所有goroutine完成
}
在这个例子中,我们使用了sync.WaitGroup
来等待多个goroutine完成。每个goroutine在开始前调用wg.Add(1)
,在完成后调用wg.Done()
。
4. 并发访问共享资源
var mu sync.Mutex // 创建一个互斥锁
var counter int
func increment() {
mu.Lock() // 锁定互斥锁
counter++ // 并发安全地增加计数
mu.Unlock() // 解锁
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
increment()
wg.Done()
}()
}
wg.Wait()
println(counter) // 应该输出1000
}
在这个例子中,我们使用sync.Mutex
来保护对共享资源counter
的并发访问。在修改counter
之前,我们通过mu.Lock()
获取锁,并在修改后通过mu.Unlock()
释放锁。
5. 使用原子操作
对于一些简单的操作,可以使用sync/atomic
包中的原子操作来避免使用互斥锁。
package main
import (
"fmt"
"sync/atomic"
"time"
)
func main() {
counter := int32(0)
const workers = 100
const operations = 1000
var wg sync.WaitGroup
wg.Add(workers)
for i := 0; i < workers; i++ {
go func() {
for j := 0; j < operations; j++ {
atomic.AddInt32(&counter, 1)
}
wg.Done()
}()
}
wg.Wait()
fmt.Println(counter) // 应该输出 operations * workers
}
在这个例子中,我们使用atomic.AddInt32
函数来原子地增加counter
的值,这样可以避免在多个goroutine中对同一变量进行操作时发生竞态条件。
通过上述方法,Go语言提供了强大的并发编程能力,允许开发者以安全和高效的方式并发访问内存。