适当的条件变量用法

时间:2014-02-05 13:39:22

标签: c multithreading pthreads mutex condition-variable

我想确定我理解条件变量是如何工作的,所以我会用我写的程序来问我的问题。

在我的程序中,我有一个“生产者”主题(一个)和“工作线程”(几个让我们假设 3 )。

生产者线程“处理”一个FIFO链表,这意味着,它的作用就是检查是否有一个项目(在我的程序中名为 request Req类型)列表的开头(由我的程序中名为 front 的全局指针指向),如果是,则将其分配到全局请求元素(称为globalReq )。

工作线程,循环运行,等待处理请求,通过将全局请求变量提取到自己的局部变量中(这是每个线程的“私有”导致每个线程都有一个独立的堆栈 - 如果我错了,请纠正我,然后处理请求。

为了做到这一点,我使用互斥量和条件变量。

一个重要的注意事项是,一旦请求存在(暂时让我们假设只存在一个请求),IT并不重要哪个工作线程将“照顾”它(假设它们都是“免费的” “ - 睡在条件变量上。”

在提取请求并将其分配到全局请求之后,生产者线程调用pthread_cond_signal - 据我所知,这解除了至少一个“阻塞”线程 - >因此它可以解锁,例如2个线程。

所以我的问题是我现有的代码(下面):

1)我怎样才能保证只有一个线程(来自工作线程)将处理请求。 我是否需要在所有通用“生产者消费者”实现中添加“while check循环”?

2)通过pthread_cond_broadcast(或者pthread_cond_signal取消阻止多个线程)解除阻塞的线程如何通过互斥锁上的内容,可能我还没掌握它...

工作线程的(每一个)代码是:

void *worker(void *arg)
{

       while(1)
       {
         printf("\n BEFORE LOCKING sthread_mutex  with thread: %d \n", syscall(SYS_gettid));
                 pthread_mutex_lock(&sthread_mutex);
         printf("\n AFTER UNLOCKING sthread_mutex  with thread: %d \n", syscall(SYS_gettid));

         printf("\n BEFORE WAITING ON cond_var with thread: %d \n", syscall(SYS_gettid));           
                 pthread_cond_wait(&cond_var,&sthread_mutex); //wait on condition variable
         printf("\n AFTER WAITING ON cond_var with thread: %d \n", syscall(SYS_gettid));            

                 printf("\n got signal for thread: %d \n",syscall(SYS_gettid));

         // extract the current request into g local variable 
             // within the "private stack" of this thread   
                 Req localReq = globalReq;  
                 pthread_mutex_unlock(&sthread_mutex);
         printf("\n AFTER UNLOCKING sthread_mutex with thread: %d \n", syscall(SYS_gettid));

            // perform the desired task
                   task(localReq);
           printf("\n BEFORE calling sem_post with thread: %d \n", syscall(SYS_gettid));
           sem_post(&sem);

       } // end while (1)

} // end of worker thread function

生产者线程的代码是:

void *producer(void *arg)
{
       while(1)
       {

              if(front != NULL)  // queue not empty  
              {
              // go to sleep if all "worker threads" are occuipied 
              // or decrement number of free "worker threads" threads by 1
              printf(" schedualer thread BEFORE calling sem_wait on sem \n");
                          sem_wait(&sem);

              // lock the sthread mutex in order to "synchronize" with the
              // "worker threads"...
              printf(" schedualer thread BEFORE locking sthread_mutex \n");
                          pthread_mutex_lock(&sthread_mutex);
              printf(" schedualer thread AFTER locking sthread_mutex \n");


                          globalReq = extract_element(); // this is the global request variable 

              // notify the worker threads that an "arriving request" needed to 
              // be taking care of
              printf(" schedualer thread BEFORE calling signal on cond_var \n");
                          // pthread_cond_signal(&cond_var);
              pthread_cond_broadcast(&cond_var);
              printf(" schedualer thread AFTER calling signal on cond_var \n");

              // unlock the smutex
              printf(" schedualer thread BEFORE UNLOCKING sthread_mutex \n");
                          pthread_mutex_unlock(&sthread_mutex);
              printf(" schedualer thread AFTER UNLOCKING sthread_mutex \n");

               }  // queue not empty  

               else
                    continue;


        }  // end while (1)

 } // end of producer

另一个问题:

生产者线程在全局信号量上调用sem_wait(在开头用工作线程数初始化,在这种情况下 3 )为了自己指示有多少工作线程正在处理请求,并且为了完成这个“机制”,工作线程一旦完成处理他们“赢了”的请求(当争用条件变量时),就调用{ {1}}表示“另一个工作线程可用”

3)这是实现这种“发信号通知有多少可用工作线程”的正确(好/有效)方式吗?

4)有什么好处&通过// *段落中提到的生产者和工作线程之间共享的全局变量“传递”请求的缺点?传递它是一种明智的方式,或者最好“仅”创建一个“新的请求变量”(在使用malloc的堆上),它将“专用”每个工作线程&请求(并且在完成为请求提供服务后,还在每个工作线程中释放它)?

5)请随意向我表明您可能会想到这段代码的任何其他评论(好的或坏的)。

修改

嘿,大家好,

一些额外问题:

除了生产者和工作者线程之外,还有另一个THREAD,称为 listener ,只有TASK才能插入到达链表(FIFO队列)的请求,所以它实际上并不是前面提到的制片人的任务。

所以我的新问题是:

8)关于我的程序的其他信息,我用信号量组成的“信号机制”是否有效?

9)生产者和监听器线程管理的链表有两个全局指针sem_postfront分别指向链表的头部和尾部(列表的头部是第一个)请求处理。)

下面是监听器线程执行的插入函数的实现,以及生产者线程执行的“提取”功能。

为了在“队列”(链接列表)上同步这两个线程,我使用了一个名为qmutex的共享互斥锁。

我的问题是,关于下面的两个代码,在每个函数中“放置”互斥锁(锁定和解锁)的“最佳”位置在哪里?

感谢分配,

盖。

插入功能:

rear

提取功能:

void insertion(void *toInsert)
{

    struct getInfo *req = (struct getInfo *)toInsert;
        newNode = (N*)malloc(sizeof(N));
        newNode->req = req;
        newNode->next = NULL;

    // WHERE SHULD I LOCK (AND UNLOCK) THE  QUEUE MUTEX ????????????????????????
        if(front == NULL)
    {
        front = newNode;
        printf("empty list - insert as head \n");     
    }

        else
    {
        rear->next = newNode;
        printf(" NOT AN EMPTY list - insert as last node \n");
    }

        rear = newNode;
}  // end of insertion

2 个答案:

答案 0 :(得分:5)

不是直接回答您的问题,首先,这里是对典型方法的描述:

您有一种队列或列表,您可以在其中添加工作数据。无论何时添加一组工作数据,首先要锁定互斥锁,添加数据,发出条件变量信号,然后解锁互斥锁。

然后,您的工作线程会锁定互斥锁,并在队列为空时等待循环中的条件。发送信号时,一个或多个工作人员将被唤醒,但只有一个(一次)将获取互斥锁。锁定互斥锁后,“获胜者”会检查队列中是否存在某些内容,将其解压缩,解锁互斥锁,并执行必要的工作。在解锁互斥锁之后,其他线程也可能会被唤醒(并且如果条件被广播将会唤醒),并且将从队列中提取下一部分工作,或者如果队列为空则返回等待。

在代码中,它看起来有点像这样:

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>

#define WORKER_COUNT 3

pthread_mutex_t mutex;
pthread_cond_t cond;
pthread_t workers[WORKER_COUNT];

static int queueSize = 0;

static void *workerFunc(void *arg)
{
    printf("Starting worker %d\n", (int)arg);
    while(1) {
        pthread_mutex_lock(&mutex);
        while(queueSize < 1) {
            pthread_cond_wait(&cond, &mutex);
        }
        printf("Worker %d woke up, processing queue #%d\n", (int)arg, queueSize);
        //Extract work from queue
        --queueSize;
        pthread_mutex_unlock(&mutex);
        //Do work
        sleep(1);
    }
}

int main()
{
    int i;

    pthread_mutex_init(&mutex, 0);
    pthread_cond_init(&cond, 0);

    for(i=0; i<WORKER_COUNT; ++i) {
        pthread_create(&(workers[i]), 0, workerFunc, (void*)(i+1));
    }

    sleep(1);
    pthread_mutex_lock(&mutex);
    //Add work to queue
    queueSize = 5;
    pthread_cond_broadcast(&cond);
    pthread_mutex_unlock(&mutex);

    sleep(10);

    return 0;
}

(我在线程之后省略了清理工作,并且将工作人员号码传递给线程很快而且很脏,但在这种情况下有效。)

在这里,工作人员将被pthread_cond_broadcast()唤醒,并且只要队列中存在某些东西就会运行(直到queueSize返回0 - 想象还有一个实际的队列),然后回去等。

回到问题:

1:互斥锁和保护变量(这里是queueSize)负责处理这个问题。您还需要保护变量,因为其他原因也会使您的线程 被唤醒(所谓的虚假唤醒,请参阅http://linux.die.net/man/3/pthread_cond_wait)。

2:如果你打电话给pthread_mutex_lock(),那么被唤醒的线程就像任何其他线程一样攻击互斥锁。

3:我不确定为什么你需要向生产者发出可用工作线程数量的信号?

4:队列需要可以从你的生产者和消费者那里访问 - 但是仍然可以用各种方式用函数(或类,如果你使用的是C ++)封装。

5:我希望上述内容足够吗?

6:pthread_cond_wait()的事情是它可能有虚假的唤醒。也就是说,即使你没有发出信号,它也可能会醒来。因此,您需要一个保护变量(我的代码示例中while()周围的pthread_cond_wait()循环),以确保实际上 是唤醒的理由,{{{ 1}}返回。然后使用与条件使用的相同的互斥锁保护保护变量(以及您需要提取的任何工作数据),然后您可以确定只有一个线程可以完成每项工作。

7:我不是让生产者进入睡眠状态,而是让它添加它可以提取到工作队列的任何数据。如果队列已满,那么它应该进入休眠状态,否则它应该继续添加内容。

8:使用你的Listener线程,我真的不明白为什么你甚至需要你的Producer线程。为什么不让工人自己打电话给pthread_cond_wait()

9:您需要保护对列表变量的所有访问。也就是说,在extract_element()中,在您首次访问insertion()之前锁定互斥锁,并在上次访问front后将其解锁。同样的事情在rear中 - 虽然你需要重写函数,以便在队列为空时也有一个有效的返回值。

答案 1 :(得分:1)

想要确定上一期和其他事项,所以新问题是:

1)如果我仍然想“坚持”我的线程实现,意思是在我写的时候使用互斥锁和条件变量,我怎么能确定当生产者线程只调用pthread_cond_signal时,一个线程将继续run(从pthread_cond_wait之后的指令开始)?
我是否需要添加另一个检查或使用另一个变量,或者(我更喜欢)只使用pthread_cond_wait / signal的通用机制?

注意:我使用pthread_cond_broadcast来“模拟”pthread_cond_signal解除阻塞多个线程的情况。

2)澄清我对该计划的“逻辑”:

生成器线程在分配请求时递减信号量的原因,相反,工作线程增加它的值,是为了使生成器线程在信号量上“进入睡眠状态”,如果所有工作线程都忙 - - &GT;意思是“等待提取和分配过程”,直到(至少)其中一个工作线程可用于处理请求。 这是我想要实现的一个很好的实现,还是有更好的方法呢?

再次感谢分配,

盖。