1. 使用互斥锁(Mutex)
- 原理:互斥锁用于保证在同一时间只有一个goroutine能够访问共享资源。当一个goroutine获取到锁,其他goroutine必须等待锁释放后才能获取并访问共享资源,从而避免数据竞争。
- 代码示例:
package main
import (
"fmt"
"sync"
)
type Counter struct {
count int
mu sync.Mutex
}
func (c *Counter) Increment() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
func (c *Counter) Get() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.count
}
func main() {
var wg sync.WaitGroup
counter := Counter{}
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
counter.Increment()
}()
}
wg.Wait()
fmt.Println("Final count:", counter.Get())
}
2. 使用读写锁(RWMutex)
- 原理:读写锁允许多个goroutine同时进行读操作,但只允许一个goroutine进行写操作。写操作时会阻止其他读和写操作,读操作时不阻止其他读操作。适用于读多写少的场景。
- 代码示例:
package main
import (
"fmt"
"sync"
)
type Data struct {
value int
mu sync.RWMutex
}
func (d *Data) Read() int {
d.mu.RLock()
defer d.mu.RUnlock()
return d.value
}
func (d *Data) Write(newValue int) {
d.mu.Lock()
d.value = newValue
d.mu.Unlock()
}
func main() {
var wg sync.WaitGroup
data := Data{}
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
data.Write(i)
}()
}
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println("Read value:", data.Read())
}()
}
wg.Wait()
}
3. 使用原子操作
- 原理:原子操作是不可分割的操作,在执行过程中不会被其他goroutine中断。Go语言的
atomic
包提供了一系列原子操作函数,适用于简单数据类型的操作,避免锁带来的开销。
- 代码示例:
package main
import (
"fmt"
"sync"
"sync/atomic"
)
func main() {
var counter int64
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
atomic.AddInt64(&counter, 1)
}()
}
wg.Wait()
fmt.Println("Final count:", atomic.LoadInt64(&counter))
}
4. 使用通道(Channel)
- 原理:通道是Go语言中用于goroutine间通信的机制,通过通道传递数据,可以避免共享内存带来的数据竞争问题。数据在goroutine之间传递而不是共享,符合“不要通过共享内存来通信,而要通过通信来共享内存”的原则。
- 代码示例:
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for j := range jobs {
fmt.Printf("Worker %d started job %d\n", id, j)
result := j * 2
fmt.Printf("Worker %d finished job %d with result %d\n", id, j, result)
results <- result
}
}
func main() {
const numJobs = 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
for w := 1; w <= 3; w++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
worker(id, jobs, results)
}(w)
}
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
go func() {
wg.Wait()
close(results)
}()
for r := range results {
fmt.Println("Result:", r)
}
}
内存泄漏防范
- 避免循环引用:确保数据结构不会形成循环引用导致垃圾回收器无法回收内存。在使用指针构建复杂数据结构时要特别注意。
- 及时关闭通道:如上述通道示例,及时关闭通道可以避免goroutine阻塞等待数据,防止内存泄漏。同时,在接收通道数据时使用
for... range
循环来确保在通道关闭后能正确退出。
- 合理使用资源:如文件句柄、网络连接等资源,使用完毕后要及时释放,Go语言的
defer
语句可以方便地处理资源释放操作。