【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 ...
随机推荐
- 返回结点值为e的二叉树指针
题意为,如果二叉树某结点的值为e(假定是整型二叉树),返回这个结点的指针.初看这道题,联想到二叉树可以很简单的遍历,直接返回这个指针不就行了吗?如下图所示,假如要返回值为3的结点指针: 设计好了一个函 ...
- 用Kotlin写一个基于Spring Boot的RESTful服务
Spring太复杂了,配置这个东西简直就是浪费生命.尤其在没有什么并发压力,随便搞一个RESTful服务 让整个业务跑起来先的情况下,更是么有必要纠结在一堆的XML配置上.显然这么想的人是很多的,于是 ...
- C#-VS发布网站-准备待发布网站-摘
通过使用“发布网站”工具部署网站项目 准备网站源文件 在vs生成发布文件 配置IIS .NET Framework 4 其他版本 Visual Studio 2008 Visual Studio ...
- War Chess (hdu 3345)
http://acm.hdu.edu.cn/showproblem.php?pid=3345 Problem Description War chess is hh's favorite game:I ...
- python 的 字节码 导入使用
1. python 模块文件可以通过编译为字节码的形式: 名字:model.py x = def funt(): import model print(model.x) x = "zhang ...
- 6.form表单四种提交方式
一.使用jquery的ajax方式提交: 二.使用easyui的form组件内置的submit方法提交: 三.先定义表单,然后使用submit方法提交: 四.先定义表单,然后按下enter键时提交:
- 用jquery将输入的文字的双向绑定
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...
- RxSwift学习笔记1:RxSwift的编程风格
第一天:简单体验与RxSwift的编程风格 import UIKit//导入Rx相关框架 import RxSwift import RxCocoa struct Music { let name:S ...
- Android-Java控制多线程执行顺序
功能需求: Thread-0线程:打印 1 2 3 4 5 6 Thread-1线程:打印1 1 2 3 4 5 6 先看一个为实现(功能需求的案例) package android.java; // ...
- INDEX--从数据存放的角度看索引
测试表结构: CREATE TABLE TB1 ( ID ,), C1 INT, C2 INT ) 1. 聚集索引(Clustered index) 聚集索引可以理解为一个包含表中除索引键外多有剩余列 ...