Необходима ли реализация семафора или мьютекса для простого счетчика?

Я пытался реализовать программы, которые вычисляют своего рода интеграл. И чтобы ускорить вычисления, один создает несколько процессов, а другой использует несколько потоков. В моей программе каждый процесс добавляет двойное значение в разделяемую память, а каждый поток добавляет двойное значение через указатель.

Вот мой вопрос. Очевидно, что операция добавления загружает значение из памяти, добавляет к нему значение и сохраняет результат в памяти. Таким образом, кажется, что мой код весьма подвержен проблеме производителя-потребителя, поскольку многие процессы/потоки обращаются к одной и той же области памяти. Однако я не смог найти случая, когда кто-то использовал семафоры или мьютексы для реализации простого аккумулятора.

// creating processes
while (whatever)
{
    pid = fork();
    if (pid == 0)
    {
        res = integralproc(clist, m, tmpcnt, tmpleft, tmpright);
        *(createshm(shm_key)) += res;
        exit(1);
    }
}
// creating or retrieving shared memory
long double* createshm(int key)
{
    int shm_id = -1;
    void* shm_ptr = (void*)-1;
    while (shm_id == -1)
    {
        shm_id = shmget((key_t)key, sizeof(long double), IPC_CREAT | 0777);
    }
    while (shm_ptr == (void*)-1)
    {
        shm_ptr = shmat(shm_id, (void*)0, 0);
    }
    return (long double*)shm_ptr;
}

// creating threads
while (whatever)
{
    threadres = pthread_create(&(targs[i]->thread_handle), NULL, integral_thread, (void*)targs[i]);
}
// thread function. targ->resptr is pointer that we add the result to.
void *integral_thread(void *arg)
{
    threadarg *targ = (threadarg*)arg;
    long double res = integralproc(targ->clist, targ->m, targ->n, targ->left, targ->right);
    *(targ->resptr) += res;
    //printf("thread %ld calculated %Lf\n", targ->i, res);
    pthread_exit(NULL);
}

Поэтому я реализовал это таким образом, и до сих пор, сколько бы процессов/потоков я не делал, результат был таким, как будто этого никогда не было. Я обеспокоен тем, что мои коды все еще могут быть потенциально опасными, просто вне поля моего зрения. Действительно ли этот код защищен от любой из этих проблем? Или я что-то упускаю из виду, и код должен быть исправлен?

Технически он не определен, если только вы не используете какую-либо блокировку или, что предпочтительнее, не делаете счетчик атомарным и не используете с ним атомарные операции.

PSkocik 10.04.2019 20:02

Я рекомендую написать простую программу для целые числа. Несколько потоков увеличивают целое число (volatile) фиксированное количество раз параллельно, без синхронизации. Сравните результат с фиксированным числом приращений, умноженным на количество потоков. Почитайте об атомарности и неопределенном поведении.

EOF 10.04.2019 20:03

Почему бы не иметь отдельный счетчик для каждого потока и не объединить их в конце?

stark 10.04.2019 22:27

Кроме того, будьте очень осторожны, чтобы не использовать volatile в качестве атома бедняка, это просто неправильное использование volatile. C получил четко определенную стандартизованную модель многопоточной памяти, в основном она взяла стандартную модель параллелизма/атомарности C++ оптом.

Erik Alapää 11.04.2019 09:47
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
57
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если все ваши потоки соревнуются в обновлении одного и того же объекта (т. е. targ->resptr для каждого потока указывает на одно и то же), то да — у вас действительно есть состязание данных, и вы можете увидеть неправильные результаты (вероятно, «потерянные обновления», где два потоки, которые завершаются одновременно, пытаются обновить сумму, и только один из них эффективен).

Вы, вероятно, этого не видели, потому что время выполнения вашей функции integralproc() велико, поэтому вероятность того, что несколько потоков одновременно достигнут точки обновления *targ->resptr, невелика.

Тем не менее, вы все равно должны решить проблему. Вы можете либо добавить блокировку/разблокировку мьютекса вокруг обновления суммы:

pthread_mutex_lock(&result_lock);
*(targ->resptr) += res;
pthread_mutex_unlock(&result_lock);

(Это не должно влиять на эффективность решения, поскольку вы блокируете и разблокируете только один раз за время существования каждого потока).

В качестве альтернативы вы можете сделать так, чтобы каждый поток записывал свой собственный частичный результат в своей собственной структуре аргументов потока:

targ->result = res;

Затем, как только все рабочие потоки будут pthread_join() отредактированы, создавший их родительский поток может просто просмотреть все структуры аргументов потока и сложить частичные результаты.

Здесь не требуется дополнительная блокировка, потому что рабочие потоки не имеют доступа к переменной результата друг друга, а pthread_join() обеспечивает необходимую синхронизацию между рабочим, устанавливающим результат, и родительским потоком, читающим его.

Я думаю, что тогда необходимо и безопасно реализовать мьютекс или семафор. Спасибо за добрый ответ.

STRIKE-F 12.04.2019 10:54

Другие вопросы по теме