В Go примитивы синхронизации можно найти в пакете sync
и они включают в себя Mutex
, RWMutex
, WaitGroup
и Once
. Ниже представлен краткий обзор каждого из примитивов и примеры их использования.
1. **Mutex (взаимное исключение)** — используется для обеспечения безопасного доступа к данным из разных горутин. Когда горутина захватывает Mutex
, все другие горутины ждут, пока Mutex
не будет освобожден.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package main import ( "fmt" "sync" ) var ( mutex sync.Mutex balance int ) func deposit(value int, wg *sync.WaitGroup) { mutex.Lock() balance += value mutex.Unlock() wg.Done() } func main() { var wg sync.WaitGroup wg.Add(2) go deposit(200, &wg) go deposit(100, &wg) wg.Wait() fmt.Printf("New Balance: %d\n", balance) } |
2. **RWMutex (Reader/Writer mutex)** — это расширенная версия Mutex
, которая разрешает множественные горутины одновременно читать данные, но требует эксклюзивного доступа для записи.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package main import ( "fmt" "sync" "time" ) var ( rwMutex sync.RWMutex balance int ) func readBalance(wg *sync.WaitGroup) { rwMutex.RLock() fmt.Printf("Balance: %d\n", balance) rwMutex.RUnlock() wg.Done() } func main() { var wg sync.WaitGroup wg.Add(1) go readBalance(&wg) wg.Wait() } |
3. **WaitGroup** — используется для ожидания завершения работы нескольких горутин. Вы вызываете Add
для установки счетчика, Done
для его уменьшения и Wait
, чтобы блокировать выполнение до тех пор, пока счетчик не дойдет до нуля.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package main import ( "fmt" "sync" "time" ) func worker(id int, wg *sync.WaitGroup) { defer wg.Done() fmt.Printf("Worker %d starting\n", id) time.Sleep(time.Second) fmt.Printf("Worker %d done\n", id) } func main() { var wg sync.WaitGroup for i := 1; i <= 5; i++ { wg.Add(1) go worker(i, &wg) } wg.Wait() } |
4. **Once** — обеспечивает, что функция будет выполнена только один раз, независимо от того, сколько раз она вызывается. Это полезно, например, для инициализации, которая должна быть выполнена единожды.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
package main import ( "fmt" "sync" ) var ( once sync.Once counter int ) func incrementCounter() { counter++ } func main() { var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { defer wg.Done() once.Do(incrementCounter) }() } wg.Wait() fmt.Printf("Counter: %d\n", counter) // Выведет "Counter: 1" } |
Эти примитивы синхронизации используют низкоуровневые функции операционной системы для блокировки и разблокировки горутин и обеспечения синхронного доступа к данным, что позволяет писать конкурентный код с безопасным доступом к общим ресурсам.