【Pthreads】Pipeline Model(Assembly Line)示例
前言
Pthreads 有几种工作模型,例如 Boss/Workder Model、Pileline Model(Assembly Line)、Background Task Model、Interface/Implementation Model,详细介绍可以参考 pthread Tutorial,这里给出一个流水线模型(Pipeline Model)的简单示例。在该示例中,主线程开启了两个子线程,一个子线程用来读取文件,一个子线程用于将结果写入文件,而主线程自身用来计算。
模型说明
很多时候,一个程序可以分为几个阶段,比如说读取数据、计算、将结果写入文件,当然我们可以使用每个线程依次执行这些操作,但是一个更好的选择是一个线程处理一个阶段,因为对于文件操作来说,硬盘的读写速率是一定的(IO很多时候会成为性能的瓶颈),即使多个线程读取文件,其读写速率也不会变快(IO操作无法使用线程并行)。所以我们可以用一个线程来处理IO,另外的线程全部用于计算上,如果计算量较大,IO的耗时是可以掩盖过去的。比如读取一个 2G 的文件,然后进行计算。使用流水线模型,我们可以这样做,用一个线程专门读取文件,我们将其成为IO线程。IO线程一次读取 50M 数据,之后交给计算线程来处理这些数据,在计算线程处理数据的同时,IO线程再去读文件,假设处理 50M 数据的时间大于读取50M数据的时间, 当计算线程处理完上一份数据之后,要处理的下一份数据读取完毕,那么计算线程又可以紧接着处理这部分数据,这样循环操作,除了第一次读取数据的时候计算线程处于空闲状态,其余读取的时候计算线程都在进行计算,这样就掩盖掉了IO的时间
实现
执行流程
主线程在程序开始时创建两个子线程,一个用于读,一个用于写,读线程每次只读取一部分文件内容,写线程将这部分数据处理完之后的结果写入文件。创建完线程之后,主线程和写线程就处于等待状态,而读线程就开始读取文件,当读线程读取完第一部分数据之后,读线程进入阻塞状态,主线程开始计算,主线程计算完毕后,写线程开始写入计算结果,同时读线程开始下一部分数据的读取。按照这个流程循环取算存,直到程序结束。
线程等待和唤醒
在执行中,3个线程都会进行等待操作,并且处理完自己的任务之后,还要再次进入等待状态。这里使用条件变量来控制线程的挂起和唤醒,使用while循环控制线程的状态的多次切换。下面是示例代码
while(1) {
pthread_mutex_lock(&read_lock);
while(read_count == 0 ) {
pthread_cond_wait(&read_cond, &read_lock);
}
read_count--;
pthread_mutex_unlock(&read_lock);
}
上面的代码中,while循环会一直执行,所以我们还要加一个是否可以跳出 while 循环的判断,以便在任务结束后可以终止线程, 如下面的代码:
while(1) {
pthread_mutex_lock(&read_lock);
while(read_count == 0 && !read_shutdown ) {
pthread_cond_wait(&read_cond, &read_lock);
}
if(read_shutdown) {
break;
}
read_flag = 1 - read_flag;
pthread_mutex_unlock(&read_lock);
}
我们看到在判断线程是否挂起的 while 循环中也加入了!read_shutdown
的判断,即如果马上就要跳出while循环,标明线程已经执行完了它的任务,则无需再进行挂起操作。唤醒该线程的代码如下所示:
pthread_mutex_lock(&read_lock);
if(loop_index == loop_nums - 1) {
read_shutdown = 1;
}
read_count = 1;
pthread_cond_signal(&read_cond);
pthread_mutex_unlock(&read_lock);
下面分析一下条件变量,首先读线程和写线程都要对应一个条件变量,暂称为 read_cond
和 write_cond
, 主线程用read_cond
来告诉读线程自己已经开始计算,读线程可以继续读取下一部分数据了,用write_cond
告诉写线程,计算已经完毕,可以将结果写入文件了 。而主线程需要两个条件变量,暂称为 cal_cond
和 cal_cond2
, 读线程使用 cal_cond
告诉主线程自己已经读完这部分数据了,主线程可以开始计算了。而写线程用 cal_cond2
告诉主线程自己已经写完了上次计算结果,可以再次分配写入的任务了。如果读线程没有读完或者写线程没有写完,主线程都要进入等待状态。
我们知道每个条件变量都会对应一个条件以及一个互斥锁,下面分析一下各个条件的初始值,程序开始时读线程开始工作,主线程要等待读线程读完才能进行计算,所以 read_cond
对应的条件为 true, cal_cond
对应的条件的为 false,写线程必须要等待主线程计算完才可以写,并且在第一次的时候写线程肯定是空闲的, 所以 write_cond
对应的条件为 false,cal_cond2
对应的的条件为 ture。
数据缓冲区
当读线程读完数据,将数据存到一个缓冲区中(比如一个数组),主线程开始计算,此时读线程又去进行读取操作。如果读线程还是将数据读到上一次读取的缓冲区中(这个缓冲区此时正在被主线程使用),那么就会出现数据竞争。为了解决这个情况,我们可以使用两个缓冲区,读线程填满一个之后再去填另外一个,使用一个变量判断当前该使用哪个缓冲区,即如下面的形式:
int read_buffer_a[BUFFER_SIZE], read_buffer_b[BUFFER_SIZE];
int read_flag;
if(read_flag) {
for(i = 0; i < BUFFER_SIZE; i++) {
fscanf(read_arg->fp, "%d", read_buffer_a+i);
}
} else {
for(i = 0; i < BUFFER_SIZE; i++) {
fscanf(read_arg->fp, "%d", read_buffer_b + i);
}
}
read_flag = 1 -read_flag;
完整代码
下面是完整的代码, 这里是github地址,可以下载下来运行一下。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <pthread.h>
#include <stdarg.h>
#define BUFFER_SIZE 10
uint32_t microseconds = 100;
// 线程信息
typedef struct _thread_info{
pthread_t thread_id;
pthread_mutex_t lock;
pthread_cond_t cond;
int run_flag;
int buffer_flag;
int shutdown;
} thread_info;
// 线程函数参数
typedef struct _thread_arg {
FILE *fp;
} thread_arg;
thread_info input_info, output_info, cal_input_info, cal_output_info;
int read_buffer_a[BUFFER_SIZE], read_buffer_b[BUFFER_SIZE];
int write_buffer_a[BUFFER_SIZE], write_buffer_b[BUFFER_SIZE];
void init_resources(int n, ...) {
va_list arg_ptr ;
int i;
va_start(arg_ptr, n);
thread_info * tmp_info = NULL;
for(i = 0; i < n; i++) {
tmp_info = va_arg(arg_ptr, thread_info *);
pthread_mutex_init(&(tmp_info->lock), NULL);
pthread_cond_init(&(tmp_info->cond), NULL);
}
va_end(arg_ptr);
}
void free_resources(int n, ...) {
va_list arg_ptr;
int i;
va_start(arg_ptr, n);
thread_info * tmp_info = NULL;
for(i = 0; i < n; i++) {
tmp_info = va_arg(arg_ptr, thread_info *);
pthread_mutex_destroy(&(tmp_info->lock));
pthread_cond_destroy(&(tmp_info->cond));
}
va_end(arg_ptr);
}
void * input_task(void * args){
thread_arg * input_arg = (thread_arg *) args;
int i;
while(1) {
pthread_mutex_lock(&(input_info.lock));
while(input_info.run_flag == 0 && !input_info.shutdown) {
pthread_cond_wait(&(input_info.cond), &(input_info.lock));
}
if(input_info.shutdown) {
break;
}
input_info.run_flag = 0;
input_info.buffer_flag = 1 - input_info.buffer_flag;
pthread_mutex_unlock(&(input_info.lock));
if(input_info.buffer_flag) {
for(i = 0; i < BUFFER_SIZE; i++) {
fscanf(input_arg->fp, "%d", read_buffer_a + i);
}
} else {
for(i = 0; i < BUFFER_SIZE; i++) {
fscanf(input_arg->fp, "%d", read_buffer_b + i);
}
}
pthread_mutex_lock(&(cal_input_info.lock));
cal_input_info.run_flag = 1;
pthread_cond_signal(&(cal_input_info.cond));
pthread_mutex_unlock(&(cal_input_info.lock));
}
return NULL;
}
void * output_task(void * args){
thread_arg * output_arg = (thread_arg *) args;
int i;
while(1) {
pthread_mutex_lock(&(output_info.lock));
while(output_info.run_flag == 0 && !output_info.shutdown) {
pthread_cond_wait(&(output_info.cond), &(output_info.lock));
}
if(output_info.shutdown) {
break;
}
output_info.run_flag = 0;
output_info.buffer_flag = 1 - output_info.buffer_flag;
pthread_mutex_unlock(&(output_info.lock));
if(output_info.buffer_flag) {
for(i = 0; i < BUFFER_SIZE; i++) {
fprintf(output_arg->fp, "%d\n", write_buffer_a[i]);
usleep(microseconds);
}
} else {
for(i = 0; i < BUFFER_SIZE; i++) {
fprintf(output_arg->fp, "%d\n", write_buffer_b[i]);
usleep(microseconds);
}
}
pthread_mutex_lock(&(cal_output_info.lock));
cal_output_info.run_flag = 1;
pthread_cond_signal(&(cal_output_info.cond));
pthread_mutex_unlock(&(cal_output_info.lock));
}
return NULL;
}
int main(){
FILE *fp_input, *fp_output;
char *input_name = "input.txt";
char *output_name = "output.txt";
int total_nums = 100;
int loop_nums = total_nums / BUFFER_SIZE;
int loop_index = 0;
int i;
thread_arg input_arg, output_arg;
if((fp_input = fopen(input_name, "r")) == NULL) {
printf("can't load input file\n");
exit(1);
}
if((fp_output = fopen(output_name, "w+")) == NULL) {
printf("can't load output file\n");
exit(1);
}
input_arg.fp = fp_input;
output_arg.fp = fp_output;
init_resources(4, &input_info, &output_info, &cal_input_info, &cal_output_info);
input_info.buffer_flag = output_info.buffer_flag = cal_input_info.buffer_flag = 0;
input_info.run_flag = cal_output_info.run_flag = 1;
output_info.run_flag = cal_input_info.run_flag = 0;
input_info.shutdown = output_info.shutdown = 0;
pthread_create(&(input_info.thread_id), NULL, input_task, &input_arg);
pthread_create(&(output_info.thread_id), NULL, output_task, &output_arg);
while(1) {
pthread_mutex_lock(&(cal_input_info.lock));
while(cal_input_info.run_flag == 0) {
pthread_cond_wait(&(cal_input_info.cond), &(cal_input_info.lock));
}
cal_input_info.buffer_flag = 1 - cal_input_info.buffer_flag;
cal_input_info.run_flag = 0;
pthread_mutex_unlock(&(cal_input_info.lock));
pthread_mutex_lock(&(input_info.lock));
if(loop_index == loop_nums - 1) {
input_info.shutdown = 1;
}
input_info.run_flag = 1;
pthread_cond_signal(&(input_info.cond));
pthread_mutex_unlock(&(input_info.lock));
// 这里可以使用OpenMp
if(cal_input_info.buffer_flag) {
for(i = 0; i < BUFFER_SIZE; i++) {
write_buffer_a[i] = read_buffer_a[i] + 1;
}
} else {
for(i = 0; i < BUFFER_SIZE; i++) {
write_buffer_b[i] = read_buffer_b[i] + 1;
}
}
pthread_mutex_lock(&(cal_output_info.lock));
while(cal_output_info.run_flag == 0) {
pthread_cond_wait(&(cal_output_info.cond), &(cal_output_info.lock));
}
cal_output_info.run_flag = 0;
pthread_mutex_unlock(&(cal_output_info.lock));
pthread_mutex_lock(&(output_info.lock));
output_info.run_flag = 1;
pthread_cond_signal(&(output_info.cond));
pthread_mutex_unlock(&(output_info.lock));
if(loop_index == loop_nums - 1) {
break;
}
loop_index++;
}
pthread_mutex_lock(&(cal_output_info.lock));
while(cal_output_info.run_flag == 0) {
pthread_cond_wait(&(cal_output_info.cond), &(cal_output_info.lock));
}
cal_output_info.run_flag = 0;
pthread_mutex_unlock(&(cal_output_info.lock));
pthread_mutex_lock(&(output_info.lock));
output_info.run_flag = 1;
output_info.shutdown = 1;
pthread_cond_signal(&(output_info.cond));
pthread_mutex_unlock(&(output_info.lock));
pthread_join(input_info.thread_id, NULL);
pthread_join(output_info.thread_id, NULL);
free_resources(4, &input_info, &output_info, &cal_input_info, &cal_output_info);
fclose(fp_input);
fclose(fp_output);
return 0;
}
参考
本文主要参考了这个Pthreads线程池
【Pthreads】Pipeline Model(Assembly Line)示例的更多相关文章
- Lavavel 程序报错 MassAssignmentException in Model.php line 452: _token
Lavarel 用类似于下面命令插入数据时候出错 Comment::create($request->all()) 错误页面截图如下: 错误原因:下面这行代码应该写到对应的Model里面,而不是 ...
- [转]ARM Pipeline
Add r0, PC, # g_oalAddressTable - (+ 8) instruction, a lot of people had cprogramdev.com Forum asked ...
- Spark ML Pipeline简介
Spark ML Pipeline基于DataFrame构建了一套High-level API,我们可以使用MLPipeline构建机器学习应用,它能够将一个机器学习应用的多个处理过程组织起来,通过在 ...
- [UE4] Adding a custom shading model
转自:https://blog.felixkate.net/2016/05/22/adding-a-custom-shading-model-1/ This was written in Februa ...
- devops-2:Jenkins的使用及Pipeline语法讲解
DevOps-Jenkins Jenkins简介 Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件项目可以进行持续 ...
- ASP.NET MVC Model验证(一)
ASP.NET MVC Model验证(一) 前言 前面对于Model绑定部分作了大概的介绍,从这章开始就进入Model验证部分了,这个实际上是一个系列的Model的绑定往往都是伴随着验证的.也会在后 ...
- Spark.ML之PipeLine学习笔记
地址: http://spark.apache.org/docs/2.0.0/ml-pipeline.html Spark PipeLine 是基于DataFrames的高层的API,可以方便用户 ...
- jenkins Pipeline 使用
说明 Jenkins pipeline 是一套插件,支持将连续输送管道实施和整合到Jenkins.Pipeline提供了一组可扩展的工具,用于通过管道DSL为代码创建简单到复杂的传送流水线.他目前支持 ...
- MyBatis逆向工程:根据table生成Model、Mapper、Mapper.xml
逆向工程工具 下载地址:https://download.csdn.net/download/zhutouaizhuwxd/10779140 1.工程导入Eclipse 2.运行MainUI.jav ...
随机推荐
- IntelliJ IDEA 2017版 编译器使用学习笔记(八) (图文详尽版);IDE快捷键使用;IDE代码重构(寻找修改痕迹)
git集成: 快速找到版本控制器中某段代码的作者 一.annotate 选中某行代码,右键,选择annotate,鼠标放于其上就会显示注释 二.移动所有改动之处: prev ...
- ASP.NET 日志组件Smart.LogNet.DLL 引用即可写入日志及读取日志
借助LogNet组件,以后查看站点日志,再也不用去服务器下载了 日志组件:LogNet.DLL ,引用即可使用 写入方法: 1.LogNet.Log.WriteLog("日志标题" ...
- 一些js在线引用文档
1.jquery在线引用: <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script& ...
- Docker Compose部署 nginx代理Tomcat集群
一.简介 使用Docker镜像部署Nginx代理的多个Tomcat集群: 使用Dockerfile构建nginx镜像 使用Dockerfile构建tomcat镜像 mysql镜像使用docker hu ...
- sql_id VS hash_value
有没有发现,v$session,v$sql,v$sqlarea,v$sqltext,v$sql_shared_cursor等试图连接的时候经常会用到hash_value,sql_id,但是他们2个之间 ...
- webservice之helloword(web)rs
spring整合webservice 1.pom.xml文件 <dependencies> <!-- cxf 进行rs开发 必须导入 --> <dependency> ...
- webService之helloword(java)rs
webservice之rs(helloworld) 1.pom.xml文件 <dependencies> <!-- 使用CXF RS开发 --> <dependency& ...
- Eclipse workspace 被占用问题
eclipse 使用一段时间后,有时会因为一些故障自己就莫名奇妙的关闭了,再打开时有时没有问题,有时会提示错误 Workspace Unavailable: Workspace in use or c ...
- poj3061
#include<stdio.h> #include<iostream> using namespace std; #include<algorithm> cons ...
- HDU1025贫富平衡
做01背包做到的这个LIS,常见的n2会超时,所以才有nlogn可行 先来介绍一下n2 dp[i] 表示该序列以a[i]为结尾的最长上升子序列的长度 所以第一层循环循环数组a,第二层循环循环第i个元素 ...