В многопоточной программе на C показан фрагмент: int counter = 0; void* inc(void*){ for(i=0;i

5 Ноя в 15:03
2 +2
0
Ответы
1
Коротко — сначала проблемы, потом практическое проявление и несколько корректных/эффективных вариантов.
Проблемы и как проявляются
1) Несинхронизированный доступ / data race. Переменная `counter` обычная `int`, операция `counter++` — это read-modify-write, неатомарна по стандарту C; на многопоточном исполнении — неопределённое поведение (lost updates, некорректные значения, вообще любые проявления). На практике итоговый счётчик часто будет меньше ожидаемого.
2) Неполная/некорректная функция/переменные в примере. В коде не объявлена `i`, функция не возвращает значение — это UB/компиляционные ошибки.
3) Уплотнение/многошточная конкуренция (contention). Защищать каждое ++ блокировкой даёт большой оверхед и масштабируемость ухудшается.
4) Ложное разделение (false sharing). Если использовать массив счётчиков по потокам без паддинга, соседние счётчики могут попасть в одну кэш-линию и вызвать лишний кэш-трафик.
Корректные и эффективные варианты
A) Простая и надёжная — мьютекс (корректно, но медленно при частых ++)
- Использование: `pthread_mutex_lock(&mtx); counter++; pthread_mutex_unlock(&mtx);`
- Правильно, но при 10610^6106 инкрементов на поток это будет медленно из‑за высокой конкуренции.
B) Атомики (C11) — корректно и обычно быстрее
- Объявление: `#include ` и `atomic_int counter = ATOMIC_VAR_INIT(0);`
- Внутри цикла: `atomic_fetch_add_explicit(&counter, 1, memory_order_relaxed);`
- `memory_order_relaxed` достаточно, если вам нужна только корректность счёта; для простоты можно использовать последовательную консистентность: `atomic_fetch_add(&counter, 1);`
- Это даёт атомарность без блокировок; при очень высокой конкуренции всё равно возможен некоторый кеш‑трафик, но значительно лучше, чем мьютекс.
C) GCC/Clang встроенные атомики (если нет C11)
- `__atomic_fetch_add(&counter, 1, __ATOMIC_RELAXED);` или старее `__sync_fetch_and_add(&counter, 1);`
D) Эффективный вариант — локальные счётчики + объединение (reducing contention)
- Каждый поток ведёт локальный `int local = 0;` и делает `local++` без синхронизации.
- Периодически (например каждые KKK итераций) или в конце делает одну атомную операцию: `atomic_fetch_add(&counter, local); local = 0;`
- Это резко снижает число атомных/блокирующих операций; выбирать KKK = 646464102410241024 или в зависимости от нагрузки.
- Если используется массив счётчиков по потокам, выравнивайте на кэш‑линию (pad до примерно \ (64\) байт) чтобы избежать false sharing.
E) Lock‑free с помощью атомиков + batching/combining (например fetch_add) — часто оптимальный выбор.
Практические советы
- Не используйте `volatile` для синхронизации — это не даёт атомарности/порядка в C.
- Всегда объявляйте/инициализируйте локальную `i` (`for (int i=0; i<100000010000001000000; ++i)`).
- В простом подсчёте где важен только итог, `atomic_fetch_add` с `memory_order_relaxed` + join потоков — хороший компромисс.
- Если нужна очень высокая пропускная способность — используйте локальные буферы/бэтчи и объединяйте в глобальный счётчик редко; следите за паддингом (cache line ≈64\approx 6464 байт).
Краткий пример (C11, вариант с локальным суммированием):
int worker(void *arg) {
atomic_int *glob = arg;
int local = 0;
for (int i = 0; i < 100000010000001000000; ++i) {
++local;
if (local == 256) { // батч
atomic_fetch_add_explicit(glob, local, memory_order_relaxed);
local = 0;
}
}
if (local) atomic_fetch_add_explicit(glob, local, memory_order_relaxed);
return 0;
}
Выводы
- Главная проблема в исходном фрагменте — data race и UB при `counter++`.
- Лучшие практики: использовать C11 атомики или локальный accumulation+атомик для производительности; мьютекс — корректно, но менее эффективно при частых операциях.
5 Ноя в 15:21
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир