前言

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_condwrite_cond, 主线程用read_cond来告诉读线程自己已经开始计算,读线程可以继续读取下一部分数据了,用write_cond告诉写线程,计算已经完毕,可以将结果写入文件了 。而主线程需要两个条件变量,暂称为 cal_condcal_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)示例的更多相关文章

  1. Lavavel 程序报错 MassAssignmentException in Model.php line 452: _token

    Lavarel 用类似于下面命令插入数据时候出错 Comment::create($request->all()) 错误页面截图如下: 错误原因:下面这行代码应该写到对应的Model里面,而不是 ...

  2. [转]ARM Pipeline

    Add r0, PC, # g_oalAddressTable - (+ 8) instruction, a lot of people had cprogramdev.com Forum asked ...

  3. Spark ML Pipeline简介

    Spark ML Pipeline基于DataFrame构建了一套High-level API,我们可以使用MLPipeline构建机器学习应用,它能够将一个机器学习应用的多个处理过程组织起来,通过在 ...

  4. [UE4] Adding a custom shading model

    转自:https://blog.felixkate.net/2016/05/22/adding-a-custom-shading-model-1/ This was written in Februa ...

  5. devops-2:Jenkins的使用及Pipeline语法讲解

    DevOps-Jenkins Jenkins简介 Jenkins是一个开源软件项目,是基于Java开发的一种持续集成工具,用于监控持续重复的工作,旨在提供一个开放易用的软件平台,使软件项目可以进行持续 ...

  6. ASP.NET MVC Model验证(一)

    ASP.NET MVC Model验证(一) 前言 前面对于Model绑定部分作了大概的介绍,从这章开始就进入Model验证部分了,这个实际上是一个系列的Model的绑定往往都是伴随着验证的.也会在后 ...

  7. Spark.ML之PipeLine学习笔记

    地址: http://spark.apache.org/docs/2.0.0/ml-pipeline.html   Spark PipeLine 是基于DataFrames的高层的API,可以方便用户 ...

  8. jenkins Pipeline 使用

    说明 Jenkins pipeline 是一套插件,支持将连续输送管道实施和整合到Jenkins.Pipeline提供了一组可扩展的工具,用于通过管道DSL为代码创建简单到复杂的传送流水线.他目前支持 ...

  9. MyBatis逆向工程:根据table生成Model、Mapper、Mapper.xml

    逆向工程工具 下载地址:https://download.csdn.net/download/zhutouaizhuwxd/10779140 1.工程导入Eclipse  2.运行MainUI.jav ...

随机推荐

  1. 返回结点值为e的二叉树指针

    题意为,如果二叉树某结点的值为e(假定是整型二叉树),返回这个结点的指针.初看这道题,联想到二叉树可以很简单的遍历,直接返回这个指针不就行了吗?如下图所示,假如要返回值为3的结点指针: 设计好了一个函 ...

  2. 用Kotlin写一个基于Spring Boot的RESTful服务

    Spring太复杂了,配置这个东西简直就是浪费生命.尤其在没有什么并发压力,随便搞一个RESTful服务 让整个业务跑起来先的情况下,更是么有必要纠结在一堆的XML配置上.显然这么想的人是很多的,于是 ...

  3. C#-VS发布网站-准备待发布网站-摘

    通过使用“发布网站”工具部署网站项目 准备网站源文件 在vs生成发布文件 配置IIS   .NET Framework 4 其他版本 Visual Studio 2008 Visual Studio ...

  4. War Chess (hdu 3345)

    http://acm.hdu.edu.cn/showproblem.php?pid=3345 Problem Description War chess is hh's favorite game:I ...

  5. python 的 字节码 导入使用

    1. python 模块文件可以通过编译为字节码的形式: 名字:model.py x = def funt(): import model print(model.x) x = "zhang ...

  6. 6.form表单四种提交方式

    一.使用jquery的ajax方式提交: 二.使用easyui的form组件内置的submit方法提交: 三.先定义表单,然后使用submit方法提交: 四.先定义表单,然后按下enter键时提交:

  7. 用jquery将输入的文字的双向绑定

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...

  8. RxSwift学习笔记1:RxSwift的编程风格

    第一天:简单体验与RxSwift的编程风格 import UIKit//导入Rx相关框架 import RxSwift import RxCocoa struct Music { let name:S ...

  9. Android-Java控制多线程执行顺序

    功能需求: Thread-0线程:打印 1 2 3 4 5 6 Thread-1线程:打印1 1 2 3 4 5 6 先看一个为实现(功能需求的案例) package android.java; // ...

  10. INDEX--从数据存放的角度看索引

    测试表结构: CREATE TABLE TB1 ( ID ,), C1 INT, C2 INT ) 1. 聚集索引(Clustered index) 聚集索引可以理解为一个包含表中除索引键外多有剩余列 ...