#include
#include "errors.h"
typedef struct stage_tag{ //流水线的工作单元结构体
pthread_mutex_t mutex; //保护当前工作单元数据的互斥锁
pthread_cond_t avail; //等待当前工作单元存储数据可用的条件变量
pthread_cond_t ready; //等待当前工作单元可处理新数据的条件变量
int data_ready; //表示当前工作单元存放数据的状态(0表示data为过期数据,1表示data为有效数据)
long data; //当前工作单元存放的数据
pthread_t thread; //当前工作单元所在的线程ID
struct stage_tag *next; //流水线中指向下一工作单单元的指针
}stage_t;
typedef struct pipe_tag{ //流水线结构体
pthread_mutex_t mutex; //保护流水线加入新数据或读出流水线工作结果的数据
stage_t *head; //流水线的第一个工作单原指针
stage_t *tail; //流水线的最后一个工作单原的指针(保存数据经过流水线处理之后所得的结果数据)
int stages; //流水线中工作单元的数量
int active; //流水线中正在处理数据的工作单元的数量
}pipe_t;
int pipe_send(stage_t *stage,long data){ //向stage所指向的工作单原传送新数据data的函数
int status; //保存线程函数的调用状态(返回值为0表示调用成功,否则调用失败就打印错误信息)
status = pthread_mutex_lock(&stage->mutex); //试图锁定stage指向的工作单原的互斥锁
if(status != 0)
return status;
while(stage->data_ready){ //等待条件变量ready发送当前工作单原可处理新数据的信号
status = pthread_cond_wait(&stage->ready,&stage->mutex); //如果data_ready为1,即当前工作单原存储的数据为有效数据则等待处理完毕
if(status != 0){ //否则,表示当前工作单元存储的数据为过期数据,可以处理新传入的数据data
pthread_mutex_unlock(&stage->mutex);
return status;
}
}
stage->data = data; //更新当前工作单原存储的数据为data
stage->data_ready = 1; //更新后的数据为有效数据,data_ready设置为1
status = pthread_cond_signal(&stage->avail); //并且发送avail信号通知等待中的线程该工作单元的数据为可用的有效数据
if(status != 0){
pthread_mutex_unlock(&stage->mutex);
return status;
}
status = pthread_mutex_unlock(&stage->mutex); //解锁当前工作单元的互斥锁
return status; //返回函数调用状态
}
void *pipe_stage(void *arg){ //线程入口函数,arg为当前的工作单元结构体的指针
stage_t *stage = (stage_t*)arg; //j将arg转换成stage_t*类型并赋值给stage
stage_t *next_stage = stage->next; //将stage的下一个工作单元的指针赋值给stage_next
int status; //同上的status
status = pthread_mutex_lock(&stage->mutex); //开始工作,试图锁定当前互斥锁
if(status != 0)
err_abort(status,"Lock pipe stage");
while(true){ //循环处理当前工作单原内的数据
while(stage->data_ready != 1){ //等待条件变量avail发送当前数据可用的信号
status = pthread_cond_wait(&stage->avail,&stage->mutex);//等待的开始时会先解锁绑定的互斥锁
if(status != 0)
err_abort(status,"Wait for previous stage");
}
pipe_send(next_stage, stage->data + 1); //将对当前数据工作(这里是对data+1)后的结果发送给下一个工作单元处理
stage->data_ready = 0; //数据传送给下一个工作单元后当前数矩即过期,data_ready设置为0
status = pthread_cond_signal(&stage->ready); //并发送ready信号通知等待中的线程,该工作单元可以接收并处理新的数据
if(status != 0)
err_abort(status,"Wake next stage");
}
}
int pipe_create(pipe_t * pipe,int stages){ //创建工作流的函数 stages表示工作单元数量
int pipe_index; //当前创建的工作单原的索引
stage_t **link = &pipe->head, *new_stage, *stage;//用于创建链表的各个指针变量
int status; //同上的status
status = pthread_mutex_init(&pipe->mutex,NULL); //初始化保护工作流链表的互斥锁
if(status != 0)
err_abort(status , "Init pipe mutex");
pipe->stages = stages;
pipe->active = 0; //当前正在处理数据的工作单元数初始化为0
for(pipe_index = 0; pipe_index <= stages; pipe_index++){ //循环创建工作单元的数据节点
new_stage = (stage_t*)malloc(sizeof(stage_t)); //分配内存空间
if(new_stage == NULL)
errno_abort("Allocate stage");
status = pthread_mutex_init(&new_stage->mutex,NULL); //下面是一些初始化工作
if(status != 0)
err_abort(status,"Init stage mutex");
status = pthread_cond_init(&new_stage->avail,NULL);
if(status != 0)
err_abort(status,"Init avail condition");
status = pthread_cond_init(&new_stage->ready,NULL);
if(status != 0)
err_abort(status, "Init ready condition");
new_stage->data_ready = 0;
*link = new_stage;
link = &new_stage->next;
}
*link = (stage_t*)NULL;
pipe->tail = new_stage;
for(stage = pipe->head;stage->next != NULL;stage = stage->next){ //为每个工作单元创建线程
status = pthread_create(&stage->thread,NULL,pipe_stage,(void*)stage);
if(status != 0)
err_abort(status,"Create pipe stage");
}
return 0;
}
int pipe_start(pipe_t *pipe, long value){ //将新加入的数据发送给工作流处理
int status;
status = pthread_mutex_lock(&pipe->mutex); //试图锁定互斥锁以加入新数据
if(status != 0)
err_abort(status,"Lock pipe mutex");
pipe->active++; //如果锁定了互斥锁,说明当前第一个工作单元可以工作,将工作的计数量加一
status = pthread_mutex_unlock(&pipe->mutex); //解锁互斥锁
if(status != 0)
err_abort(status, "Unlock pipe mutex");
pipe_send(pipe->head,value); //将数据发送给第一个工作单元处理
return 0;
}
int pipe_result(pipe_t *pipe,long *result){ //获取处理结果的函数
stage_t *tail = pipe->tail;
long value; //保存结果数据
int empty = 0; //判断工作流中数据是否为空的flag
int status;
status = pthread_mutex_lock(&pipe->mutex); //试图锁定流水线的互斥锁
if(status != 0)
err_abort(status, "Lock pipe mutex");
if(pipe->active <= 0) //如果当前工作中的工作单元小于等于0
empty = 1; //将empty置为1
else
pipe->active--; //否则减少一个工作中的工作单元数(因为取出来来一个数据嘛)
status = pthread_mutex_unlock(&pipe->mutex); //解锁流水线的互斥锁
if(status != 0)
err_abort(status, "Unlock pipe mutex");
if(empty) //如果流水线中的数据为空,则获取数据失败,函数直接返回0
return 0;
pthread_mutex_lock(&tail->mutex); //试图锁定最后一个工作单元的互斥锁
while(!tail->data_ready) //等待最后一个工作单元发送其中存放的数据为可用的有效数矩的信号
pthread_cond_wait(&tail->avail,&tail->mutex);
*result = tail->data; //将最后一个工作单元中的有效数据取出存入result中
tail->data_ready = 0; //最后一个工作单原的数据已取出,则其中的data为过期数据,将data_ready置为0
pthread_cond_signal(&tail->ready); //并发送信号给其他正在等待中的线程,最后一个工作单元可以处理新的数据了
pthread_mutex_unlock(&tail->mutex); //锁定最后一个工作单元的互斥锁
return 1; //获取数据成功,返回1
}
int main(int argc, char *argv[]){ //主函数
pipe_t my_pipe; //定义工作流变量
long value,result; //value为需要处理的数据,result为处理结果
int status;
char line[128]; //输入缓存
pipe_create(&my_pipe, 10); //创建新的工作流(默认创建10个工作单元的工作流)
printf("Enter integer values, or \"=\" for next result\n"); //提示信息
while(true){ //循环等待用户输入并处理数据
printf("Data>"); //提示符
if(fgets(line,sizeof(line),stdin) == NULL)exit(0); //用户输入结束退出
if(strlen(line) <= 1) continue;
if(strlen(line) <= 2 && line[0] == '='){ //如果输入 = 表示读取流水线中的处理结果
if(pipe_result(&my_pipe,&result)) //如果读取结果成功
printf("Result is %ld\n",result); //则将结果打印出来
else //否则读取失败
printf("Pipe is empty\n"); //打印提示,工作流为空
}else{
if(sscanf(line,"%ld",&value) < 1) //将缓存中的数据读出
fprintf(stderr, "Enter an integer value\n");
else pipe_start(&my_pipe,value); //调用函数,处理新加入的数据
}
}
}
这个例程也是 POSIX多线程程序设计 一书中说明工作流方式的例程, 反复看了很多遍才理清楚其中的逻辑,特别是两个条件变量ready avail 和 data_ready这个flag的关系.
有几点需要说明的:
1>因为工作流默认创建10个工作单元,每个工作单元独占一个线程(最后一个工作单元除外,它只负责保存处理结果),所以一共可以同时处理11个数据,如果输入更多数据,整个工作流中的工作单元都在等待下一个工作单元通过ready发出可以接收新数据的信号,而tail所指的工作单原则在等待主线程读出数据,而此时主线程因为加入来新数据而堵塞在等待head所指的工作单元通过ready发出可以接受新数据的信号,从而出现死锁现象,整个进程都被挂起。
2> 每个stage_t中的互斥锁只保护当前工作单元的数据,条件变量也是. 其中ready条件变量是由自己发出给上一个工作单元接收信号的,代表当前工作单原中的数据已经过期,通知上一个工作单原可以将新的数据交给其处理了; avail条件变量是由自己发出给下一个工作单原接收信号的,代表当前工作单元中的数据已经处理完毕,通知下一个数据单元可以拿去继续下一次处理来. 这可能有点绕,但也很好理解:B->C这两个工作单元,如果C中处理完毕的数据还未交给下一个工作单元,也即C中的数据未过期,则B将阻塞,等待C中的数据传送给下一个工作单元处理(此时C中的数据过期),并通过ready发送信号通知B可以将新的数据交给C处理来,这时B将解除阻塞,继续执行.如果C中的数据已经过期,而B中的数据还未处理好(比如没有新的数据加入,或者正在处理新的数据的过程中),则C会阻塞一直等待B通过avail发送信号通知C,B有新的处理好的数据(即有效数据)可以交给C处理来,此时C才会接触阻塞继续工作. 所以ready和avail这两个条件变量是相关联的,最后组合起来就是一个完整的工作流的信号传输体系.
3> 根据第二点就很容易说明第一点的原因来: 因为当前工作流中有11个数据而且从未读出,则最先加入工作流的数据d1在tail所指的工作单元G11中等待读出, 第二加入工作流的数据d2则在第十个工作单原G10中等待G11通过ready发送可以处理新数据的信号,所以G10所在的线程处于阻塞状态.而此时d3所在的G9也同样在等待G10发送ready信号以接收d3进行处理,所以G9所在的线程同样处于阻塞状态,依次类推,d11所在的G1在等待G2的ready信号,线程G1阻塞,因此,此时用户输入的第12个数据d12在主函数中调用pipe_start()欲进行处理时pipe_start()会将数据交给pipe_send(pipe->head,d12)处理,而pipe_send会阻塞在等待G1发出ready的信号的条件变量上,即主线程被阻塞,整个进程被挂起。
本人还在学习中,鉴于水平有限,如果有错误还请指教,谢谢!