生产者消费者问题

本文介绍了一个经典的并发编程问题——生产者消费者模式,并提供了一个具体的实现案例。通过使用条件变量和互斥锁,解决了生成方和使用者之间的同步问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://zhanglibin.blog.51cto.com/2553477/610354
From:《POSIX多线程编程》
生成方和使用者问题
    并发编程中收集了许多标准的众所周知的问题,生成方和使用者问题只是其中的一个问题。此问题涉及到一个大小限定的缓冲区和两类线程(生成方和使用者),生成方将项放入缓冲区中,然后使用者从缓冲区中取走项。
    生成方必须在缓冲区中有可用空间之后才能向其中放置内容。使用者必须在生成方向缓冲区中写入之后才能从中提取内容。
    条件变量表示一个等待某个条件获得信号的线程队列。示例4-11 中包含两个此类队列。一个队列(less)针对生成方,用于等待缓冲区中出现空位置。另一个队列(more)针对使用者,用于等待从缓冲槽位的空位置中提取其中包含的信息。该示例中还包含一个互斥锁,因为描述该缓冲区的数据结构一次只能由一个线程访问。
 
示例4-11生成方和使用者的条件变量问题
typedef struct
{
    char buf[BSIZE];
    int occupied;
    int nextin;
    int nextout;
    pthread_mutex_t mutex;
    pthread_cond_t more;
    pthread_cond_t less;
} buffer_t;
 
buffer_t buffer;
 
    如示例4-12中所示,生成方线程获取该互斥锁以保护buffer数据结构,然后,缓冲区确定是否有空间可用于存放所生成的项。如果没有可用空间,生成方线程会调用pthread_cond_wait()。pthread_cond_wait()会导致生成方线程连接正在等待less条件获得信号的线程队列。less表示缓冲区中的可用空间。
    于此同时,在调用pthread_cond_wait()的过程中,该线程会释放互斥锁的锁定。正在等待的生成方线程依赖于使用者线程在条件为真时发出信号,如示例4-12中所示。该条件获得信号时,将会唤醒等待less的第一个线程。但是,该线程必须再次锁定互斥锁,然后才能从pthread_cond_wait()返回。
    获取互斥锁可确保该线程再次以独占方式访问缓冲区的数据结构。该线程随后必须检查缓冲区中是否确实存在可用空间。如果空间可用,该线程会向下一个可用的空位置中进行写入。
    与此同时,使用者线程可能正在等待项出现在缓冲区中。这些线程正在等待条件变量more。刚在缓冲区中存储内容的生成方线程会调用pthread_cond_signal()
以唤醒下一个正在等待的使用者。如果没有正在等待的使用者,此调用将不起作用。
    最后,生成方线程会解除锁定互斥锁,从而允许其他线程处理缓冲区的数据结构。
 
示例4-12生成方和使用者问题:生成方
void producer(buffer_t * b, char item)
{
    pthread_mutex_lock(&b->mutex);
    while(b->occupied == BSIZE)
        pthread_cond_wait(&b->less, &b->mutex);
    
    b->buf[b->nextin++] = item;
    b->nextin %= BSIZE;
    b->occupied++;
    pthread_cond_signal(&b->more);
    pthread_mutex_unlock(&b->mutex);
}
示例4-13生成方和使用者问题:使用者
char consumer(buffer_t * b)
{
    char item;
    pthread_mutex_lock(&b->mutex);
    while(b->occupied <= 0)
        pthread_cond_wait(&b->more, &b->mutex);
    item = b->buf[b->nextout++];
    b->nextout %= BSIZE;
    b->occupied--;
    pthread_cond_signal(&b->less);
    pthread_mutex_unlock(&b->mutex);
    return (item);
}
 
2011年11月4日
在转载这篇文章几个月后,一个朋友的项目中需要使用这个模式。并且,我进行的一个AIX项目,也需要这个模式。我将其封装,形成了以下两个文件。
 
List.h
  1. /** 
  2.  * @file    List.h 
  3.  * @author  George Smith Patton 
  4.  *          张立斌  
  5.  */   
  6. #ifndef BIN_LIST_H 
  7. #define BIN_LIST_H 
  8.  
  9. #include <pthread.h> 
  10.  
  11. #define CAST(Cls, obj) ((Cls)(obj)) 
  12. #define OFFSET_OF(Cls, member) \ 
  13.     CAST(unsigned long, &(CAST(Cls *, 0)->member)) 
  14. #define CONTAINER_OF(pObj, Cls, member) \ 
  15.     CAST(Cls *, CAST(unsigned long, pObj)-OFFSET_OF(Cls, member)) 
  16.  
  17. #ifdef  __cplusplus 
  18. #define STATIC_INLINE   inline 
  19. #else 
  20. #define STATIC_INLINE   static inline 
  21. #endif 
  22.  
  23. typedef struct ListNode 
  24.     struct ListNode * prev; 
  25.     struct ListNode * next; 
  26. } ListNode, List; 
  27.  
  28. #define LIST_INIT(list) {&(list), &(list)} 
  29.  
  30. STATIC_INLINE void ListInit(List * const list) 
  31.     list->prev = list; 
  32.     list->next = list; 
  33.  
  34. STATIC_INLINE void __ListAdd(ListNode * const prev, ListNode * const next, 
  35.     ListNode * const node) 
  36.     node->prev = prev; 
  37.     node->next = next; 
  38.     prev->next = node; 
  39.     next->prev = node; 
  40.  
  41. STATIC_INLINE void ListAddHead(List * const list,  
  42.     ListNode * const node) 
  43.     __ListAdd(list, list->next, node); 
  44.  
  45. STATIC_INLINE void ListAddTail(List * const list,  
  46.     ListNode * const node) 
  47.     __ListAdd(list->prev, list, node); 
  48.  
  49. STATIC_INLINE void __ListDel(ListNode * const prev, ListNode * const next) 
  50.     prev->next = next; 
  51.     next->prev = prev; 
  52.  
  53. STATIC_INLINE ListNode * ListDelNode(const ListNode * const node) 
  54.     __ListDel(node->prev, node->next); 
  55.     return CAST(ListNode *, node); 
  56.  
  57. STATIC_INLINE ListNode * ListDelHead(const List * const list) 
  58.     return ListDelNode(list->next); 
  59.  
  60. STATIC_INLINE ListNode * ListDelTail(const List * const list) 
  61.     return ListDelNode(list->prev); 
  62.  
  63. typedef struct 
  64.     volatile List list; 
  65.     pthread_mutex_t mutex; 
  66.     pthread_cond_t condEmpty; 
  67.     pthread_cond_t condFull; 
  68.     volatile unsigned size; 
  69.     unsigned maxSize; 
  70.     volatile unsigned producerSize; 
  71. } ProducerConsumerList; 
  72.  
  73. #define PRODUCER_CONSUMER_INIT(pcList, maxSz, producerSz) \ 
  74.     {LIST_INIT(*CAST(List *, &(pcList).list)), PTHREAD_MUTEX_INITIALIZER, PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER, \ 
  75.     0, (maxSz), producerSz} 
  76.  
  77. #ifdef  __cplusplus 
  78. extern "C" { 
  79. #endif 
  80.  
  81. int ProducerConsumerListInit(ProducerConsumerList * const list, const unsigned maxSize, const unsigned producerSize); 
  82.  
  83. void ProducerConsumerListAddHead(ProducerConsumerList * const list, volatile ListNode * const node); 
  84.  
  85. void ProducerConsumerListAddTail(ProducerConsumerList * const list, volatile ListNode * const node); 
  86.  
  87. ListNode * ProducerConsumerListDelHead(ProducerConsumerList * const list); 
  88.  
  89. ListNode * ProducerConsumerListDelTail(ProducerConsumerList * const list); 
  90.  
  91. void ProducerConsumerListOneProducerEnd(ProducerConsumerList * const list); 
  92.  
  93. void ProducerConsumerListDel(ProducerConsumerList * const list); 
  94.  
  95. #ifdef  __cplusplus 
  96. #endif 
  97.  
  98. #endif 
List.c
  1. /** 
  2.  * @file    List.c 
  3.  * @author  George Smith Patton 
  4.  *          张立斌  
  5.  */ 
  6. #include "List.h" 
  7.  
  8. int ProducerConsumerListInit(ProducerConsumerList * const list, const unsigned maxSize, const unsigned producerSize) 
  9.     int result; 
  10.     ListInit(CAST(List *, &list->list)); 
  11.     result = pthread_mutex_init(&list->mutex, 0); 
  12.     if(result) 
  13.         return result; 
  14.     result = pthread_cond_init(&list->condEmpty, 0); 
  15.     if(result) 
  16.         return result; 
  17.     result = pthread_cond_init(&list->condFull, 0); 
  18.     if(result) 
  19.         return result; 
  20.     list->size = 0; 
  21.     list->maxSize = maxSize; 
  22.     list->producerSize = producerSize; 
  23.     return result; 
  24.  
  25. void ProducerConsumerListAddHead(ProducerConsumerList * const list, volatile ListNode * const node) 
  26.     pthread_mutex_lock(&list->mutex); 
  27.     while(list->size == list->maxSize) 
  28.         pthread_cond_wait(&list->condFull, &list->mutex); 
  29.     __ListAdd(CAST(ListNode *, &list->list), list->list.next, CAST(ListNode *, node)); 
  30.     ++list->size; 
  31.     pthread_cond_signal(&list->condEmpty); 
  32.     pthread_mutex_unlock(&list->mutex); 
  33.  
  34. void ProducerConsumerListAddTail(ProducerConsumerList * const list, volatile ListNode * const node) 
  35.     pthread_mutex_lock(&list->mutex); 
  36.     while(list->size == list->maxSize) 
  37.         pthread_cond_wait(&list->condFull, &list->mutex); 
  38.     __ListAdd(list->list.prev, CAST(ListNode *, &list->list), CAST(ListNode *, node)); 
  39.     ++list->size; 
  40.     pthread_cond_signal(&list->condEmpty); 
  41.     pthread_mutex_unlock(&list->mutex); 
  42.  
  43. ListNode * ProducerConsumerListDelHead(ProducerConsumerList * const list) 
  44.     pthread_mutex_lock(&list->mutex); 
  45.     while(0 == list->size) 
  46.     { 
  47.         if(0 == list->producerSize) 
  48.         { 
  49.             pthread_mutex_unlock(&list->mutex); 
  50.             return 0; 
  51.         } 
  52.         else 
  53.             pthread_cond_wait(&list->condEmpty, &list->mutex); 
  54.     } 
  55.     ListNode * result = ListDelNode(list->list.next); 
  56.     --list->size; 
  57.     pthread_cond_signal(&list->condFull); 
  58.     pthread_mutex_unlock(&list->mutex); 
  59.     return result; 
  60.  
  61. ListNode * ProducerConsumerListDelTail(ProducerConsumerList * const list) 
  62.     pthread_mutex_lock(&list->mutex); 
  63.     while(0 == list->size) 
  64.     { 
  65.         if(0 == list->producerSize) 
  66.         { 
  67.             pthread_mutex_unlock(&list->mutex); 
  68.             return 0; 
  69.         } 
  70.         else 
  71.             pthread_cond_wait(&list->condEmpty, &list->mutex); 
  72.     } 
  73.     ListNode * result = ListDelNode(list->list.prev); 
  74.     --list->size; 
  75.     pthread_cond_signal(&list->condFull); 
  76.     pthread_mutex_unlock(&list->mutex); 
  77.     return result; 
  78.  
  79. void ProducerConsumerListOneProducerEnd(ProducerConsumerList * const list) 
  80.     pthread_mutex_lock(&list->mutex); 
  81.     --list->producerSize; 
  82.     pthread_cond_signal(&list->condEmpty); 
  83.     pthread_mutex_unlock(&list->mutex); 
  84.  
  85. void ProducerConsumerListDel(ProducerConsumerList * const list) 
  86.     pthread_mutex_destroy(&list->mutex); 
  87.     pthread_cond_destroy(&list->condEmpty); 
  88.     pthread_cond_destroy(&list->condFull); 
并进行了简单的测试:
threadTest.c
 
  1. /** 
  2.  * @file    threadTest.c 
  3.  * @author  George Smith Patton 
  4.  *          张立斌  
  5.  */ 
  6. #include "List.h" 
  7.  
  8. #include <unistd.h> 
  9.  
  10. #include <stdio.h> 
  11. #include <stdlib.h> 
  12.  
  13. typedef struct 
  14.     ListNode node; 
  15.     int a; 
  16. } AListNode; 
  17.  
  18. static inline AListNode * AListNodeMalloc(int a) 
  19.     AListNode * result; 
  20.     for(;;) 
  21.     { 
  22.         result = CAST(AListNode *, malloc(sizeof(AListNode))); 
  23.         if(result) 
  24.             break
  25.         perror(__func__); 
  26.         sleep(1); 
  27.     } 
  28.     result->a = a; 
  29.     printf("%s %lX %d\n", __func__, CAST(unsigned long, result), result->a); 
  30.     return result; 
  31.  
  32. static inline void AListNodeFree(const AListNode * const a) 
  33.     printf("%s %lX %d\n", __func__, CAST(unsigned long, a), a->a); 
  34.     free(CAST(AListNode *, a)); 
  35.  
  36. typedef struct 
  37.     ProducerConsumerList * list; 
  38. } ThreadArg; 
  39.  
  40. void * thread1(void * arg); 
  41. void * thread2(void * arg); 
  42.  
  43. void * thread1(void * arg) 
  44.     ThreadArg * threadArg = CAST(ThreadArg *, arg); 
  45.     int index; 
  46.     int arr[] = {2, 3, 5, 7, 11, 13, 19, 23}; 
  47.     for(index=0; index<8; ++index) 
  48.     { 
  49.         AListNode * a = AListNodeMalloc(arr[index]); 
  50.         ProducerConsumerListAddTail(threadArg->list, &a->node); 
  51.     } 
  52.     ProducerConsumerListOneProducerEnd(threadArg->list); 
  53.     return 0; 
  54.  
  55. void * thread2(void * arg) 
  56.     ThreadArg * threadArg = CAST(ThreadArg *, arg); 
  57.     ListNode * node; 
  58.     while(0 != (node = ProducerConsumerListDelHead(threadArg->list))) 
  59.     { 
  60.         AListNode * a = CONTAINER_OF(node, AListNode, node); 
  61.         AListNodeFree(a); 
  62.     } 
  63.     return 0; 
  64.  
  65. int main(void
  66.     int result; 
  67.     ProducerConsumerList list = PRODUCER_CONSUMER_INIT(list, -1, 1); 
  68.     ThreadArg threadArg = {&list}; 
  69.     pthread_t t1Id = 0; 
  70.     pthread_t t2Id = 0; 
  71.     result = pthread_create(&t1Id, 0, thread1, &threadArg); 
  72.     if(result) 
  73.         goto End; 
  74.     result = pthread_create(&t2Id, 0, thread2, &threadArg); 
  75.     if(result) 
  76.         goto End; 
  77.     if(t1Id) 
  78.     { 
  79.         result = pthread_join(t1Id, 0); 
  80.         if(result) 
  81.             goto End; 
  82.     } 
  83.     if(t2Id) 
  84.     { 
  85.         result = pthread_join(t2Id, 0); 
  86.         if(result) 
  87.             goto End; 
  88.     } 
  89.     ProducerConsumerListDel(&list); 
  90. End: 
  91.     return result; 
1、实验目的 (1)掌握基本的同步互斥算法,理解生产者消费者同步的问题模型。 (2)了解Windows 2000/XP中多线程的并发执行机制,线程间的同步和互斥。 (3)学习使用Windows2000/XP中基本的同步对象,掌握相应的API。 2、实验要求 (1)创建生产者消费者线程 在Windows2000环境下,创建一个控制台进程,在此进程中创建n个线程来模拟生产者或者消费者。这些线程的信息由本程序定义的“测试用例文件”中予以指定。 该文件的格式和含义如下: 3 1 P 3 2 P 4 3 C 4 1 4 P 2 5 C 3 1 2 4 第一行说明程序中设置几个临界区,其余每行分别描述了一个生产者或者消费者线程的信息。每一行的各字段间用Tab键隔开。不管是消费者还是生产者,都有一个对应的线程号,即每一行开始字段那个整数。第二个字段用字母P或者C区分是生产者还是消费者。第三个字段表示在进入相应线程后,在进行生产和消费动作前的休眠时间,以秒计时;这样做的目的是可以通过调整这一列参数,控制开始进行生产和消费动作的时间。如果是代表生产者,则该行只有三个字段。如果代表消费者,则该行后边还有若干字段,代表要求消费的产品所对应的生产者的线程号。所以务必确认这些对应的线程号存在并且该线程代表一个生产者。 (2)生产和消费的规则 在按照上述要求创建线程进行相应的读写操作时,还需要符合以下要求: ①共享缓冲区存在空闲空间时,生产者即可使用共享缓冲区。 ②从上边的测试数据文件例子可以看出,某一生产者生产一个产品后,可能不止一个消费者,或者一个消费者多次地请求消费该产品。此时,只有当所有的消费需求都被满足以后,该产品所在的共享缓冲区才可以被释放,并作为空闲空间允许新的生产者使用。 ③每个消费者线程的各个消费需求之间存在先后顺序。例如上述测试用例文件包含一行信息“5 C 3 l 2 4”,可知这代表一个消费者线程,该线程请求消费1,2,4号生产者线程生产的产品。而这种消费是有严格顺序的,消费1号线程产品的请求得到满足后才能继续往下请求2号生产者线程的产品。 ④要求在每个线程发出读写操作申请、开始读写操作和结束读写操作时分别显示提示信息。 (3)相关基础知识 本实验所使用的生产者消费者模型具有如下特点: 本实验的多个缓冲区不是环形循环的,也不要求按顺序访问。生产者可以把产品放到目前某一个空缓冲区中。 消费者只消费指定生产者的产品。 在测试用例文件中指定了所有的生产和消费的需求,只有当共享缓冲区的数据满足了所有关于它的消费需求后,此共享缓冲区才可以作为空闲空间允许新的生产者使用。 本实验在为生产者分配缓冲区时各生产者间必须互斥,此后各个生产者的具体生产活动可以并发。而消费者之间只有在对同一产品进行消费时才需要互斥,同时它们在消费过程结束时需要判断该消费对象是否已经消费完毕并清除该产品。 Windows用来实现同步和互斥的实体。在Windows中,常见的同步对象有:信号量(Semaphore)、互斥量(Mutex)、临界段(CriticalSection)等。使用这些对象都分为三个步骤,一是创建或者初始化:接着请求该同步对象,随即进入临界区,这一步对应于互斥量的上锁;最后释放该同步对象,这对应于互斥量的解锁。这些同步对象在一个线程中创建,在其他线程中都可以使用,从而实现同步互斥。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值