Проблема: одновременно несколько потоков выполняют неблокируемое чтение/запись общей переменной `counter` — операция `counter++` неатомарна, возникает гонка за данными и неверный итог. Корректные способы синхронизации (с примерами): 1) Мьютекс (правильно, но сильно сериализует доступ) ``` pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER; int counter = 0; void* inc(void* arg){ for(int i = 0; i < 100000100000100000; ++i){ pthread_mutex_lock(&m); counter++; pthread_mutex_unlock(&m); } return NULL; } ``` 2) C11 атомики (лучше по производительности при высокой конкуренции) ``` #include
atomic_int counter = ATOMIC_VAR_INIT(0); void* inc(void* arg){ for(int i = 0; i < 100000100000100000; ++i) atomic_fetch_add_explicit(&counter, 1, memory_order_relaxed); return NULL; } ``` 3) Батчинг (рекомендация для максимальной производительности — уменьшает число синхронных операций) ``` atomic_int counter = ATOMIC_VAR_INIT(0); void* inc(void* arg){ int local = 0; const int C = 100100100; // размер батча for(int i = 0; i < 100000100000100000; ++i){ local++; if(local == C){ atomic_fetch_add_explicit(&counter, local, memory_order_relaxed); local = 0; } } if(local) atomic_fetch_add_explicit(&counter, local, memory_order_relaxed); return NULL; } ``` Влияние на производительность (кратко): - Без синхронизации — некорректно, но максимально быстро (ноль затрат на синхронизацию). - С мьютексом — каждое ++ выполняется под блокировкой: доступ почти полностью сериализуется, высокая задержка/низкая пропускная способность при большом числе потоков. - С атомиками — быстрее, так как операции атомарного инкремента менее дорогие, но при интенсивном обновлении одного счётчика возникает "ping‑pong" кеш‑линий между ядрами, что ограничивает масштабируемость. - Батчинг снижает количество дорогостоящих синхронных операций примерно в CCC раз: число атомик‑операций ≈ T×⌈100000/C⌉T \times \lceil 100000 / C \rceilT×⌈100000/C⌉ для TTT потоков, что обычно даёт существенный прирост. Ожидаемый корректный итог: при TTT потоках final = T×100000T \times 100000T×100000. Рекомендация: для простого счётчика используйте атомики с memory_order_relaxed; если потоков много и частые обновления — примените локальные буферы/батчинг и редкие атомарные добавления.
Корректные способы синхронизации (с примерами):
1) Мьютекс (правильно, но сильно сериализует доступ)
```
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;
void* inc(void* arg){
for(int i = 0; i < 100000100000100000; ++i){
pthread_mutex_lock(&m);
counter++;
pthread_mutex_unlock(&m);
}
return NULL;
}
```
2) C11 атомики (лучше по производительности при высокой конкуренции)
```
#include atomic_int counter = ATOMIC_VAR_INIT(0);
void* inc(void* arg){
for(int i = 0; i < 100000100000100000; ++i)
atomic_fetch_add_explicit(&counter, 1, memory_order_relaxed);
return NULL;
}
```
3) Батчинг (рекомендация для максимальной производительности — уменьшает число синхронных операций)
```
atomic_int counter = ATOMIC_VAR_INIT(0);
void* inc(void* arg){
int local = 0;
const int C = 100100100; // размер батча
for(int i = 0; i < 100000100000100000; ++i){
local++;
if(local == C){
atomic_fetch_add_explicit(&counter, local, memory_order_relaxed);
local = 0;
}
}
if(local) atomic_fetch_add_explicit(&counter, local, memory_order_relaxed);
return NULL;
}
```
Влияние на производительность (кратко):
- Без синхронизации — некорректно, но максимально быстро (ноль затрат на синхронизацию).
- С мьютексом — каждое ++ выполняется под блокировкой: доступ почти полностью сериализуется, высокая задержка/низкая пропускная способность при большом числе потоков.
- С атомиками — быстрее, так как операции атомарного инкремента менее дорогие, но при интенсивном обновлении одного счётчика возникает "ping‑pong" кеш‑линий между ядрами, что ограничивает масштабируемость.
- Батчинг снижает количество дорогостоящих синхронных операций примерно в CCC раз: число атомик‑операций ≈ T×⌈100000/C⌉T \times \lceil 100000 / C \rceilT×⌈100000/C⌉ для TTT потоков, что обычно даёт существенный прирост.
Ожидаемый корректный итог: при TTT потоках final = T×100000T \times 100000T×100000.
Рекомендация: для простого счётчика используйте атомики с memory_order_relaxed; если потоков много и частые обновления — примените локальные буферы/батчинг и редкие атомарные добавления.