linux多线程自己从接触很久也有不少实践,但总是觉得理解不够深刻,不够系统。借这篇文章试着再次系统学习一下linux多线程编程,理解编程的concept,细致看一下POSIX pthread API的实现。还是凭借强大的google search,找到几篇不错的文章和教程附在最后。我在这篇文章中的总结大多都是基于这些材料的学习和自己实践经验的一点总结。

Thread基本知识

Process 地址空间

Thread附着在process内部,先看一下process在CPU上是个什么样的吧。启动一个linux process,OS会开辟一块内存用来装载code,保存data和process状态。看一下进程的地址空间。

根据访问权限,进程地址空间分为user space和kernel space。32bit系统中高1G为kernel space,低3G为user space,具体划分为:

Process control block(从高1G kernel space中分配)

stack

memory mapping segment

heap

bss and data

text

1G的kernel space是这台机器上所有processes共享的,每个进程的PCB存在这个空间中,一般应用程序是没有办法直接访问修改的,但是kernel 通过/proc 提供给应用程序一个接口可以查看PCB的信息,部分内容还可以修改,详细可以看一下/proc。剩下的stack/heap/text/...都驻留在process user space,是属于process私有空间。详细的kernel如何管理进程memory还可以再开一篇。

Thread是什么?

process是个重型的运行实体,以process为单位切分任务和调度,os的开销太大了。我们可以把process这个单位再切小些,thread的概念就诞生了。好,我们来看一下怎样把这个单位切小的。简单来讲,thread共享大部分的process的内容,只维护必需的一小部分作为私有内容。

Thread自己维护的私有内容

Kernel space

Stack pointer

Registers

Scheduling properties (such as policy or priority)

Set of pending and blocked signals

Thread specific data.

User space

stack

其他诸如PCB中进程信息,用户空间中的text/data/heap/...都是同一个process下所有Threads共享的。有了这些thread自己私有的信息,os就可以以thread为单位去调度了。因为它是轻量级的,所以相比process,thread一般具有更好的性能,更快的响应速度。但是thread的稳定性和编程复杂度要比process差些,要考虑的内容比较多。

Threads通信和同步

正因为同一个process内的threads间天然共享了大量的内存,thread间的信息交互要比较高效,同时也增加了复杂度,总要处理好共享内存间的互斥。当然process间也可以共享内存,比如通过进程父子关系,或者通过/dev/shm mmap特定物理内存到进程空间内或者其他。

线程间通信

所有的IPC(inter process communication)方法都适用于thread间的通信。比较全的IPC总结,可以参考IPC。比较常用的我们会涉及到message queue,sharememory,semaphore,socket,signal等。semaphore是共享资源互斥的方法,其他都是冗余的方式进行通信。互斥是个比较复杂的话题,我们单开一节讨论一下。

共享资源的互斥

为什么要保护共享资源做互斥访问,这里不罗嗦了。通过对共享资源(临界区)加锁可以实现互斥访问,互斥锁(mutex)也有多种类型。

simple blocking

一方拿到临界区锁后,其它人再来拿锁都会挂起。

Recursive(递归型)

允许锁的拥有者多次申请锁而不被挂起,对递归调用有用。

Reader/Writer

允许多个reader同时share读锁,如果有reader在读,writer申请锁会block直到所有reader释放。可以理解为一写多读,写时互斥。这种锁有写饿死的风险。

其中POSIX的pthread库支持recursive和reader/writer类型的锁。

共享访问带来的风险和挑战

共享访问中有写操作,必然要考虑互斥。互斥有风险,使用需谨慎。如果你最终不可避免的要使用互斥锁,要关注互斥锁的这些风险。

deadlock(死锁)

死锁一般发生在双方或者多方在申请两个以上的互斥锁,然后大家各拿了部分,互不相让。开发者要尽量避免这种编程场景发生,如果真的需要可以编程要么同时获得,要么一个都不要,做人要有骨气!

race condition(竞争条件)

共享资源在没有互斥机制保护时,由于线程调度的不确定性会导致共享的资源变化无序无规律,程序的输出也就不确定了。共享资源无互斥保护,线程间竞争访问,输出无法保证。这要求开发者要特别小心识别出程序中的那些共享资源,加锁保护。尤其是第三方的开源软件,多线程调用时要注意是否是线程安全的。

priority reversion(优先级反转)

优先级反转是个很有意思的问题,尤其是在嵌入式实时OS上,进程/线程的调度是抢占式的,高优先级的任务ready时可以直接抢占CPU,这事再加上互斥就容易出问题了。比如三个任务H,M,L,优先级递减,同时H和L共享资源R。当L先申请到互斥锁访问临界区还没释放R的时候,H这时候申请R访问导致自己挂起,这么巧M变ready了,OS调度让M抢占了L的cpu。如果L一直得不到执行并释放R,这样就造成了高优先级的H得不到执行,反而一些比H优先级低的M们能得到CPU。这就是优先级反转。实时OS的高优先级任务一般都是比较重要的任务需要马上处理,得不到处理意味着可能要出大事。所以这个问题的影响还是挺大的,比较著名的例子就是火星探路者的故事,可以参考一下火星探路者故障分析。解决方法也有不少

尽量避免不同优先级的任务共享资源,可以通过信息容易做任务间通信。

访问临界区时关闭中断,保证临界区的代码执行不被强占。嵌入式编程中常用。

优先级继承,当有高优先级任务想要访问共享资源时,提高正在执行的低优先级任务的优先级到高优先级级别直至退出临界区。上面的探路者修正程序使用了该方法。

随机提高ready且持有锁的任务优先级,windows用了该方法。

Multi Threads应用场景

写了这么多,那到底什么时候可以应用多线程来解决问题呢?根据经验,一般下面一些场景我们可以考虑使用多线程。

多核处理器,任务比较容易切分为并行处理的小块。如果是计算密集型的,线程数量可以考虑跟core的数量相当。

有delay比较多的IO操作,可以考虑将IO的操作分离给单独的线程。

有人机交互和实时响应等实时性要求较高任务,可以考虑分离为优先级较高的线程。

有大量实时要求不高的计算,可以考虑分离为优先级较低的后台任务。

Thread编程模型

实事求是,具体问题具体分析是放之四海而皆准的问题解决之道,所以没有普适的编程模型。下面列举3种应用比较多的模型以供学习。

Thread Pool (Master/Worker)

通过线程池维护一组可用的线程,master作为主线程负责管理维护worker线程,同时负责对外接口和工作的分发。

Peer (Workcrew)

跟master/worker类似,只是master在启动线程池后退化为普通一员,大家一起分担任务,没有主从的星形拓扑结构。

Pipeline

跟CPU的pipline技术类似,将一个工作流分成很多串行的部分,每一部分都由不同的线程负责,大家各司其职,我做完我的工作就转交给下一个线程,齐心协力最后完成整个工作。流水线如果拍的好可以很好的提高工作效率,但是这种模型风险也比较大,一定要处理好工作的切分,和线程间的交互。

POSIX API详解

Thread management

pthread_create (thread,attr,start_routine,arg) #创建thread

pthread_exit (status) # thread退出

pthread_cancel (thread) # 退出指定的thread

pthread_attr_init (attr) #初始化thread属性

pthread_attr_destroy(attr)

pthread_setaffinity_np or sched_setaffinity # 设置thread可运行的CPU,也就是绑定CPU

pthread_join (threadid,status) # 阻塞等待threadid指定的thread完成

pthread_detach (threadid) # 线程创建默认是joinable,调用该函数设置线程的状态为detached,则该线程运行结束后会自动释放所有资源,别人再join等待它时候不会阻塞了。

pthread_attr_setdetachstate (attr,detachstate)

pthread_attr_getdetachstate (attr,detachstate)

pthread_self () # 返回自己所在的线程id

pthread_equal (thread1,thread2) # 比较两个线程

大部分API见名思意比较简单,详细看一下pthread_create.

#include

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

参数说明:

thread: 指针,所指向的内存存储新创建thread的属性,返回给caller来标识该线程

attr: thread的配置参数集

start_routine: thread创建后执行的处理函数,thread的主体

arg: start_routine的入参

功能说明:

创建thread API,成功后返回0. 创建的thread跟创建者是平行关系,没有等级继承关系。

thread有以下属性

Detached or joinable state

Scheduling inheritance

Scheduling policy

Scheduling parameters

Scheduling contention scope

Stack size

Stack address

Stack guard (overflow) size

Mutexes

pthread_mutex_init (mutex,attr) # 动态生成一个mutex变量

pthread_mutex_destroy (mutex) # 释放mutex变量

pthread_mutexattr_init (attr) # 设置mutex属性

pthread_mutexattr_destroy (attr)

pthread_mutex_lock (mutex) # lock操作,如果mutex已经lock调用者会阻塞

pthread_mutex_trylock (mutex) # 尝试lock,非阻塞调用

pthread_mutex_unlock (mutex) # unlock操作

Condition variables

pthread_cond_init (condition,attr)

pthread_cond_destroy (condition)

pthread_condattr_init (attr)

pthread_condattr_destroy (attr)

pthread_cond_wait (condition,mutex) # 调用者阻塞直到condition条件成立,注意调用者阻塞时会自动释放mutex,唤醒时会自动lock mutex。调用前确保lock mutex,调用后确保调用unlock mutex

pthread_cond_signal (condition) # 通知对方条件满足,调用前确保lock mutex,调用后确保调用unlock mutex

pthread_cond_broadcast (condition)

条件变量是另外一种线程间同步的方式,其实是一种挂起和唤醒的通信方式。可以理解为定义一个条件变量定义了一个线程间的通信通道,wait这个变量一方其实是在等待有人在这个通道上发个信号来,如果没有人发信号他就一直阻塞挂起。它需要跟mutex配合使用,直接通过一个例子感受一下。条件变量的存在就是让wait的这一方睡起来直到有人通知它条件满足可以起来干活了,否则没有条件变量只用mutex做同步,这个wait的一方需要不断的查询是否条件满足,低效浪费。

#include

#include

#include

#define NUM_THREADS 3

#define TCOUNT 10

#define COUNT_LIMIT 12

int count = 0;

pthread_mutex_t count_mutex;

pthread_cond_t count_threshold_cv;

void *inc_count(void *t)

{

int i;

long my_id = (long)t;

for (i=0; i < TCOUNT; i++) {

pthread_mutex_lock(&count_mutex);

count++;

/*

Check the value of count and signal waiting thread when condition is reached. Note that this occurs while mutex is locked.

*/

if (count == COUNT_LIMIT) {

printf("inc_count(): thread %ld, count = %d Threshold reached. ",

my_id, count);

pthread_cond_signal(&count_threshold_cv);

printf("Just sent signal.\n");

}

printf("inc_count(): thread %ld, count = %d, unlocking mutex\n",

my_id, count);

pthread_mutex_unlock(&count_mutex);

/* Do some work so threads can alternate on mutex lock */

sleep(1);

}

pthread_exit(NULL);

}

void *watch_count(void *t)

{

long my_id = (long)t;

printf("Starting watch_count(): thread %ld\n", my_id);

/*

Lock mutex and wait for signal. Note that the pthread_cond_wait routine

will automatically and atomically unlock mutex while it waits.

Also, note that if COUNT_LIMIT is reached before this routine is run by

the waiting thread, the loop will be skipped to prevent pthread_cond_wait

from never returning.

*/

pthread_mutex_lock(&count_mutex);

while (count < COUNT_LIMIT) {

printf("watch_count(): thread %ld Count= %d. Going into wait...\n", my_id,count);

pthread_cond_wait(&count_threshold_cv, &count_mutex);

printf("watch_count(): thread %ld Condition signal received. Count= %d\n", my_id,count);

printf("watch_count(): thread %ld Updating the value of count...\n", my_id,count);

count += 125;

printf("watch_count(): thread %ld count now = %d.\n", my_id, count);

}

printf("watch_count(): thread %ld Unlocking mutex.\n", my_id);

pthread_mutex_unlock(&count_mutex);

pthread_exit(NULL);

}

int main(int argc, char *argv[])

{

int i, rc;

long t1=1, t2=2, t3=3;

pthread_t threads[3];

pthread_attr_t attr;

/* Initialize mutex and condition variable objects */

pthread_mutex_init(&count_mutex, NULL);

pthread_cond_init (&count_threshold_cv, NULL);

/* For portability, explicitly create threads in a joinable state */

pthread_attr_init(&attr);

pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

pthread_create(&threads[0], &attr, watch_count, (void *)t1);

pthread_create(&threads[1], &attr, inc_count, (void *)t2);

pthread_create(&threads[2], &attr, inc_count, (void *)t3);

/* Wait for all threads to complete */

for (i = 0; i < NUM_THREADS; i++) {

pthread_join(threads[i], NULL);

}

printf ("Main(): Waited and joined with %d threads. Final value of count = %d. Done.\n",

NUM_THREADS, count);

/* Clean up and exit */

pthread_attr_destroy(&attr);

pthread_mutex_destroy(&count_mutex);

pthread_cond_destroy(&count_threshold_cv);

pthread_exit (NULL);

}

Synchronization

pthread_rwlock_destroy

pthread_rwlock_init

pthread_rwlock_rdlock

pthread_rwlock_timedrdlock

pthread_rwlock_timedwrlock

pthread_rwlock_tryrdlock

pthread_rwlock_trywrlock

pthread_rwlock_unlock

pthread_rwlock_wrlock

pthread_rwlockattr_destroy

pthread_rwlockattr_getpshared

pthread_rwlockattr_init

pthread_rwlockattr_setpshared

上面提到的读写锁。允许多个reader同时share读锁,如果有reader在读,writer申请锁会block直到所有reader释放。

linux posix 线程池_linux多线程--POSIX Threads Programming相关推荐

  1. linux posix 线程池_posix多线程有感--自旋锁

    转自:http://www.csdn123.com/html/blogs/20130509/11141.htm 自旋锁是SMP架构中的一种low-level的同步机制. 当线程A想要获取一把自旋锁而该 ...

  2. linux usleep 线程控制权_linux多线程同步—信号量

    linux多线程编程-信号量 信号量机制 锁机制使用是有限制的,锁只有两种状态,即加锁和解锁,对于互斥的访问一个全局变量,这样的方式还可以对付,但是要是对于其他的临界资源,比如说多台打印机等,这种方式 ...

  3. 【C++ 语言】Visual Studio 配置 POSIX 线程 ( Windows 不支持 POSIX | 配置文件下载 | 库文件说明 | 配置过程 )

    文章目录 POSIX 线程局限性 POSIX 线程配置文件下载及目录说明 Windows 动态库与静态库 POSIX 线程配置 ( 项目配置 ) POSIX 线程配置 ( Windows 动态库拷贝 ...

  4. linux下线程池实现

    linux下线程池实现 转自:http://blog.csdn.net/lmh12506/article/details/7753952 前段时间在github上开了个库,准备实现自己的线程池的,因为 ...

  5. 【二十四】springboot使用EasyExcel和线程池实现多线程导入Excel数据

      springboot篇章整体栏目:  [一]springboot整合swagger(超详细 [二]springboot整合swagger(自定义)(超详细) [三]springboot整合toke ...

  6. linux线程池实现多线程并发,基于Linux的多线程池并发Web服务器设计-电子设计工程.PDF...

    基于Linux的多线程池并发Web服务器设计-电子设计工程.PDF 第 卷 第 期 电子设计工程 年 月 基于 的多线程池并发 服务器设计 陈 涛 任海兰 武汉邮电科学研究院 湖北 武汉 摘要 时至今 ...

  7. linux动态线程池--原理,这儿的代码不完整

    本文给出了一个通用的线程池框架,该框架将与线程执行相关的任务进行了高层次的抽象,使之与具体的执行任务无关.另外该线程池具有动态伸缩性,它能根据执行任务的轻重自动调整线程池中线程的数量.文章的最后,我们 ...

  8. 谈一谈linux下线程池

    什么是线程池: 首先,顾名思义,就是把一堆开辟好的线程放在一个池子里统一管理,就是一个线程池. 其次,为什么要用线程池,难道来一个请求给它申请一个线程,请求处理完了释放线程不行么?也行,但是如果创建线 ...

  9. java线程池_Java多线程并发:线程基本方法+线程池原理+阻塞队列原理技术分享...

    线程基本方法有哪些? 线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等. 线程等待(wait) 调用该方法的线程进入 WAITING 状态,只有等 ...

最新文章

  1. java 字符串xml,解析java中的xml字符串?
  2. WebRTC的现状和未来:专访W3C WebRTC Chair Bernard Aboba(上)
  3. IIS 错误代码分析手册
  4. python中自带的模块_python中的模块详解
  5. apicloud项目怎么运行_Spring Boot教程(3) – 运行第一个项目
  6. vue indev.html,webpack - Can't add script tag to Vue component files ( *.vue ) - Stack Overflow
  7. 小扎展示Facebook十年产品路线图,跳票的智能音箱又有新爆料
  8. iptables常用配置规则
  9. WIN7下的ORACLE精简版客户端(ORACLE Instant Client)安装与配置指南
  10. 点击按钮传递参数并调用ajax,jQuery 单击使用 jQuery 的按钮并在 ajax 成功后将数据附加到响应...
  11. 【Spring-IOC】bean扫描器ClassPathBeanDefinitionScanner详解
  12. 【log4cpp_学习】2_log4cpp配置文件的使用
  13. 竖排书A5双面打印设置指南
  14. 【HANA系列】SAP 一位SAP培训顾问的建议:SAP HANA应该如何学习?
  15. 16年9月第二周---deadline
  16. wingide python_WingIDE下载|Wingware Python WingIDE汉化中文版6.0 下载_当游网
  17. 使用async.whilst要注意的地方
  18. STM32F1主从定时器设置
  19. 16083001(古墓丽影GPA)
  20. 永恒之蓝 MS17-010漏洞复现

热门文章

  1. 扩展剂:模式还是反模式?
  2. Java并发教程–可调用,将来
  3. IntelliJ IDEA for Mac在MacOS模式下的编译和运行快捷键(Compile and Run Shortcut)
  4. javadoc - Java API 文档生成器(Windows版本)
  5. iphone11什么时候上市_hd3手表高仿哪里买 什么时候上市?
  6. 铺铜过孔不要十字_谈谈商周青铜器上圈足的镂孔现象
  7. 二级java题型及分值_2016年全国计算机二级java语言程序设计考试大纲
  8. git.exe 启动 慢_四川成都surface电脑启动到一半黑屏维修服务地址电话
  9. python对列表中的数值进行统计运算_python-从单词列表中计算元音并返回数字作......
  10. linux下gate版本管理,Linux安装使用GoldenGate