代码之家  ›  专栏  ›  技术社区  ›  kizzx2

在pthread中,如何将信号可靠地传递给另一个线程?

  •  12
  • kizzx2  · 技术社区  · 15 年前

    我正在尝试用pthread编写一个简单的线程池程序。然而,似乎 pthread_cond_signal 不阻塞,这会造成问题。例如,假设我有一个“生产者-消费者”计划:

    pthread_cond_t my_cond = PTHREAD_COND_INITIALIZER;
    pthread_mutex_t my_cond_m = PTHREAD_MUTEX_INITIALIZER;
    
    void * liberator(void * arg)
    {
        // XXX make sure he is ready to be freed
        sleep(1);
    
        pthread_mutex_lock(&my_cond_m);
        pthread_cond_signal(&my_cond);
        pthread_mutex_unlock(&my_cond_m);
    
        return NULL;
    }
    
    int main()
    {
        pthread_t t1;
        pthread_create(&t1, NULL, liberator, NULL);
    
        // XXX Don't take too long to get ready. Otherwise I'll miss 
        // the wake up call forever
        //sleep(3);
    
        pthread_mutex_lock(&my_cond_m);
        pthread_cond_wait(&my_cond, &my_cond_m);
        pthread_mutex_unlock(&my_cond_m);
    
        pthread_join(t1, NULL);
    
        return 0;
    }
    

    如二所述 XXX 马克,如果我拿走 sleep 呼叫,然后 main() 可能会因为错过了叫醒电话而暂停 liberator() . 当然, 睡觉 也不是一个非常可靠的方法来确保这一点。

    在实际情况下,这将是一个工作线程告诉管理器线程它已经准备好工作了,或者是管理器线程通知新工作可用。

    你如何在pthread中可靠地做到这一点?


    详述

    @博雷利德的回答是一种工作,但他对这个问题的解释可能会更好。我建议任何关注这个问题的人阅读评论中的讨论,了解正在发生的事情。

    特别是,我自己会修改他的答案和这样的代码示例,以使这更清楚。(因为博雷利德的原始答案,在汇编和工作时,使我很困惑)

    // In main
    pthread_mutex_lock(&my_cond_m);
    
    // If the flag is not set, it means liberator has not 
    // been run yet. I'll wait for him through pthread's signaling 
    // mechanism
    
    // If it _is_ set, it means liberator has been run. I'll simply 
    // skip waiting since I've already synchronized. I don't need to 
    // use pthread's signaling mechanism
    if(!flag) pthread_cond_wait(&my_cond, &my_cond_m);
    
    pthread_mutex_unlock(&my_cond_m);
    
    // In liberator thread
    pthread_mutex_lock(&my_cond_m);
    
    // Signal anyone who's sleeping. If no one is sleeping yet, 
    // they should check this flag which indicates I have already 
    // sent the signal. This is needed because pthread's signals 
    // is not like a message queue -- a sent signal is lost if 
    // nobody's waiting for a condition when it's sent.
    // You can think of this flag as a "persistent" signal
    flag = 1;
    pthread_cond_signal(&my_cond);
    pthread_mutex_unlock(&my_cond_m);
    
    2 回复  |  直到 15 年前
        1
  •  7
  •   Borealid    15 年前

    使用同步变量。

    main :

    pthread_mutex_lock(&my_cond_m);
    while (!flag) {
        pthread_cond_wait(&my_cond, &my_cond_m);
    }
    pthread_mutex_unlock(&my_cond_m);
    

    线程中:

    pthread_mutex_lock(&my_cond_m);
    flag = 1;
    pthread_cond_broadcast(&my_cond);
    pthread_mutex_unlock(&my_cond_m);
    

    对于生产者-消费者问题,这将是消费者在缓冲区为空时睡眠,生产者在缓冲区满时睡眠。记得把锁取下来 之前 访问全局变量。

        2
  •  4
  •   kizzx2    15 年前

    我找到了解决办法 here . 对我来说,要理解问题的一个棘手之处是:

    1. 生产者和消费者必须能够双向沟通。任何一种方法都是不够的。
    2. 这种双向通信可以打包成一个pthread条件。

    举例来说,上面提到的博客文章证明了这实际上是有意义和可取的行为:

    pthread_mutex_lock(&cond_mutex);
    pthread_cond_broadcast(&cond):
    pthread_cond_wait(&cond, &cond_mutex);
    pthread_mutex_unlock(&cond_mutex);
    

    其理念是,如果生产商和消费者都采用这种逻辑,他们中的任何一个都可以先睡觉,因为每个人都可以唤醒另一个角色。换一种说法,在一个典型的生产者-消费者场景中——如果消费者需要睡觉,那是因为生产者需要醒来,反之亦然。在单个pthread条件下封装此逻辑是有意义的。

    当然,上面的代码有一种意想不到的行为,即当工作线程实际上只想唤醒生产者时,它还会唤醒另一个正在休眠的工作线程。这可以通过简单的变量检查来解决,如@borealid建议的那样:

    while(!work_available) pthread_cond_wait(&cond, &cond_mutex);
    

    在工人广播时,所有工人线程都将被唤醒,但一个接一个(因为隐式互斥锁在 pthread_cond_wait )因为其中一个工作线程将消耗工作(设置 work_available 回到 false ,当其他工作线程唤醒并实际开始工作时,工作将不可用,因此工作线程将再次睡眠。

    以下是我测试的一些注释代码,适用于任何感兴趣的人:

    // gcc -Wall -pthread threads.c -lpthread
    
    #include <stdio.h>
    #include <pthread.h>
    #include <stdlib.h>
    #include <assert.h>
    
    pthread_cond_t my_cond = PTHREAD_COND_INITIALIZER;
    pthread_mutex_t my_cond_m = PTHREAD_MUTEX_INITIALIZER;
    
    int * next_work = NULL;
    int all_work_done = 0;
    
    void * worker(void * arg)
    {
        int * my_work = NULL;
    
        while(!all_work_done)
        {
            pthread_mutex_lock(&my_cond_m);
    
            if(next_work == NULL)
            {
                // Signal producer to give work
                pthread_cond_broadcast(&my_cond);
    
                // Wait for work to arrive
                // It is wrapped in a while loop because the condition 
                // might be triggered by another worker thread intended 
                // to wake up the producer
                while(!next_work && !all_work_done)
                    pthread_cond_wait(&my_cond, &my_cond_m);
            }
    
            // Work has arrived, cache it locally so producer can 
            // put in next work ASAP
            my_work = next_work;
            next_work = NULL;
            pthread_mutex_unlock(&my_cond_m);
    
            if(my_work)
            {
                printf("Worker %d consuming work: %d\n", (int)(pthread_self() % 100), *my_work);
                free(my_work);
            }
        }
    
        return NULL;
    }
    
    int * create_work()
    {
        int * ret = (int *)malloc(sizeof(int));
        assert(ret);
        *ret = rand() % 100;
        return ret;
    }
    
    void * producer(void * arg)
    {
        int i;
    
        for(i = 0; i < 10; i++)
        {
            pthread_mutex_lock(&my_cond_m);
            while(next_work != NULL)
            {
                // There's still work, signal a worker to pick it up
                pthread_cond_broadcast(&my_cond);
    
                // Wait for work to be picked up
                pthread_cond_wait(&my_cond, &my_cond_m);
            }
    
            // No work is available now, let's put work on the queue
            next_work = create_work();
            printf("Producer: Created work %d\n", *next_work);
    
            pthread_mutex_unlock(&my_cond_m);
        }
    
        // Some workers might still be waiting, release them
        pthread_cond_broadcast(&my_cond);
    
        all_work_done = 1;
        return NULL;
    }
    
    int main()
    {
        pthread_t t1, t2, t3, t4;
    
        pthread_create(&t1, NULL, worker, NULL);
        pthread_create(&t2, NULL, worker, NULL);
        pthread_create(&t3, NULL, worker, NULL);
        pthread_create(&t4, NULL, worker, NULL);
    
        producer(NULL);
    
        pthread_join(t1, NULL);
        pthread_join(t2, NULL);
        pthread_join(t3, NULL);
        pthread_join(t4, NULL);
        return 0;
    }
    
    推荐文章