简单计数器是否需要实现信号量或互斥量?

Is implementing semaphore or mutex necessary for simple counter?

我尝试实现计算某种积分的程序。为了加快计算速度,一个创建多个进程,另一个使用多个线程。在我的程序中,每个进程在共享内存中添加一个double值,每个线程通过指针添加一个double值。

这是我的问题。 add 操作显然是从内存中加载值,向其添加一个值,并将结果存储到内存中。因此,我的代码似乎很容易出现生产者-消费者问题,因为许多 processes/threads 访问同一内存区域。但是,我找不到有人使用信号量或互斥量来实现简单累加器的情况。

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

所以我就这样实现了,到现在为止不管做了多少processes/threads,结果就好像没发生过一样。 我担心我的代码可能仍然存在潜在危险,只是刚好在我的视线之外。 这段代码真的没有这些问题吗?还是我忽略了什么,是否应该修改代码?

如果您的线程都在竞相更新同一个对象(即,每个线程的 targ->resptr 指向同一事物),那么是的 - 您确实存在数据竞争,并且您会看到不正确的结果(很可能,"lost updates" 碰巧同时完成的两个线程尝试更新总和,但只有其中一个有效)。

你可能没有看到这个,因为你的 integralproc() 函数的执行时间很长,所以多个线程同时到达更新点的机会 *targ->resptr 很低。

尽管如此,您仍然应该解决问题。您可以在总和更新周围添加互斥锁 lock/unlock:

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

(这应该不会影响解决方案的效率,因为您在每个线程的生命周期中只锁定和解锁一次)。

或者,您可以让每个线程在其自己的线程参数结构中记录自己的部分结果:

targ->result = res;

然后,一旦所有工作线程都被 pthread_join()ed,创建它们的父线程就可以遍历所有线程参数结构并将部分结果相加。

这里不需要额外的锁定,因为工作线程不访问彼此的结果变量,并且 pthread_join() 在设置结果的工作线程和读取它的父线程之间提供了必要的同步。