G
go-with-me
@golangwithus1.9K подп.
3.7Kпросмотров
29 июля 2025 г.
📷 ФотоScore: 4.0K
🤪 Surprising WaitGroup Panic Часто ли вы ловили паники, используя WaitGroup? - не думаем. Сегодняшняя история об одной из таких, зарытой где-то в недрах, вас очень удивит Не будем томить, приступаем! Для начала рассмотрим избитую структуру: type WaitGroup struct { noCopy noCopy state atomic.Uint64 // high 32 bits are counter, low 32 bits are waiter count. sema uint32 } — noCopy - специфическая структура, используемая утилитой govet для маркировки откопированных значений — sema - используется внутренними функциями пакета sync, чтобы реализовать блокировку и разблокировку — state - отвечает за счетчик и количество ожидающих горутин Углубимся в рассмотрение поля state: — Старшие 32 бита представляют из себя счетчик, который инкрементируется при вызовах Add(N) и декрементируется при вызовах Done() — Младшие 32 бита являются количеством ожидающих потоков Теперь посмотрим на внутреннюю логику метода Add: func (wg *WaitGroup) Add(delta int) { // ... state := wg.state.Add(uint64(delta) << 32) v := int32(state >> 32) w := uint32(state) // ... if v < 0 { panic("sync: negative WaitGroup counter") } // ... } — Прибавляем к счетчику наш delta, сдвигая его влево на 32 бита — Извлекаем значение счетчика из state, сдвигая его вправо на 32 бита — Извлекаем количество ожидающих потоков из state, что нас не особо интересует :) Ключевое здесь то, что инкрементация происходит до всех проверок Смотря на этот код, мы задумались о том, а что если наш counter окажется переполненным? Ответ не заставил себя долго ждать. И вот почему! Семантически счетчик воспринимается как int32, то есть один старший бит отвечает за знак, а остальные за мантиссу (само число). В случае, когда счетчик был инкрементирован методом Add(N) до состояния, когда все младшие биты установлены в 1 (счетчик имеет значение 2_147_483_647) и к этому значению мы прибавляем 1, то счетчик становится переполненным и вместо ожидаемого значения 2_147_483_648, мы получаем значение -2_147_483_648 Вот, что происходит на битовом уровне: До Add(1): Двоичное: 01111111 11111111 11111111 11111111 int32: 2147483647 После Add(1): Двоичное: 10000000 00000000 00000000 00000000 int32: -2147483648 Итого: переменная v в методе Add будет иметь отрицательное значение, что вызовет панику Для воспроизведения этого сценария вы можете воспользоваться следующими двумя сниппетами var wg sync.WaitGroup wg.Add(math.MaxInt32) // goroutines launch... wg.Add(1) // panic: sync: negative WaitGroup counter // goroutine launch... var wg sync.WaitGroup for range math.MaxInt32 + 1 { wg.Add(1) // panic: sync: negative WaitGroup counter // goroutines launch... } Мораль WaitGroup не счетная палата и не готова к отслеживанию популяции бактерий на Венере. Пристально следите за своими Add'ами и Done'ами, не отпуская их на волю, и контролируйте количество потоков в вашей системе Статью писали с Дашей: @dariasroom Stay tuned ❤️ #golang #concurrency
3.7K
просмотров
2999
символов
Да
эмодзи
Да
медиа

Другие посты @golangwithus

Все посты канала →
🤪 Surprising WaitGroup Panic Часто ли вы ловили паники, исп — @golangwithus | PostSniper