我有三个线程,一个线程是主线程,另外两个是工作线程。第一个线程,当有工作要做时,会唤醒两个线程中的一个。唤醒时每个线程执行一些计算,如果发现更多工作要做,可以唤醒其他工作线程或者只是决定自己完成工作(例如,通过向本地队列添加工作)。 虽然工作线程有工作要做,但主线程必须等待工作完成。我用条件变量实现了这个(这里报告的代码隐藏了很多细节,请问是否有不可理解的东西):
MAIN THREAD(伪代码):
//this function can be called from the main several time. It blocks the main thread till the work is done.
void new_work(){
//signaling to worker threads if work is available
//Now, the threads have been awakened, it's time to sleep till they have finished.
pthread_mutex_lock(&main_lock);
while (work > 0) //work is a shared atomic integer, incremented each time there's work to do and decremented when finished executing some work unit
pthread_cond_wait(&main_cond);
pthread_mutex_unlock(&main_lock);
}
工人线程:
while (1){
pthread_mutex_lock(&main_lock);
if (work == 0)
pthread_cond_signal(&main_cond);
pthread_mutex_unlock(&main_lock);
//code to let the worker thread wait again -- PROBLEM!
while (I have work to do, in my queue){
do_work()
}
}
问题在于:当一个工作线程唤醒主线程时,我不确定工作线程是否在等待将自己置于新工作的等待状态。即使我用另一个条件变量实现这个等待,也可能发生主线程是清醒的,有些工作直到达到他必须唤醒尚未调用等待的线程的点......这样就可以了导致糟糕的结果。我已经尝试了几种方法来解决这个问题,但我找不到解决方案,也许有一种明显的方法可以解决它,但我很想念它。
你能提供解决这类问题的方案吗?我正在使用C语言,我可以使用你认为适合的任何同步机制,比如pthreads或posix信号量。
由于
答案 0 :(得分:2)
处理此问题的常用方法是拥有一个工作队列并保护其免受溢出和下溢的影响。像这样的东西(我没有留下“pthread_”前缀):
mutex queue_mutex;
cond_t queue_not_full, queue_not_empty;
void enqueue_work(Work w) {
mutex_lock(&queue_mutex);
while (queue_full())
cond_wait(&queue_not_full, &queue_mutex);
add_work_to_queue(w);
cond_signal(&queue_not_empty);
mutex_unlock(&queue_mutex);
}
Work dequeue_work() {
mutex_lock(&queue_mutex);
while (queue_empty())
cond_wait(&queue_not_empty, &queue_mutex);
Work w = remove_work_from_queue();
cond_signal(&queue_not_full);
mutex_unlock(&queue_mutex);
}
注意这些函数之间的对称性:enqueue< - > dequeue,empty< - > full,not_empty< - >不满。
这为任意数量的线程提供了一个线程安全的有界大小队列,从而产生工作和任意数量的线程消耗工作。 (实际上,它是使用条件变量的典型示例。)如果你的解决方案看起来不完全像这样,它可能应该非常接近......
答案 1 :(得分:1)
如果您希望主线程将工作分配给其他两个,那么请等到两个线程完成其工作后再继续,您可以通过屏障完成此操作。
barrier是一种同步构造,您可以使用该构造使代码在代码中的某个位置等待,直到一定数量的线程都准备好继续运行。基本上,您初始化pthread屏障,说 x 线程数必须等待它才允许继续。当每个线程完成其工作并准备继续时,它将在屏障上等待,并且一旦 x 线程数达到屏障,它们都被允许继续。
在您的情况下,您可以执行以下操作:
pthread_barrier_t barrier;
pthread_barrier_init(&barrier, 3);
master()
{
while (work_to_do) {
put_work_on_worker_queues();
pthread_barrier_wait(&barrier);
}
}
worker()
{
while(1) {
while (work_on_my_queue()) {
do_work();
}
pthread_barrier_wait(&barrier);
}
}
这应该让你的主线程放弃工作,然后等待两个工作线程完成它们被给予的工作(如果有的话),然后继续。
答案 2 :(得分:0)
你有没有“新工作”队列,由主线程管理?主线程可以一次为每个工作线程输出1个作业。主线程还会听取工人完成的工作。如果工作线程找到需要执行的新作业,只需将其添加到“新作业”队列中,主线程将分发它。
伪代码:
JobQueue NewJobs;
Job JobForWorker[NUM_WORKERS];
workerthread()
{
while(wait for new job)
{
do job (this may include adding new jobs to NewJobs queue)
signal job complete to main thread
}
}
main thread()
{
while(whatever)
{
wait for job completion on any worker thread
now a worker thread is free put a new job on it
}
}
答案 3 :(得分:0)
我相信你所拥有的是producer-consumer problem的变体。你正在做的是写一个计数信号量的临时实现(一个用于提供不仅仅是互斥的信号量)。
如果我已经正确地阅读了您的问题,那么您要做的就是让工作线程阻塞,直到有一个工作单元可用,然后在可用时执行一个工作单元。您的问题是在有太多工作可用且主线程试图解除已经工作的工作人员的情况下。我会按如下方式构建你的代码。
sem_t main_sem;
sem_init(&main_sem, 0, 0);
void new_work() {
sem_post(&main_sem);
pthread_cond_wait(&main_cond);
}
void do_work() {
while (1) {
sem_wait(&main_sem);
// do stuff
// do more stuff
pthread_cond_signal(&main_sem);
}
}
现在,如果工作线程生成更多工作,那么他们只需sem_post
到信号量,只需将pthread_cond_signal
推迟到完成所有工作。
但是请注意,如果您确实需要主线程在工作人员工作时始终阻塞,那么当您可以调用执行该功能的函数时,将工作推送到另一个线程是没有用的。