父级不等待工作线程完成任务

Parent does not wait for worker threads to complete task

本文关键字:线程 完成任务 工作 等待      更新时间:2023-10-16

在我的程序中,主线程创建 4 个(或更多)工作线程。在某些时候,父线程(主线程)必须等待工作线程才能完成一些计算。线程在无限循环上运行,所以我无法使用 pthread_join(.., ..) POSIX 函数来等待工作线程完成。所以我正在使用全局计数器和条件变量。

主线程代码

unsigned jobs = 0; // global variable
// global mutex and cv. They get initialised in my main.
pthread_mutex_t counter_mutex;
pthread_cond_t  counter_cv;
static void process(..){
    jobs = myArray.size(); 
    // I am using a function here that broadcasts a cv in order to
    // wake up the workers
    pthread_mutex_lock(&counter_mutex); // lock counter
        while (jobs > 0){
            pthread_cond_wait(&counter_cv, &counter_mutex); // PARENT SHOULD GET STUCK HERE TILL WORKERS ARE DONE
        }
        // cout << "Workers are done" << endl;
    pthread_mutex_unlock(&counter_mutex);   // unlock counter
}

工人守则

 extern unsigned jobs;
 extern pthread_mutex_t counter_mutex;
 extern pthread_cond_t  counter_cv;
 void *run() {
    for (int i = 0;; i++) {
        // do some calculations here
        pthread_mutex_lock(&counter_mutex); // lock counter
            jobs--;
            if (jobs == 0){
                pthread_cond_signal(&counter_cv);
                cout << "All jobs are done" << endl;
            }
        pthread_mutex_unlock(&counter_mutex);   // unlock counter
    }
}    

问题是有时我的主线程不会卡在条件变量上以等待工作线程,这有时会导致分段错误。是否存在任何我看不到的竞争条件并导致此问题?

代码中存在明显的并发问题。为了简单起见,假设我们有 2 名工人,还剩下 2 份工作。 可能会发生以下情况:

  • 作业计数设置为 2
  • 父级被困在等待条件变量
  • 两个工作线程在他们的无限循环中开始处理迭代
  • 无论出于何种原因,第一个工作线程都会更快地完成其任务,并执行锁定的作业计数更新。作业计数递减为 1,因此父项将等待并释放锁。 然后,工作线程开始其下一次迭代。不幸的是,实际上没有工作了(1 个已经完成,1 个目前由第二个工人执行)。但它启动了一个假设的工作(也许解决同时被删除或超出范围的数组元素?
  • 第二个工作线程完成其作业,作业计数递减为 0,导致父工作线程被唤醒,按住锁,退出循环并解锁。
  • 您注意到,虽然父级认为它已经结束,但第一个工作线程仍在处理假设的任务,也许试图访问myArray而它被清理干净,或者其他任何可能出错的地方。

因此,您有两个种族和/或赛段的机会:在父母和仍然活跃的工人中,忙于一项不存在的任务。

我认为,如果您启动循环以获取锁定,检查是否还有剩余的作业,并预先减少作业计数,以便同行工作人员知道真正剩下的内容,那么工作线程循环会更安全:

void *run() {
    for (int i = 0;; i++) {
        pthread_mutex_lock(&counter_mutex); // lock counter
            if (jobs == 0){
                pthread_cond_signal(&counter_cv);
                cout << "All jobs are done" << endl;
            }
            else jobs--;
        pthread_mutex_unlock(&counter_mutex);   // unlock counter
        // do some calculations here
    }
}   

优点是,工人只有在真正剩下工作时才工作。 那么唯一的麻烦是,父母被第一个失业的工人唤醒了。然而,其他工人可能仍在运行。

例如,如果

这是一个问题,您还可以维护一个仍然处于活动状态的作业计数器,并使父循环打开(jobs>0 || active_jobs>0)

我在你的代码中看到的唯一问题是每个工作线程都会疯狂运行(如果你的处理不涉及计时器/IO,可能会以 100% CPU 运行),并且一旦没有更多的作业就不会终止。

在实际情况下,您的工作人员应该从队列或类似内容中获取作业请求,并在没有可用作业要处理时阻止(等待可用作业或某些终止命令)。

我在这里看不到竞争条件。我看到的唯一错误是作业计数的系统性减少。它很快就会变成负面的,如果你假设它不能,这可能会造成各种各样的麻烦。为避免这种情况,只需在递减计数器之前将其测试为零即可。