赞
踩
Go语言中是不建议用锁,而是用通道Channel来代替(不要通过共享内存来通信,而通过通信来共享内存),当然锁也是可以用,锁是防止同一时刻多个goroutine操作同一个资源;
GO语言中,要传递某个数据给另一个goroutine(协程),可以把这个数据封装成一个对象,然后把这个对象的指针传入某个channel中,另外一个goroutine从这个channel中读出这个指针,并处理其指向的内存对象。GO从语言层面保证同一个时间只有一个goroutine能够访问channel里面的数据,为开发者提供了一种优雅简单的工具,所以GO的做法就是使用channel来通信,通过通信来传递内存数据,使得内存数据在不同的goroutine中传递,而不是使用共享内存来通信。
加锁操作通常通过 sync
包中的 Mutex
类型来实现。Mutex
(互斥锁)是一种最基本的锁机制,用于保护共享资源,确保在同一时间只有一个 goroutine 可以访问共享资源,从而避免数据竞争和并发问题。
加锁的场景,通过合理地使用锁,可以确保并发程序的正确性和稳定性,避免出现数据竞争和其他并发问题。
我们来认识几种加锁和不加锁的使用;
适用于读多写少的场景下,才能提高程序的执行效率.
package main import ( "fmt" "sync" "time" ) func main() { var mu sync.Mutex // 定义一个互斥锁 var counter int // 启动多个 goroutine 并发地增加计数器的值 for i := 0; i < 5; i++ { go func() { for j := 0; j < 1000; j++ { // 在访问共享资源之前先加锁 mu.Lock() counter++ // 完成对共享资源的访问后释放锁 mu.Unlock() } }() } // 等待所有 goroutine 完成 time.Sleep(time.Second) // 打印最终计数器的值 fmt.Println("Final Counter:", counter) } # 说明 var rwLock sync.RWMutex rwLock.RLock() // 获取读锁 rwLock.RUnlock() // 释放读写 rwLock.Lock() // 获取写锁 rwLock.Unlock() // 释放写锁
以下介绍不需要用户额外加锁的并发操作
sync.Waitgroup
是一种同步原语,
用来等groutine执行完再继续,是一个结构体.是值类型.给函数传参数的时候要传指针.
WaitGroup
是线程安全的,内部使用原子操作,无需额外加锁。package main import ( "fmt" "sync" "time" ) func main() { var wg sync.WaitGroup for i := 0; i < 3; i++ { wg.Add(1) // 添加一个 goroutine 到计数器 go func(id int) { defer wg.Done() // goroutine 完成任务后减少计数器 fmt.Printf("Goroutine %d starting\n", id) time.Sleep(time.Second) // 模拟任务执行 fmt.Printf("Goroutine %d done\n", id) }(i) } fmt.Println("Main goroutine waiting for other goroutines to finish...") wg.Wait() // 等待所有 goroutine 完成任务 fmt.Println("All goroutines finished.") } # 说明 wg.Add(1) // 起几个goroutine就加几个计数 wg.Done() // 在goroutine对应的函数中,函数要结束的时候表示goroutine完成,计数器-1 wg.Wait() // 阻塞,等待所有的goroutine都结束
某些函数只需要执行一次的时候,就可以使用sync.Once
比如 blog加载图片那个例子
var once sync.Once
once.Do() // 接受一个没有参数也没有返回值的函数,如有需要可以使用闭包
package main import ( "fmt" "sync" ) func main() { var once sync.Once // 定义一个初始化函数,只会被执行一次 initialize := func() { fmt.Println("Initializing...") } // 开启多个 goroutine 同时调用初始化函数 for i := 0; i < 3; i++ { go func() { once.Do(initialize) // 使用 sync.Once 确保初始化函数只被执行一次 }() } fmt.Println("Main goroutine waiting...") }
sync.Map
可以用来存储缓存数据,以供多个 goroutine 并发访问。它可以在不需要额外的锁机制的情况下提供并发安全的缓存存储和访问,从而提高缓存系统的性能和并发能力。sync.Map
可以用来管理全局状态。例如,一个 Web 服务器中可以使用 sync.Map
来存储用户的会话状态或其他全局状态信息。sync.Map
可以用来存储配置信息,并提供并发安全的访问和更新接口。这样可以保证在配置更新的过程中不会出现数据竞争或其他并发问题。sync.Map
可以用来存储任务的执行状态或其他相关信息。多个 goroutine 可以并发地读取和更新任务状态,而无需额外的锁机制,从而提高任务调度器的并发能力和性能。sync.Map
可以作为局部缓存的实现。每个节点的局部缓存可以独立地进行读写操作,而无需与其他节点进行同步,从而提高系统的响应速度和吞吐量。sync.Map
是 Go 语言标准库 sync
包中提供的一种并发安全的键值对映射类型。与普通的 map
不同,sync.Map
在并发访问时不需要额外的锁机制,因此在并发场景下具有更好的性能。
是一个开箱即用(不需要make初始化)的并发安全的map,
package main import ( "fmt" "sync" ) func main() { var m sync.Map // 使用 Store 方法向 sync.Map 中存储键值对 m.Store("key1", "value1") m.Store("key2", "value2") m.Store("key3", "value3") // 使用 Load 方法从 sync.Map 中加载键对应的值 if value, ok := m.Load("key1"); ok { fmt.Println("Value for key1:", value) } else { fmt.Println("Key1 not found") } // 使用 Range 方法遍历 sync.Map 中的所有键值对 fmt.Println("All key-value pairs:") m.Range(func(key, value interface{}) bool { fmt.Println("Key:", key, "Value:", value) return true // 返回 true 继续遍历,返回 false 中止遍历 }) // 使用 Delete 方法从 sync.Map 中删除键值对 m.Delete("key2") // 检查是否包含某个键 fmt.Println("Contains key3?", m.Load("key3")) // 清空 sync.Map m.Range(func(key, value interface{}) bool { m.Delete(key) return true }) } # 说明 // Map[key] = value // 原生map syncMap.Store(key, value) syncMap.Load(key) syncMap.LoadOrStore() syncMap.Delete() syncMap.Range()
Go语言内置了一些针对内置的基本数据类型的一些并发安全的操作;使用场景想用就用
package main import ( "fmt" "sync" "sync/atomic" ) func main() { var counter int64 // 使用 int64 类型的计数器 var wg sync.WaitGroup const numGoroutines = 10 wg.Add(numGoroutines) // 多个 goroutine 并发地对计数器进行增加操作 for i := 0; i < numGoroutines; i++ { go func() { for j := 0; j < 1000; j++ { atomic.AddInt64(&counter, 1) // 使用原子的增加操作 } wg.Done() }() } wg.Wait() // 等待所有 goroutine 完成 fmt.Println("Final Counter:", counter) }
time.After
函数返回的 channel 来实现某个操作的超时判断。package main import ( "fmt" "time" ) func sender(ch chan<- int) { for i := 0; i < 5; i++ { ch <- i // 向通道发送数据 time.Sleep(time.Second) } close(ch) // 关闭通道 } func receiver(ch <-chan int) { for num := range ch { // 从通道接收数据,直到通道关闭 fmt.Println("Received:", num) } } func main() { ch := make(chan int) // 创建一个整型通道 go sender(ch) // 启动发送数据的 goroutine go receiver(ch) // 启动接收数据的 goroutine time.Sleep(6 * time.Second) // 等待一段时间,确保 goroutine 有足够的时间执行 }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。