linux多线程学习(七)——实现“生产者和消费者”

本文通过信号量机制解决生产者消费者问题,详细介绍了如何利用信号量实现线程间互斥同步,通过创建缓冲区和两个线程,演示生产者如何放入产品,消费者如何取出产品的过程,同时确保缓冲区不会溢出或为空的情况。

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


在上一篇文章中,利用信号量实现了线程间的互斥,这一篇将要利用信号量的互斥同步机制来实现一个经典实例,就是“生产者和消费者”。

1、简单描述生产者和消费者的问题。

有一个缓冲区和两个线程:生产者和消费者。生产者把产品放入缓冲区,而消费者从缓冲区中拿走。当缓冲区满时,生产者必须等待;另外,当缓冲区空时,消费者必须等待,并且缓冲区不能同时进行生产者和消费者的操作。

[cpp]  view plain copy
  1. #include <stdio.h>    
  2. #include <stdlib.h>    
  3. #include <pthread.h>    
  4. #include <semaphore.h>    
  5. #include <errno.h>    
  6. #include <fcntl.h>    
  7. #include <string.h>  
  8.     
  9. #define FIFO "my_fifo"    
  10. #define N    10    
  11. #define return_if_fail(p) if((p) == 0){printf ("[%s]:func error!/n", __func__);return;}    
  12.     
  13. typedef struct _PrivInfo    
  14. {    
  15.   sem_t avail;    
  16.   sem_t full;    
  17.   sem_t mutex;  
  18.   char  buf_r[255];    
  19.   time_t end_time;    
  20.   int fd;    
  21. }PrivInfo;    
  22.     
  23. static void info_init (PrivInfo* thiz);    
  24. static void info_destroy (PrivInfo* thiz);    
  25. static void* productor (PrivInfo* thiz);    
  26. static void* consumer (PrivInfo* thiz);    
  27.     
  28. int main (int argc, char* argv[])    
  29. {    
  30.   pthread_t pt_1 = 0;    
  31.   pthread_t pt_2 = 0;    
  32.   int ret = 0;    
  33.   PrivInfo* thiz = NULL;    
  34.       
  35.   thiz = (PrivInfo*) malloc (sizeof (PrivInfo));    
  36.   if (thiz == NULL)    
  37.   {    
  38.     printf ("[%s]: Failed to malloc PrivInfo./n", __func__);    
  39.     return -1;    
  40.   }    
  41.     
  42.   info_init (thiz);    
  43.     
  44.   ret = pthread_create (&pt_1, NULL, (void*)productor, thiz);    
  45.   if (ret != 0)    
  46.   {    
  47.     perror ("pthread_1_create:");    
  48.   }    
  49.     
  50.   ret = pthread_create (&pt_2, NULL, (void*)consumer, thiz);    
  51.   if (ret != 0)    
  52.   {    
  53.     perror ("pthread_2_func:");    
  54.   }    
  55.     
  56.   pthread_join (pt_1, NULL);    
  57.   pthread_join (pt_2, NULL);    
  58.     
  59.   info_destroy (thiz);    
  60.   thiz = NULL;    
  61.     
  62.   return 0;    
  63. }    
  64.     
  65. static void info_init (PrivInfo* thiz)    
  66. {    
  67.   return_if_fail (thiz != NULL);    
  68.     
  69.   thiz->end_time = time(NULL) + 10;    
  70.       
  71.   sem_init (&thiz->avail, 0, N);    
  72.   sem_init (&thiz->full, 0, 0);    
  73.   sem_init (&thiz->mutex, 0, 1);    
  74.     
  75.   if (mkfifo (FIFO, O_CREAT|O_EXCL) && (errno != EEXIST))    
  76.   {    
  77.     printf ("cannot create fifo server./n");    
  78.   }    
  79.   
  80.   printf ("Prepareing for reading bytes.../n");    
  81.     
  82.   memset (thiz->buf_r, 0, sizeof (thiz->buf_r));    
  83.     
  84.   thiz->fd = open (FIFO, O_RDWR|O_NONBLOCK, 0777);    
  85.   if (thiz->fd == -1)    
  86.   {    
  87.     perror ("open FIFO:");    
  88.     info_destroy (thiz);    
  89.     exit (1);    
  90.   }    
  91.     
  92.   return ;    
  93. }    
  94.     
  95. static void info_destroy (PrivInfo* thiz)    
  96. {    
  97.   return_if_fail (thiz != NULL);    
  98.     
  99.   sem_destroy (&thiz->avail);    
  100.   sem_destroy (&thiz->full);    
  101.   sem_destroy (&thiz->mutex);    
  102.     
  103.   free (thiz);    
  104.   thiz = NULL;    
  105.     
  106.   return;    
  107. }    
  108.     
  109. static void* productor (PrivInfo* thiz)    
  110. {    
  111.   return_if_fail (thiz != NULL);    
  112.     
  113.  int ret = 0;    
  114.     
  115.   while (time (NULL) < thiz->end_time)    
  116.   {    
  117.     sem_wait (&thiz->avail);    
  118.     sem_wait (&thiz->mutex);    
  119.     
  120.     if ((ret = write (thiz->fd, "hello", 5)) == -1)    
  121.     {    
  122.        if (errno == EAGAIN)    
  123.        {    
  124.            printf ("The FIFO has not been read, Please try later again./n");    
  125.        }    
  126.        else    
  127.        {    
  128.            printf ("write hello to the FIFO./n");    
  129.        }    
  130.     }    
  131.     
  132.     sem_post (&thiz->full);    
  133.     sem_post (&thiz->mutex);    
  134.     
  135.     sleep (1);    
  136.   }    
  137.     
  138.   return;    
  139. }    
  140.     
  141. static void* consumer (PrivInfo* thiz)    
  142. {    
  143.    return_if_fail (thiz != NULL);    
  144.     
  145.    int ret = 0;    
  146.     
  147.    while (time (NULL) < thiz->end_time)    
  148.   {    
  149.      sem_wait (&thiz->full);    
  150.      sem_wait (&thiz->mutex);    
  151.     
  152.      if ((ret = read(thiz->fd, thiz->buf_r, 255)) > 0)    
  153.      {    
  154.                   
  155.         printf ("read %s frm FIFO./n", thiz->buf_r);    
  156.       }    
  157.       else   
  158.       {  
  159.         if (errno == EAGAIN)    
  160.         {    
  161.            printf ("no data yet./n");     
  162.         }  
  163.       }  
  164.     
  165.       sem_post (&thiz->avail);    
  166.       sem_post (&thiz->mutex);    
  167.   
  168.       sleep(1);  
  169.   }    
  170.     
  171.   return;    
  172. }    

从上面的经典例子中,结合了多线程的同时性编程问题。学习线程算是告了一段落,在以后的编程应用中,希望能落实到实践中去,大家一起加油~~

(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、付费专栏及课程。

余额充值