视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
Linux下实现多线程的生产者消费者问题
2025-10-02 04:38:18 责编:小OO
文档
Linux下实现多线程的生产者消费者问题

一、原理的理解

生产者-消费者问题是一个经典的线程同步问题,该问题最早由Dijkstra提出,用以演示他提出的信号量机制。在同一个线程地址空间内执行的两个线程。生产者线程生产物品,然后将物品放置在一个空缓冲区中供消费者线程消费。消费者线程从缓冲区中获得物品,然后释放缓冲区。当生产者线程生产物品时,如果没有空缓冲区可用,那么生产者线程必须等待消费者线程释放出一个空缓冲区。当消费者线程消费物品时,如果没有满的缓冲区,那么消费者线程将被阻塞,直到新的物品被生产出来。

多个生产/消费者在有界缓冲上操作。它利用N个字节的共享内存作为有界循环缓冲区,利用写一字符模拟放一个产品,利用读一字符模拟消费一个产品。当缓冲区空时消费者应阻塞睡眠,而当缓冲区满时生产者应当阻塞睡眠。一旦缓冲区中有空单元,生产者线程就向空单元中入写字符,并报告写的内容和位置。一旦缓冲区中有未读过的字符,消费者线程就从该单元中读出字符,并报告读取位置。生产者不能向同一单元中连续写两次以上相同的字符,消费者也不能从同一单元中连续读两次以上相同的字符。

二、完成步骤

1、思路分析

本作业是完善课件上的线程综合实例的练习生产者-消费者问题,重构这个程序的框架,完成性能分析,使之进一步理解掌握Linux下线程的同步、通信以及互斥和多线程的安全问题。

一般情况下,解决互斥方法常用信号量和互斥锁,即semaphore和mutex,而解决这个问题,多采用一个类似资源槽的结构,每个槽位标示了指向资源的指针以及该槽位的状态,生产者和消费者互斥查询资源槽,判断是否有产品或者有空位可以生产,然后根据指针进行相应的操作。同时,为了告诉生产者或者消费者资源槽的情况,还要有一个消息传送机制,无论是管道还是线程通信。

然而,本次试验有几个特殊的要求:

A、循环缓冲。

B、除了stderr,stdout等外,只用小于2个的互斥锁、

C、放弃资源槽分配机制,采用额外的数据结构。

D、生产者一直持续生产,形成生产消费的良性循环。

首先,使用一个互斥锁,意味着资源槽机制就不能使用了。因为资源槽虽以用一个互斥锁完成,但是需要有额外的通信,如果使用管道通信,则管道也必须是互斥,这就不满足1个互斥锁的要求。其次,要求生产者一直生产,这就否定了另外一种方法:消费者、生产者的位置均平等,消费者消费的时候生产者不能生产,生产者生产的时候消费者不能消费。因此,就需要采用A要求,也就是循环链表的形式。

为了保证互斥要求,需要定义一个数据结构,这个数据结构包含两个指针,一个读一个写,同时有一个资源数目量,告诉生产者和消费者是否可以生产或者消费。由于该数据结构很小,因而可以对此结构互斥访问。同时,对于每组数据,都有一个标志位,表示此组数据是否被占用,生产者和消费者均可以先占用此位置然后完成相应的操作。

当消费者互斥访问此结构时,首先判断是否有数据可以取,如果没有,直接等待,若有数据可取,先更改标志位占用此数据,并将资源数目-1。然后交出互斥,把数据拷贝到自己缓冲区内,清空数据。当生产者访问时,首先判断有没有空位可以生产,如果没有,直接等待,若有数据可以生产,先判断该位是否被占用,如果没被占用,则占用此位置进行生产。生产完成后,将占用位改为未占用,同时将资源数目+1。这个过程可以如图所示:

2、重要函数和数据结构

(1)数据结构

#define N 5   // 生产者的数目 

#define N2 15   // 消费者数目 

#define M 20 // 缓冲数目

#define debug 0 //调试模式

#define nowait 1 //是否添加额外等待

pthread_mutex_t mutex; // 互斥信号量

int prod_id = 0;   //生产者id

int proc_id = 0; //消费者id

//成功以及失败次数计数

int Succ[N+N2];

int Fail[N+N2];

//用于生产的填充数据

char dt[30]="aaaaaaaaaabbbbbbbbbbcccccccccc";

int toexit=0;//退出标记

//共用数据区

struct data_struc

{

    int Occupied; //该位置是否被占用 无论生产还是消费

    char d[1024]; //数据

    struct data_struc* nx; //下一个指针

};

//互斥量

struct mtx

{

    struct data_struc* rd_p;

    struct data_struc* wr_p;

    int cnt; //产品数量

};

struct mtx *mymutex;

(2)函数说明

void printinfo();//调试函数,打印公用数据区的内容和状态

void Create_Empty_DS();//初始化循环链表

void *buy();//消费者

void *sell();//生产者

void *Recount();//数据统计,同时控制程序自动退出

3、程序流程图

A、主函数

B、统计线程

C、生产者线程

D、消费者线程

三、实验数据

1、程序运行截图

    虚拟机:VMware Workstation7.0,win7;硬件:i3,2G DDR1333;编译:gcc product.c –pg –lpthread;报告生成:gprof –b。

运行时截图:

CPU占用情况(无额外等待):

2、原始数据

由于程序要求使用gprof进行分析,而在机器配置较好的情况下,使用gprof可能分析不出每个函数执行时间,甚至连函数都分析不全,因而在实际代码中添加了一些空循环来增加cpu占用。

为了更便于观察,首先假设如下场景:消费者、生产者在执行各自动作前,均usleep(1),同时,消费者在成功消费之后,也会usleep(3)来为其他消费者让位。同时,也将测试没有任何等待的数据。程序内有一计时线程,每个一秒会显示一次生产消费情况,当60秒后程序自动退出,从而保存数据已被分析。

生产者数量1消费者数量1缓冲长度20
额外等待CPU占用

<0.3%
函数分析情况
无法分析到消费者、生产者线程
程序运行情况
生产者: #1  成功次数:12243  失败次数:12213

消费者: #1  成功次数:12223  失败次数:0

生产者数量1消费者数量1缓冲长度20
额外等待CPU占用

>97%
函数分析情况
  %   cumulative   self              self     total           

 time   seconds   seconds    calls  Ts/call  Ts/call  name    

 54.59      2.08     2.08                             buy

 45.41      3.81     1.73                             sell

  0.00      3.81     0.00        1     0.00     0.00  Create_Empty_DS

            Call graph

granularity: each sample hit covers 4 byte(s) for 0.26% of 3.81 seconds

index % time    self  children    called     name

[1]     54.6    2.08    0.00                 buy [1]

-----------------------------------------------

[2]     45.4    1.73    0.00                 sell [2]

-----------------------------------------------

                0.00    0.00       1/1           main [8]

[3]      0.0    0.00    0.00       1         Create_Empty_DS [3]

-----------------------------------------------

Index by function name

   [3] Create_Empty_DS         [1] buy                     [2] sell

程序运行情况
生产者: #1  成功次数:21260  失败次数:1146117485

消费者: #1  成功次数:21240  失败次数:1151177763

生产者数量1消费者数量5缓冲长度20
额外等待CPU占用

<0.3%
函数分析情况
无法分析到消费者、生产者线程
程序运行情况
生产者: #1  成功次数:24829  失败次数:0

消费者: #1  成功次数:4900  失败次数:15102

消费者: #2  成功次数:5261  失败次数:14386

…..

消费者: #5  成功次数:5069  失败次数:14752

生产者数量1消费者数量5缓冲长度20
额外等待CPU占用

>97%
函数分析情况
  %   cumulative   self              self     total           

 time   seconds   seconds    calls  Ts/call  Ts/call  name    

 85.29      2.90     2.90                             buy

 14.71      3.40     0.50                             sell

  0.00      3.40     0.00        1     0.00     0.00  Create_Empty_DS

            Call graph

granularity: each sample hit covers 4 byte(s) for 0.29% of 3.40 seconds

index % time    self  children    called     name

[1]     85.3    2.90    0.00                 buy [1]

-----------------------------------------------

[2]     14.7    0.50    0.00                 sell [2]

-----------------------------------------------

                0.00    0.00       1/1           main [8]

[3]      0.0    0.00    0.00       1         Create_Empty_DS [3]

-----------------------------------------------

Index by function name

   [3] Create_Empty_DS         [1] buy                     [2] sell

程序运行情况
生产者: #1  成功次数:10800  失败次数:362944304

消费者: #1  成功次数:1980  失败次数:3688095

消费者: #5  成功次数:2560  失败次数:391801113

生产者数量3消费者数量10缓冲长度20
额外等待CPU占用

约4%

函数分析情况
  %   cumulative   self              self     total           

 time   seconds   seconds    calls  Ts/call  Ts/call  name    

 94.08      1.59     1.59                             sell

  5.92      1.69     0.10                             buy

  0.00      1.69     0.00        1     0.00     0.00  Create_Empty_DS

            Call graph

granularity: each sample hit covers 4 byte(s) for 0.59% of 1.69 seconds

index % time    self  children    called     name

[1]     94.1    1.59    0.00                 sell [1]

-----------------------------------------------

[2]      5.9    0.10    0.00                 buy [2]

-----------------------------------------------

                0.00    0.00       1/1           main [8]

[3]      0.0    0.00    0.00       1         Create_Empty_DS [3]

-----------------------------------------------

Index by function name

   [3] Create_Empty_DS         [2] buy                     [1] sell

程序运行情况
生产者: #1  成功次数:161677  失败次数:0

生产者: #2  成功次数:161412  失败次数:0

生产者: #3  成功次数:161535  失败次数:0

消费者: #1  成功次数:442  失败次数:75030

消费者: #2  成功次数:47878  失败次数:67808

消费者: #3  成功次数:47805  失败次数:67967

……

消费者: #10  成功次数:45942  失败次数:716

生产者数量3消费者数量10缓冲长度20
额外等待CPU占用

>97%
函数分析情况
  %   cumulative   self              self     total           

 time   seconds   seconds    calls  Ts/call  Ts/call  name    

 77.28      2.96     2.96                             buy

 22.72      3.83     0.87                             sell

  0.00      3.83     0.00        1     0.00     0.00  Create_Empty_DS

            Call graph

granularity: each sample hit covers 4 byte(s) for 0.26% of 3.83 seconds

index % time    self  children    called     name

[1]     77.3    2.96    0.00                 buy [1]

-----------------------------------------------

[2]     22.7    0.87    0.00                 sell [2]

-----------------------------------------------

                0.00    0.00       1/1           main [8]

[3]      0.0    0.00    0.00       1         Create_Empty_DS [3]

-----------------------------------------------

Index by function name

   [3] Create_Empty_DS         [1] buy                     [2] sell

程序运行情况
生产者: #1  成功次数:40  失败次数:174184091

生产者: #2  成功次数:4000  失败次数:179724333

生产者: #3  成功次数:4120  失败次数:170018676

消费者: #1  成功次数:1280  失败次数:182205853

消费者: #2  成功次数:1340  失败次数:173044281

…..

消费者: #9  成功次数:1020  失败次数:166877134

消费者: #10  成功次数:1380  失败次数:1803475

生产者数量5消费者数量20缓冲长度40
额外等待CPU占用

约9%

函数分析情况
  %   cumulative   self              self     total           

 time   seconds   seconds    calls  Ts/call  Ts/call  name    

 96.37      4.25     4.25                             sell

  3.63      4.41     0.16                             buy

  0.00      4.41     0.00        1     0.00     0.00  Create_Empty_DS

            Call graph

granularity: each sample hit covers 4 byte(s) for 0.23% of 4.41 seconds

index % time    self  children    called     name

[1]     96.4    4.25    0.00                 sell [1]

-----------------------------------------------

[2]      3.6    0.16    0.00                 buy [2]

-----------------------------------------------

                0.00    0.00       1/1           main [8]

[3]      0.0    0.00    0.00       1         Create_Empty_DS [3]

-----------------------------------------------

Index by function name

   [3] Create_Empty_DS         [2] buy                     [1] sell

程序运行情况
生产者: #1  成功次数:1396  失败次数:0

生产者: #2  成功次数:139235  失败次数:0

….

生产者: #5  成功次数:137801  失败次数:0

消费者: #1  成功次数:25922  失败次数:988

消费者: #2  成功次数:39009  失败次数:70402

消费者: #3  成功次数:395  失败次数:69154

…..

消费者: #20  成功次数:33775  失败次数:80811

生产者数量5消费者数量20缓冲长度40
额外等待CPU占用

>97%
函数分析情况
  %   cumulative   self              self     total           

 time   seconds   seconds    calls  Ts/call  Ts/call  name    

 80.29      2.81     2.81                             buy

 19.71      3.50     0.69                             sell

  0.00      3.50     0.00        1     0.00     0.00  Create_Empty_DS

            Call graph

granularity: each sample hit covers 4 byte(s) for 0.29% of 3.50 seconds

index % time    self  children    called     name

[1]     80.3    2.81    0.00                 buy [1]

-----------------------------------------------

[2]     19.7    0.69    0.00                 sell [2]

-----------------------------------------------

                0.00    0.00       1/1           main [8]

[3]      0.0    0.00    0.00       1         Create_Empty_DS [3]

-----------------------------------------------

Index by function name

   [3] Create_Empty_DS         [1] buy                     [2] sell

程序运行情况
生产者: #1  成功次数:4120  失败次数:81703999

….

生产者: #5  成功次数:4200  失败次数:918744

消费者: #1  成功次数:1080  失败次数:83483253

消费者: #2  成功次数:1240  失败次数:85970149

消费者: #3  成功次数:1360  失败次数:106265813

消费者: #4  成功次数:960  失败次数:82522392

消费者: #5  成功次数:1360  失败次数:93462020

消费者: #6  成功次数:1160  失败次数:91337608

消费者: #7  成功次数:1520  失败次数:102954859

消费者: #8  成功次数:1080  失败次数:91363902

消费者: #9  成功次数:760  失败次数:105319175

消费者: #10  成功次数:1320  失败次数:102144117

消费者: #11  成功次数:720  失败次数:90933552

消费者: #12  成功次数:1120  失败次数:90537228

消费者: #13  成功次数:920  失败次数:90670465

消费者: #14  成功次数:840  失败次数:109153419

消费者: #15  成功次数:1320  失败次数:91949204

消费者: #16  成功次数:960  失败次数:80094023

消费者: #17  成功次数:1120  失败次数:81866777

消费者: #18  成功次数:1000  失败次数:87802980

消费者: #19  成功次数:800  失败次数:79470741

消费者: #20  成功次数:960  失败次数:93098801

生产者数量30消费者数量100缓冲长度200
额外等待CPU占用

26%
函数分析情况
  %   cumulative   self              self     total           

 time   seconds   seconds    calls  Ts/call  Ts/call  name    

 79.17     10.49    10.49                             sell

 20.83     13.25     2.76                             buy

  0.00     13.25     0.00        1     0.00     0.00  Create_Empty_DS

            Call graph

granularity: each sample hit covers 4 byte(s) for 0.08% of 13.25 seconds

index % time    self  children    called     name

[1]     79.2   10.49    0.00                 sell [1]

-----------------------------------------------

[2]     20.8    2.76    0.00                 buy [2]

-----------------------------------------------

                0.00    0.00       1/1           main [8]

[3]      0.0    0.00    0.00       1         Create_Empty_DS [3]

-----------------------------------------------

Index by function name

   [3] Create_Empty_DS         [2] buy                     [1] sell

程序运行情况
生产者: #1  成功次数:24555  失败次数:0

生产者: #2  成功次数:24423  失败次数:0

生产者: #3  成功次数:24529  失败次数:0

生产者: #4  成功次数:24428  失败次数:0

…..

生产者: #8  成功次数:24552  失败次数:0

生产者: #9  成功次数:24467  失败次数:0

生产者: #10  成功次数:24612  失败次数:0

…………………………….

生产者: #29  成功次数:24439  失败次数:0

生产者: #30  成功次数:24723  失败次数:0

消费者: #1  成功次数:7351  失败次数:173

消费者: #2  成功次数:08  失败次数:18192

消费者: #3  成功次数:6725  失败次数:17620

…….

消费者: #8  成功次数:7830  失败次数:15630

消费者: #9  成功次数:72  失败次数:16677

………

消费者: #97  成功次数:6628  失败次数:17780

消费者: #98  成功次数:7200  失败次数:16692

消费者: #99  成功次数:6921  失败次数:17335

消费者: #100  成功次数:7433  失败次数:16319

生产者数量30消费者数量100缓冲长度200
额外等待CPU占用

>97%
函数分析情况
  %   cumulative   self              self     total           

 time   seconds   seconds    calls  Ts/call  Ts/call  name    

 78.77      2.82     2.82                             buy

 21.23      3.58     0.76                             sell

  0.00      3.58     0.00        1     0.00     0.00  Create_Empty_DS

            Call graph

granularity: each sample hit covers 4 byte(s) for 0.28% of 3.58 seconds

index % time    self  children    called     name

[1]     78.8    2.82    0.00                 buy [1]

-----------------------------------------------

[2]     21.2    0.76    0.00                 sell [2]

-----------------------------------------------

                0.00    0.00       1/1           main [8]

[3]      0.0    0.00    0.00       1         Create_Empty_DS [3]

-----------------------------------------------

Index by function name

   [3] Create_Empty_DS         [1] buy                     [2] sell

程序运行情况
生产者: #1  成功次数:3600  失败次数:105017

生产者: #2  成功次数:4000  失败次数:16728291

生产者: #3  成功次数:3200  失败次数:17111918

生产者: #4  成功次数:3400  失败次数:10212130

……

生产者: #17  成功次数:1400  失败次数:15370944

生产者: #18  成功次数:2200  失败次数:21377626

生产者: #19  成功次数:2600  失败次数:11692857

生产者: #20  成功次数:3800  失败次数:16007285

生产者: #21  成功次数:2200  失败次数:14244794

生产者: #22  成功次数:3600  失败次数:22401337

…..

生产者: #29  成功次数:4000  失败次数:18425015

生产者: #30  成功次数:4800  失败次数:25033822

消费者: #1  成功次数:1800  失败次数:12785639

消费者: #2  成功次数:2800  失败次数:16116393

消费者: #3  成功次数:2400  失败次数:10309678

消费者: #4  成功次数:800  失败次数:244415

消费者: #52  成功次数:0  失败次数:13483907

消费者: #53  成功次数:0  失败次数:20152736

消费者: #54  成功次数:0  失败次数:14236228

消费者: #55  成功次数:800  失败次数:21535076

消费者: #56  成功次数:400  失败次数:18554678

消费者: #57  成功次数:200  失败次数:20539844

消费者: #58  成功次数:800  失败次数:12377087

…….

消费者: #94  成功次数:1600  失败次数:14401603

消费者: #95  成功次数:2000  失败次数:24301257

消费者: #96  成功次数:1800  失败次数:15476220

消费者: #97  成功次数:2600  失败次数:175168

消费者: #98  成功次数:1000  失败次数:15002154

消费者: #99  成功次数:200  失败次数:14299381

消费者: #100  成功次数:1600  失败次数:21723729

生产者数量50消费者数量200缓冲长度500
额外等待CPU占用

>97%
函数分析情况
  %   cumulative   self              self     total           

 time   seconds   seconds    calls  Ts/call  Ts/call  name    

 98.61     50.26    50.26                             sell

  1.39     50.97     0.71                             buy

  0.00     50.97     0.00        1     0.00     0.00  Create_Empty_DS

            Call graph

granularity: each sample hit covers 4 byte(s) for 0.02% of 50.97 seconds

index % time    self  children    called     name

[1]     98.6   50.26    0.00                 sell [1]

-----------------------------------------------

[2]      1.4    0.71    0.00                 buy [2]

-----------------------------------------------

                0.00    0.00       1/1           main [8]

[3]      0.0    0.00    0.00       1         Create_Empty_DS [3]

-----------------------------------------------

Index by function name

   [3] Create_Empty_DS         [2] buy                     [1] sell

程序运行情况
 生产者: #1  成功次数:14839  失败次数:0

生产者: #2  成功次数:14688  失败次数:0

生产者: #3  成功次数:14838  失败次数:0

生产者: #4  成功次数:14738  失败次数:0

………..

…..

生产者: #49  成功次数:14908  失败次数:0

生产者: #50  成功次数:14949  失败次数:0

消费者: #1  成功次数:3602  失败次数:20037

消费者: #2  成功次数:3777  失败次数:19650

消费者: #3  成功次数:3655  失败次数:19907

消费者: #4  成功次数:3909  失败次数:19429

消费者: #5  成功次数:3744  失败次数:19693

………

消费者: #200  成功次数:3590  失败次数:19568

生产者数量50消费者数量50缓冲长度200
额外等待CPU占用

>97%
函数分析情况
 %   cumulative   self              self     total           

 time   seconds   seconds    calls  Ts/call  Ts/call  name    

 51.80      1.87     1.87                             buy

 48.20      3.61     1.74                             sell

  0.00      3.61     0.00        1     0.00     0.00  Create_Empty_DS

            Call graph

granularity: each sample hit covers 4 byte(s) for 0.28% of 3.61 seconds

index % time    self  children    called     name

[1]     51.8    1.87    0.00                 buy [1]

-----------------------------------------------

[2]     48.2    1.74    0.00                 sell [2]

-----------------------------------------------

                0.00    0.00       1/1           main [8]

[3]      0.0    0.00    0.00       1         Create_Empty_DS [3]

-----------------------------------------------

Index by function name

   [3] Create_Empty_DS         [1] buy                     [2] sell

程序运行情况
生产者: #1  成功次数:3800  失败次数:23207623

生产者: #2  成功次数:4000  失败次数:18301181

生产者: #3  成功次数:2000  失败次数:23687122

生产者: #4  成功次数:3200  失败次数:22469725

生产者: #5  成功次数:2400  失败次数:16314631

生产者: #6  成功次数:3400  失败次数:27401390

生产者: #7  成功次数:1200  失败次数:22341461

生产者: #8  成功次数:5000  失败次数:20794031

生产者: #9  成功次数:2400  失败次数:12731020

生产者: #10  成功次数:3600  失败次数:19212245

生产者: #11  成功次数:4800  失败次数:19359142

……

生产者: #48  成功次数:5000  失败次数:21311750

生产者: #49  成功次数:4000  失败次数:24779388

生产者: #50  成功次数:2400  失败次数:20580230

消费者: #1  成功次数:6000  失败次数:212666

消费者: #2  成功次数:4200  失败次数:24949360

消费者: #3  成功次数:5200  失败次数:23671124

….

消费者: #17  成功次数:2800  失败次数:172273

消费者: #18  成功次数:2800  失败次数:18082421

消费者: #19  成功次数:3000  失败次数:16376502

消费者: #20  成功次数:1000  失败次数:23365359

…..

消费者: #33  成功次数:2000  失败次数:20199384

消费者: #34  成功次数:1600  失败次数:27577017

消费者: #35  成功次数:1800  失败次数:14283957

消费者: #36  成功次数:1400  失败次数:27276326

消费者: #37  成功次数:3400  失败次数:24800367

消费者: #38  成功次数:3400  失败次数:24015530

……

消费者: #49  成功次数:3200  失败次数:22219422

消费者: #50  成功次数:2200  失败次数:18238523

生产者数量50消费者数量50缓冲长度200
额外等待CPU占用

63%
函数分析情况
  %   cumulative   self              self     total           

 time   seconds   seconds    calls  Ts/call  Ts/call  name    

 92.32     26.81    26.81                             sell

  7.68     29.04     2.23                             buy

  0.00     29.04     0.00        1     0.00     0.00  Create_Empty_DS

            Call graph

granularity: each sample hit covers 4 byte(s) for 0.03% of 29.04 seconds

index % time    self  children    called     name

[1]     92.3   26.81    0.00                 sell [1]

-----------------------------------------------

[2]      7.7    2.23    0.00                 buy [2]

-----------------------------------------------

                0.00    0.00       1/1           main [8]

[3]      0.0    0.00    0.00       1         Create_Empty_DS [3]

-----------------------------------------------

Index by function name

   [3] Create_Empty_DS         [2] buy                     [1] sell

程序运行情况
生产者: #1  成功次数:14774  失败次数:12234

生产者: #2  成功次数:14821  失败次数:12209

…..

生产者: #47  成功次数:14754  失败次数:12265

生产者: #48  成功次数:145  失败次数:12160

生产者: #49  成功次数:14796  失败次数:12126

生产者: #50  成功次数:14722  失败次数:12149

消费者: #1  成功次数:14816  失败次数:65

消费者: #2  成功次数:14836  失败次数:65

消费者: #3  成功次数:14814  失败次数:58

消费者: #4  成功次数:14804  失败次数:72

…..

消费者: #26  成功次数:14817  失败次数:62

消费者: #27  成功次数:14851  失败次数:56

消费者: #28  成功次数:14840  失败次数:65

消费者: #29  成功次数:14802  失败次数:56

…..

消费者: #49  成功次数:14819  失败次数:60

消费者: #50  成功次数:14836  失败次数:59

四、结果分析

1、资源消耗的分析

首先,是整体的资源消耗,由于结构问题,生产者和消费者的实质都是while(1)的循环,这意味着如果不插入等待时间的话,任何一种方案,无论生产者消费者的数目,运行起来都可以让CPU的占用达到100%,即使线程的资源开销要小于进程。

另外,就是本题假设的情况,在有等待的情况下,我们假定生产者生产东西需要更多的时间,这个时间为20000的一个空循环,而消费者只需要6000的空循环即可。以3消费者10生产者,有等待这组数据为例,生产者和消费者的CPU占用比例差不多为94:6,即便是3*4:10也只是12:10而已,但是实际上生产者的CPU占用比例很高,这和生产者的操作有关。生产者完成的任务是将一组数据循环写入一个数组内,而这个数组的长度为1024,这本身就需要一定的资源消耗,而消费者只是执行一个strcpy。尽管如此,在总共60秒的运行中,几个函数的CPU占用时间也只是1.6s而已。

接下来,是没有等待的情况。以5生产者,20消费者,40缓冲区的情况为例,从分析报告可以得出,消费者和生产者的CPU占用比大概为4:1,但是从CPU占用时间来看,生产者的3.5秒还比消费者的2.8秒要多,这也说明了生产者实际的工作更占用CPU资源,而绝大多数时间都花在对信号量的等待上。

另外,需要考虑的是互斥操作的开销问题,加锁和解锁都是原子动作,可以互斥完成,因而理论上占用的CPU资源应该非常少。以3消费者10生产者,无等待这组数据的运行结果为例,生产者成功的次数大概在10的3次方级别,而失败的次数确高达10的9次方,从程序运行过程可以得知,失败的操作也包括加锁、访问数量位、解锁这些,从数字的比较我们也可以看出这些原子操作的速度之快。因而,个人认为,在线程数量不是很大的情况下,课件中给的程序的多锁操作并不会对程序的性能造成太过明显的影响。

由于性能原因,并未给出更多数据的测试报告,而影响这个性能的主要原因是大量的printf,包括清屏、光标重定位、显示文字等,这才是影响整个程序性能的真正原因。

2、具体应用情况

生产者-消费者是一个很广泛的问题,代表了多线程互斥访问共享资源,以及线程之间通信的各种问题的总和。这个可以从数据看出,简单有无等待的设置,可以让数据差异变的如此明显。毕竟,在实际应用中,生产者如何生产数据,产生怎样的数据,而消费者又如何拿到数据,是通过管道还是内存共享区,拿到数据之后,是进行处理还是写入磁盘,都有可能,因而,简单通过几组数据来判断哪个进程的效率更高,是不科学的。

我们可以通过两个实际的例子来解释有无等待的情况。有等待更像是在火锅店吃火锅,商家只要把锅准备好,将生菜切好端上来即可,而顾客大量的时间用在食用上,等待的时间也很少,这样资源位置会大量被消耗,但是实际资源消耗的速度很慢,但是由于空间被占用,生产者无法继续生产。而无等待更像是快餐外卖,商家花大量时间将食物准备好,而消费者只需要交钱拿东西走人即可,资源位置空闲很多,消费者等待时间较长,但是处理数据的过程简单又节约资源。

这里还有一个问题,就是资源槽的数量,理论上这个方案应该放弃资源槽,但是从实际实现方法来看,只不过将每个资源槽的标志位移动到资源内部,然后通过读写指针进行查询,实际上这还是资源槽的变相实现方式。但是单就资源槽的数目来看,是个很复杂的问题,要保证在生产者想生产的时候不会没槽位,而消费者查询资源槽状态又不会花费太长时间,这需要具体分析每个函数执行的时间,因而本实验中只是简单设计为消费者数量的1-3倍。

3、线程的调度情况

首先要考虑的是线程的调度顺序。为了方便起见,每个线程在产生的时候都有一个编号,而创建也是按照1-Count这样的顺序for循环创建的。如果线程调度的顺序和线程创建的顺序一样,那么在无等待情况下应该有这样的结果:消费者远多于生产者,这样当生产者生产出数据时,前面几个优先完成等待并读取数据的进程就可以占据先机,拿到数据(这里不考虑数据消费的时间,因为程序的思路和食堂占座差不多,只要把位置占掉就可以了),而后面一些进程则由于位置不佳基本拿不到数据。但是从数据来看并不是这样:

从数据来看,99号消费者只成功拿到200次数据,而100号消费者则拿到了1600次数据,而96号消费者更是拿到了2600次数据。这就说明了线程调度的随机性,线程调度的顺序和线程创建的顺序完全不一样,当然如果利用进程+特殊的框架能做到,比如令牌环作业我用的那套框架,就可以保证每个进程按顺序执行,当然这是使用了其他的进程来辅助调度。

另外一个比较有意思的是,成功次数为什么都是100的整数倍,而失败的次数则可以有很大的差异。除了原子操作占用资源较少,可以很快完成之外,更多的我感觉是因为时间片的轮转。无论时间片是怎么分配的,在一个时间片内必须保证原子操作的完成,且操作完成前不能被终止和剥夺,而其他的函数操作则不能保证其原子性。但是有一点不解,就是这个时间片的长度,从us级等待来看,由于其他进程的自动等待,可以保证消费者公平的获取资源,但是没有等待时,一个时间片至少可以让一个消费者完成200次消费操作,这就不好理解了。

当然,插入等待时间也是一种解决资源分配的方式,这样可以保证每个线程都有机会获取到资源,不至于让某个线程连续的占用资源很长时间。但是在实际情况下,每个线程完成工作不一样,又不可能像实验写出的进程那样无私,拿到资源后就自动等待,这就需要使用线程间通信机制了,让拿不到资源的线程挂起,生产者完成生产后再唤醒这些进程。

4、gprof本身的问题

“gprof是GNU profiler工具。可以显示程序运行的“flat profile”,包括每个函数的调用次数,每个函数消耗的处理器时间。也可以显示“调用图”,包括函数的调用关系,每个函数调用花费了多少时间。还可以显示“注释的源代码”,是程序源代码的一个复本,标记有程序中每行代码的执行次数。

在编译或链接源程序的时候在编译器的命令行参数中加入“-pg”选项,编译时编译器会自动在目标代码中插入用于性能测试的代码片断,这些代码在程序在运行时采集并记录函数的调用关系和调用次数,以及采集并记录函数自身执行时间和子函数的调用时间,程序运行结束后,会在程序退出的路径下生成一个gmon.out文件。这个文件就是记录并保存下来的监控数据。可以通过命令行方式的gprof或图形化的Kprof来解读这些数据并对程序的性能进行分析。另外,如果想查看库函数的profiling,需要在编译是再加入“-lc_p”编译参数代替“-lc”编译参数,这样程序会链接libc_p.a库,才可以产生库函数的profiling信息。如果想执行一行一行的profiling,还需要加入“-g”编译参数。”

这是摘自网上一段关于gprof的介绍,实际应用中,-lc_p参数貌似无法使用,而加上-g之后,并没有多出什么有用的信息。而且,问题并不止这些,从网上介绍来看,貌似gprof是在编译的时候,让gcc为每个函数调用加上一个类似“mount”的函数,来记录函数的调用状况。但是,当函数的执行时间很短时,gprof并不能分析出这些函数,这也就是为什么在代码中为每段代码额外加上一点占用cpu资源的代码,更奇怪的是,这种情况只出现在使用wait的情况,当不wait直接while(1)执行,则完全没有这个问题。

另外,就是根据上文分析,同时根据实践经验可知,大量的IO操作非常占用资源,包括屏幕的擦写,但是gprof并没有分析出这部分的cpu占用,就连Recount这个统计函数都没有出现在函数列表内,这就更诡异了。因而,如果想要完整的分析一个程序的性能,单用gprof肯定是不够的,尤其是对于有应用背景的程序来说。当然,对于完全是CPU占用的程序,比如各种查找、排序算法,使用gprof应该会收到相当好的效果。

5、程序性能

    应该说,这个程序的性能只能算一般,当关闭-pg选项时,同时关闭recount的IO操作,在100生产者100消费者,缓冲长度500,增加额外等待时,CPU占用已经接近100%,如果不改变环境变量,在创建160个生产者或者220个消费者时,程序会由于资源分配报错。当然,这样的数目对于一般的应用已经足够了。

6、可能的改进

首先,就是线程调度的方式,忙等肯定不是一个好方法,如果换成线程间通信,轮流挂起或者唤醒某个线程,可能会更好。

其次,是消费者的资源分配问题。程序设计的消费者有一个1024字节的buffer,用来处理接收到的数据,但是这个buffer直到线程消失才被回收,如果加上一些资源回收的机制可能会改善程序的性能。

最后,是对于生产者和消费者具体完成工作的问题,应该说这个实验设计的场景还是很简单,完全的内存拷贝,没有什么复杂运算和文件操作,而且由于手头没有原始版本的代码,无法比较两个代码的性能,整个程序的框架也是重新编写的,这应该也是一点不足。 

五、总结体会

    通过这次实验的练习,我对linux下的线程、进程等知识有了更直观深入的了解,同时也深感linux下开发程序的不易,通过写这几个程序,发现在linux下,最常见的运行错误莫过于“段错误”,这个错误的范围很广,从访问越界,到数组上下限错误,再到内存或者堆栈溢出,都可能触发这个错误,而如果没有一定的经验这是相当棘手的问题。当然,这个综合练习实验有很好的价值,除了涉及多个知识点之外,自己在做程序的时候也通过解决问题,对linux下一些函数认识的更加透彻,例如对进程间的通信,多线程的安全问题,管道问题都有了进一步的了解,初步学习了一下linux下的编程还是很不错的。下载本文

显示全文
专题