Нужна ли реализация семафора или мьютекса для простого счетчика? - PullRequest
0 голосов
/ 10 апреля 2019

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

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

// 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);
}

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

1 Ответ

1 голос
/ 11 апреля 2019

Если все ваши потоки стремятся обновить один и тот же объект (т. Е. 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() обеспечивает необходимую синхронизацию между рабочим, устанавливающим результат, и родительским потоком, читающим его.

...