场景

1.经常在Windows, MacOSX 开发C多线程程序的时候, 经常需要和线程打交道, 如果开发人员的数量不多时, 同时掌握Win32和pthread线程

并不是容易的事情, 而且使用Win32线程并不能写出跨平台的实现. 所以在成本的制约下选用pthread作为跨平台线程库的首选. 有足够人力的公司可以再封装一层对Win32和本地pthread的调用. 比如 chrome.

2.线程在做高可用, 高性能的程序时必不可少, 比如Socket, 并发任务, 顺序任务,文件下载等需要充分利用CPU资源节省执行时间的编码上起着至关重要的作用. 没有多线程的话, 再处理后台任务,多任务上会更麻烦一些, 当然可以使用协程, 但那个并不灵活和常见.

3.学习一门线程模型, 最让人关心的就是到底在什么场景下需要用到线程, 这种POSIX 线程模型还能怎么用?

介绍

1.POSIX 线程, 通常称作pthreads, 是独立于语言的执行模型, 也是一个并行执行模型. 它允许程序在重叠的时间控制不同的工作流. 每一个工作流被称作线程, 同时创建和控制这些流程的工作是通过POSIX Threads API来实现的. POSIX Threads 的API定义是通过标准的

POSIX.1c, Threads extensions (IEEE Std 1003.1c-1995).

2.API的实现在所有的Unix-like系统里都有,比如 FreeBSD, NetBSD, OpenBSD, Linux, Mac OS X and Solaris, 对于Windows系统, 在 SFU/SUA 子系统里提供了一组POSIX APIs的实现, 并且有第3方库 pthread-w32, 它是基于存在的Windows API 实现的.

说明

1.《Programming with Posix Threads》 这本书介绍了3种模型(Pipeline,Work Crew,Client/Server). 我们有必要分析下, 虽然现实场景比这些更复杂, 但是他们组成了基础, 掌握了更多的基础才能组成复杂的房屋设计, 可以说是一种设计模式.

2.新学习这个pipeline模型, 还没在实际项目中使用, 所以没有很好的例子, 暂时用官方例子说明, 当然这个例子实在过于简单. 但是简单也正好记住它的原理.

管道(Pipeline)

说明

每个线程重复执行在序列集合里的相同的操作, 并传递每个线程的输出到另一个线程执行下一个步骤, 可以称作为流水线.

特点

  1. 每个线程就像一个生产特定部件的机器, 它只生产某个部件, 不做其他事情.
  2. 这个线程一直在做这个事情, 不会停止, 有材料时会一直生产完, 生产一个部件就会传递到下一个线程.
  3. 它属于一个流水线中一部分, 每个步骤都是有顺的, 适合处理有序的数据流.

例子:

一个图片文件, 线程A 负责读取这个图片到数组, 线程B 负责搜索图片数组里的某些特定数据, 线程C 负责搜集线程B搜索到的流式数据到报表里.

或者 线程A,B,C 在一个有序序列里分别对数据进行处理.

注: 看到这里有人会问, 没必要3个线程啊, 用1个线程处理也行阿. 我们想想线程的作用, 节省时间, 充分利用CPU资源实现并发.假如数据分析总共需要9分钟时间, 有10个这样的任务, 单线程就需要90 分钟. 如果是3个线程, 每个线程处理一部分3分钟的数据. 总共需要

3*10+2 = 32 分钟. 性价比很高吧.

代码例子

pipe.c :

1.每个在管道里的线程对它的输入值+1, 并传递到下一个线程. 主程序从stdin读入一系列命令行. 1个命令行或者是一个数字, 这个数字注入到管道的开始. 或者字符 “=.” 它会让程序从管道末尾读入下一个结果并把它输出到stdout. 主函数创建管道, 并且循环从stdin里读入行数据. 如果行数据是一个单一 “=” 字符. 它会从管道拉取一个值并且打印他们, 否则它会转换行为一个整数值

  1. /*
  2. * The main program to "drive" the pipeline...
  3. */
  4. int main (int argc, char *argv[])
  5. {
  6. pipe_t my_pipe;
  7. long value, result;
  8. int status;
  9. char line[128];
  10. pipe_create (&my_pipe, 10);
  11. printf ("Enter integer values, or \"=\" for next result\n");
  12. while (1) {
  13. printf ("Data> ");
  14. if (fgets (line, sizeof (line), stdin) == NULL) exit (0);
  15. if (strlen (line) <= 1) continue;
  16. if (strlen (line) <= 2 && line[0] == '=') {
  17. if (pipe_result (&my_pipe, &result))
  18. printf ("Result is %ld\n", result);
  19. else
  20. printf ("Pipe is empty\n");
  21. } else {
  22. if (sscanf (line, "%ld", &value) < 1)
  23. fprintf (stderr, "Enter an integer value\n");
  24. else
  25. pipe_start (&my_pipe, value);
  26. }
  27. }
  28. }

2.管道里的每个stage使用一个stage_t类型变量表示. stage_t包含一个mutex来同步访问这个stage. avail条件变量用来发送给stage表明数据已经准备好可以处理了, 同时每个阶段拥有一个条件变量ready来表明它准备接收新的数据了. data 成员变量是前一个stage传递来的, thread是操作这个stage的线程, next 是指向下一个stage的指针.

3.pipe_t 结构体描述了一个管道. 它提供指针指向管道阶段的第一和最后一个stage. 第一个stage, head代表了管道里的第一个线程. 最后一个stage, tail是一个特别的stage_t, 它没有线程– 因为它只是用来存储管道的最后数据.

  1. /*
  2. * Internal structure describing a "stage" in the
  3. * pipeline. One for each thread, plus a "result
  4. * stage" where the final thread can stash the value.
  5. */
  6. typedef struct stage_tag {
  7. pthread_mutex_t mutex; /* Protect data */
  8. pthread_cond_t avail; /* Data available */
  9. pthread_cond_t ready; /* Ready for data */
  10. int data_ready; /* Data present */
  11. long data; /* Data to process */
  12. pthread_t thread; /* Thread for stage */
  13. struct stage_tag *next; /* Next stage */
  14. } stage_t;
  15. /*
  16. * External structure representing the entire
  17. * pipeline.
  18. */
  19. typedef struct pipe_tag {
  20. pthread_mutex_t mutex; /* Mutex to protect pipe */
  21. stage_t *head; /* First stage */
  22. stage_t *tail; /* Final stage */
  23. int stages; /* Number of stages */
  24. int active; /* Active data elements */
  25. } pipe_t;

4.pipe_send, 这个函数用来开始管道传输, 并且也是被每个stage调用来传递data到下一个stage里. 它首先开始等待指定的stage的ready 条件变量直到它能接收新的数据(之前的数据没处理完). 存储新的数据值, 同时告诉stage数据已经准备好.

  1. /*
  2. * Internal function to send a "message" to the
  3. * specified pipe stage. Threads use this to pass
  4. * along the modified data item.
  5. */
  6. int pipe_send (stage_t *stage, long data)
  7. {
  8. int status;
  9. status = pthread_mutex_lock (&stage->mutex);
  10. if (status != 0)
  11. return status;
  12. /*
  13. * If there's data in the pipe stage, wait for it
  14. * to be consumed.
  15. */
  16. while (stage->data_ready) {
  17. status = pthread_cond_wait (&stage->ready, &stage->mutex);
  18. if (status != 0) {
  19. pthread_mutex_unlock (&stage->mutex);
  20. return status;
  21. }
  22. }
  23. /*
  24. * Send the new data
  25. */
  26. stage->data = data;
  27. stage->data_ready = 1;
  28. status = pthread_cond_signal (&stage->avail);
  29. if (status != 0) {
  30. pthread_mutex_unlock (&stage->mutex);
  31. return status;
  32. }
  33. status = pthread_mutex_unlock (&stage->mutex);
  34. return status;
  35. }

5.pipe_stage是管道里每个stage的开始函数, 这个函数的参数就是指向stage_t结构体的指针. 这个线程永远循环执行处理数据. 因为 mutex在循环外被locked, 线程看起来好像一直锁定stage的 mutex对象, 然而, 它是花费大多数时间来等待新的数据, 通过avail条件变量. 注意这个线程自动解锁关联到条件变量的mutex. 当data获取到数据, 线程自增数据值, 同时传递结果到下一个stage. 接着线程清除data_ready变量值来表明不再有数据, 同时发信号给ready条件变量来唤醒那些正在等待这个管道stage就绪的线程.

注意: 这个stage在处理数据时并没有解锁stage的mutex,也就是如果在处理耗时的操作时一直是锁住的. 只要data_ready不设置为0之前,可以在处理耗工作前解锁, 处理完耗时的工作之后再加锁设置 data_ready = 0.

  1. /*
  2. * The thread start routine for pipe stage threads.
  3. * Each will wait for a data item passed from the
  4. * caller or the previous stage, modify the data
  5. * and pass it along to the next (or final) stage.
  6. */
  7. void *pipe_stage (void *arg)
  8. {
  9. stage_t *stage = (stage_t*)arg;
  10. stage_t *next_stage = stage->next;
  11. int status;
  12. status = pthread_mutex_lock (&stage->mutex);
  13. if (status != 0)
  14. err_abort (status, "Lock pipe stage");
  15. while (1) {
  16. while (stage->data_ready != 1) {
  17. status = pthread_cond_wait (&stage->avail, &stage->mutex);
  18. if (status != 0)
  19. err_abort (status, "Wait for previous stage");
  20. }
  21. pipe_send (next_stage, stage->data + 1);
  22. stage->data_ready = 0;
  23. status = pthread_cond_signal (&stage->ready);
  24. if (status != 0)
  25. err_abort (status, "Wake next stage");
  26. }
  27. /*
  28. * Notice that the routine never unlocks the stage->mutex.
  29. * The call to pthread_cond_wait implicitly unlocks the
  30. * mutex while the thread is waiting, allowing other threads
  31. * to make progress. Because the loop never terminates, this
  32. * function has no need to unlock the mutex explicitly.
  33. */
  34. }

6.pipe_create函数创建一个管道, 它能创建任意个stage, 并连接他们成为一个链表. 对于每个stage, 它分配内存给stage_t 结构体并且初始化成员. 注意最后一个stage是被分配和初始化来保存管道最后的结果. 注意最后一个stage并不创建线程.

注意: 这里的链表操作方法,不需要额外的判断.

  1. /*
  2. * External interface to create a pipeline. All the
  3. * data is initialized and the threads created. They'll
  4. * wait for data.
  5. */
  6. int pipe_create (pipe_t *pipe, int stages)
  7. {
  8. int pipe_index;
  9. stage_t **link = &pipe->head, *new_stage, *stage;
  10. int status;
  11. status = pthread_mutex_init (&pipe->mutex, NULL);
  12. if (status != 0)
  13. err_abort (status, "Init pipe mutex");
  14. pipe->stages = stages;
  15. pipe->active = 0;
  16. for (pipe_index = 0; pipe_index <= stages; pipe_index++) {
  17. new_stage = (stage_t*)malloc (sizeof (stage_t));
  18. if (new_stage == NULL)
  19. errno_abort ("Allocate stage");
  20. status = pthread_mutex_init (&new_stage->mutex, NULL);
  21. if (status != 0)
  22. err_abort (status, "Init stage mutex");
  23. status = pthread_cond_init (&new_stage->avail, NULL);
  24. if (status != 0)
  25. err_abort (status, "Init avail condition");
  26. status = pthread_cond_init (&new_stage->ready, NULL);
  27. if (status != 0)
  28. err_abort (status, "Init ready condition");
  29. new_stage->data_ready = 0;
  30. *link = new_stage;
  31. link = &new_stage->next;
  32. }
  33. *link = (stage_t*)NULL; /* Terminate list */
  34. pipe->tail = new_stage; /* Record the tail */
  35. /*
  36. * Create the threads for the pipe stages only after all
  37. * the data is initialized (including all links). Note
  38. * that the last stage doesn't get a thread, it's just
  39. * a receptacle for the final pipeline value.
  40. *
  41. * At this point, proper cleanup on an error would take up
  42. * more space than worthwhile in a "simple example", so
  43. * instead of cancelling and detaching all the threads
  44. * already created, plus the synchronization object and
  45. * memory cleanup done for earlier errors, it will simply
  46. * abort.
  47. */
  48. for ( stage = pipe->head;
  49. stage->next != NULL;
  50. stage = stage->next) {
  51. status = pthread_create (
  52. &stage->thread, NULL, pipe_stage, (void*)stage);
  53. if (status != 0)
  54. err_abort (status, "Create pipe stage");
  55. }
  56. return 0;
  57. }

7.pipe_start和pipe_result函数. pipe_start函数入栈一个data数据到管道的开始stage并且迅速返回,不等待结果. pipe_result函数允许请求者等待最终的结果. pipe_start 函数递增pipeline里的active计数, 这个active允许pipe_result探测没有更多的active项需要收集, 同时迅速返回不阻塞. pipe_result 首先检查是否有一个active项在管道里. 如果没有, 它返回status为0, 之后解锁pipeline的mutex. 如果有另一个item在管道里, pipe_result锁定tail stage,并且等待它接收数据. 它复制数据并且重置tail stage以便能接收下一个数据项.

  1. /*
  2. * External interface to start a pipeline by passing
  3. * data to the first stage. The routine returns while
  4. * the pipeline processes in parallel. Call the
  5. * pipe_result return to collect the final stage values
  6. * (note that the pipe will stall when each stage fills,
  7. * until the result is collected).
  8. */
  9. int pipe_start (pipe_t *pipe, long value)
  10. {
  11. int status;
  12. status = pthread_mutex_lock (&pipe->mutex);
  13. if (status != 0)
  14. err_abort (status, "Lock pipe mutex");
  15. pipe->active++;
  16. status = pthread_mutex_unlock (&pipe->mutex);
  17. if (status != 0)
  18. err_abort (status, "Unlock pipe mutex");
  19. pipe_send (pipe->head, value);
  20. return 0;
  21. }
  22. /*
  23. * Collect the result of the pipeline. Wait for a
  24. * result if the pipeline hasn't produced one.
  25. */
  26. int pipe_result (pipe_t *pipe, long *result)
  27. {
  28. stage_t *tail = pipe->tail;
  29. long value;
  30. int empty = 0;
  31. int status;
  32. status = pthread_mutex_lock (&pipe->mutex);
  33. if (status != 0)
  34. err_abort (status, "Lock pipe mutex");
  35. if (pipe->active <= 0)
  36. empty = 1;
  37. else
  38. pipe->active--;
  39. status = pthread_mutex_unlock (&pipe->mutex);
  40. if (status != 0)
  41. err_abort (status, "Unlock pipe mutex");
  42. if (empty)
  43. return 0;
  44. pthread_mutex_lock (&tail->mutex);
  45. while (!tail->data_ready)
  46. pthread_cond_wait (&tail->avail, &tail->mutex);
  47. *result = tail->data;
  48. tail->data_ready = 0;
  49. pthread_cond_signal (&tail->ready);
  50. pthread_mutex_unlock (&tail->mutex);
  51. return 1;
  52. }

术语

SFU (Windows Services for UNIX)

SUA (Interix subsystem component Subsystem for UNIX-based Applications)

参考

https://en.wikipedia.org/wiki/POSIX_Threads

https://en.wikipedia.org/wiki/Windows_Services_for_UNIX

Programming with POSIX Threads

[并发并行]_[线程模型]_[Pthread线程使用模型之一管道Pipeline]的更多相关文章

  1. [并发并行]_[线程模型]_[Pthread线程使用模型之三 客户端/服务端模型(Client/Server]

    Pthread线程使用模型之三 客户端/服务端模型(Client/Server) 场景 1.在客户端/服务端模型时,客户端向服务端请求一些数据集的操作. 服务端执行执行操作独立的(多进程或跨网络)– ...

  2. [并发并行]_[线程模型]_[Pthread线程使用模型之二 工作组work crew]

    Pthread线程使用模型之二工作组(Work crew) 场景 1.一些耗时的任务,比如分析多个类型的数据, 是独立的任务, 并不像 pipeline那样有序的依赖关系, 这时候pipeline就显 ...

  3. 8.15 day33 进程池与线程池_协程_IO模型(了解)

    进程池和线程池 开进程开线程都需要消耗资源,只不过两者比较的情况线程消耗的资源比较少 在计算机能够承受范围之内最大限度的利用计算机 什么是池? ​ 在保证计算机硬件安全的情况下最大限度地利用计算机 ​ ...

  4. 线程模型、pthread 系列函数 和 简单多线程服务器端程序

    一.线程有3种模型,分别是N:1用户线程模型,1:1核心线程模型和N:M混合线程模型,posix thread属于1:1模型. (一).N:1用户线程模型 “线程实现”建立在“进程控制”机制之上,由用 ...

  5. posix 线程(一):线程模型、pthread 系列函数 和 简单多线程服务器端程序

    posix 线程(一):线程模型.pthread 系列函数 和 简单多线程服务器端程序 一.线程有3种模型,分别是N:1用户线程模型,1:1核心线程模型和N:M混合线程模型,posix thread属 ...

  6. 并发,并行,线程,进程,GIL锁

    1.并发和并行 并发: 同时做某些事,但是强调同一时段做多件事 如:同一路口,发生了车辆要同时通过路面的时间. 并行: 互不干扰的在同一时刻做多件事 如:同一时刻,同时有多辆车在多条车道上跑,即同时发 ...

  7. 关于CPU核心,线程,进程,并发,并行,及java线程之间的关系

    前言:作为一个转行java的小白,一直搞不清楚java中的多线程.于是来梳理一下关于CPU核心,线程,进程,并发,并行,及java线程之间的关系, 1.CPU角度来看: 我们以Intel的Core i ...

  8. 高效并发一 Java内存模型与Java线程(绝对干货)

    高效并发一 Java内存模型与Java线程 本篇文章,首先了解虚拟机Java 内存模型的结构及操作,然后讲解原子性,可见性,有序性在 Java 内存模型中的体现,最后介绍先行发生原则的规则和使用. 在 ...

  9. python并发编程-进程池线程池-协程-I/O模型-04

    目录 进程池线程池的使用***** 进程池/线程池的创建和提交回调 验证复用池子里的线程或进程 异步回调机制 通过闭包给回调函数添加额外参数(扩展) 协程*** 概念回顾(协程这里再理一下) 如何实现 ...

随机推荐

  1. checkbox的readonly属性设置

    方式一: checkbox没有readOnly属性,如果使用disabled=“disabled”属性的话,会让checkbox变成灰色的,用户很反感这种样式可以这样让它保持只读: 设置它的oncli ...

  2. non-fragile:oc2.0特性

    Runtime Versions and Platforms There are different versions of the Objective-C runtime on different ...

  3. 「FJ2014集训 采药人的路径」

    题目 考虑一下把\(0\)看成\(-1\),那么就是找到一条边权和为\(0\)的路径,且这条路径可以被分成两段,边权和都是\(0\) 没有第二个限制就是点分裸题了 其实有了第二个限制还是点分裸题 考虑 ...

  4. Ajax实例二:取得新内容

    Ajax实例二:取得新内容 通过点击pre和next按钮,从服务器取得最新内容. HTML代码 <div id="slide">图片显示区</div> &l ...

  5. Crontab 学习

    分钟 小时 日期 月份 星期几 整点执行 0 8-22 * * * /usr/bin/php /home/anbaojia/wwwroot/shjn/yii report/sync 查看 cron 执 ...

  6. C#处理List<object>重复数据的问题

    private class ListDistinct : IEqualityComparer<object> { public bool Equals(object x, object y ...

  7. linux nginx 配置php

    linux nginx 配置php   下载php源码 解压 configure ./configure --prefix=/usr/local/php --enable-fpm --with-mcr ...

  8. 正则表达式和decimal format的实际项目运用

    最近review测试框架底层代码,一是看看有哪些可以重构的,以便减少冗余增加重用,二是优化一下代码结构增强代码的健壮性. 其中有一个地方印象比较深刻,特记录分享如下: 背景:在电商场景中,价格是特别重 ...

  9. 转-四种方案解决ScrollView嵌套ListView问题

    本人网上用的ID是泡面或安卓泡面,学习一年半之前开始从事Android应用开发,这是我写的第一篇Android技术文章,转载请注明出处和作者,有写的不好的地方还请帮忙指出,谢谢. 在工作中,曾多次碰到 ...

  10. ORA-04044: 此处不允许过程, 函数, 程序包或类型和

    用Orale代码建表时,出现 SQL> comment on column SCORE.cno 2 is '学号(外键)';comment on column SCORE.cno is '学号( ...