- 常用同步机制:
- 互斥锁(
sync.Mutex
):
- 原理:互斥锁用于保护共享资源,在同一时间只允许一个goroutine访问共享资源。当一个goroutine获取了互斥锁,其他goroutine必须等待锁被释放才能获取并访问资源。
- 使用场景:适用于读写操作频繁且对性能要求不是极高的场景,因为加锁和解锁操作会有一定的性能开销。
- 读写锁(
sync.RWMutex
):
- 原理:读写锁允许多个goroutine同时进行读操作,但只允许一个goroutine进行写操作。读操作之间不会互斥,而写操作会排斥读操作和其他写操作。
- 使用场景:适用于读操作远远多于写操作的场景,这样可以提高并发性能,减少读操作的等待时间。
- 通道(
chan
):
- 原理:通过通道进行数据传递,所有对指针slice的操作都通过通道来进行。goroutine将操作(如添加、修改、读取)封装成消息发送到通道,由一个专门的goroutine从通道接收消息并执行操作,这样就避免了多个goroutine直接并发访问指针slice。
- 使用场景:适用于需要对指针slice进行复杂操作且希望以消息驱动方式进行处理的场景。
- 最佳实践:
- 尽量减少共享状态,能不共享数据就不共享,将数据分散到各个goroutine中,避免竞态条件的产生。
- 明确读写操作频率,根据读写频率选择合适的同步机制,如读多写少用读写锁,读写均衡或写多读少用互斥锁。
- 对指针slice的操作进行封装,将同步逻辑放在封装的函数内部,使外部调用者无需关心同步细节,提高代码的可维护性和安全性。
- 示例代码:
package main
import (
"fmt"
"sync"
)
var (
mutex sync.Mutex
pointerSlice []*int
)
func addElement(num int) {
mutex.Lock()
defer mutex.Unlock()
newNum := num
pointerSlice = append(pointerSlice, &newNum)
}
func readElement(index int) *int {
mutex.Lock()
defer mutex.Unlock()
if index < len(pointerSlice) {
return pointerSlice[index]
}
return nil
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
addElement(n)
}(i)
}
go func() {
wg.Wait()
for i := 0; i < len(pointerSlice); i++ {
fmt.Println(*readElement(i))
}
}()
select {}
}
package main
import (
"fmt"
"sync"
)
var (
rwMutex sync.RWMutex
pointerSlice []*int
)
func addElement(num int) {
rwMutex.Lock()
defer rwMutex.Unlock()
newNum := num
pointerSlice = append(pointerSlice, &newNum)
}
func readElement(index int) *int {
rwMutex.RLock()
defer rwMutex.RUnlock()
if index < len(pointerSlice) {
return pointerSlice[index]
}
return nil
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
addElement(n)
}(i)
}
go func() {
wg.Wait()
for i := 0; i < len(pointerSlice); i++ {
fmt.Println(*readElement(i))
}
}()
select {}
}
package main
import (
"fmt"
"sync"
)
type sliceOp struct {
opType string
num int
index int
result chan *int
}
var pointerSlice []*int
func sliceOperator(opChan chan sliceOp) {
for op := range opChan {
switch op.opType {
case "add":
newNum := op.num
pointerSlice = append(pointerSlice, &newNum)
case "read":
if op.index < len(pointerSlice) {
op.result <- pointerSlice[op.index]
} else {
op.result <- nil
}
}
}
}
func main() {
var wg sync.WaitGroup
opChan := make(chan sliceOp)
go sliceOperator(opChan)
for i := 0; i < 10; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
resultChan := make(chan *int)
opChan <- sliceOp{opType: "add", num: n}
close(resultChan)
}(i)
}
go func() {
wg.Wait()
for i := 0; i < len(pointerSlice); i++ {
resultChan := make(chan *int)
opChan <- sliceOp{opType: "read", index: i, result: resultChan}
fmt.Println(*<-resultChan)
close(resultChan)
}
close(opChan)
}()
select {}
}