linux posix 线程池_linux多线程--POSIX Threads Programming

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

Thread基本知识

Process 地址空间

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

9459a114cdbb7aa2a07279b75ef212e9.png

根据访问权限,进程地址空间分为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的内容,只维护必需的一小部分作为私有内容。

aa756c7da726085695cd472ab74bbb3d.png

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特定物理内存到进程空间内或者其他。

线程间通信

f888b79842bfef872ed49cb9b5a9b3a2.png

所有的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释放。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/347880.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机相关货品类别,工业计算机的种类以及产品供应链

(文章来源&#xff1a;OFweek)因为工业计算机几乎是针对某种特定需求而生&#xff0c;它的型态可说是五花八门&#xff0c;如果要简单做个分类&#xff0c;可以工业计算机的类型与层级来区分。大致上有板卡、子系统与系统整合解决方案三种。板卡&#xff1a;又有单板计算机(Sin…

Java基本语法(6)--算术运算符

除号&#xff08;/&#xff09;要注意的是除数与被除数的数据类型。如果都是整型&#xff0c;那么结果还是一个整型&#xff0c;因此结果相当于是取整&#xff1b;如果至少有一浮点型&#xff0c;那么结果是浮点型&#xff0c;也就可以除到完整彻底&#xff0c;带小数点的。 前…

soapui 证书_SoapUI入门之附件上传和配置Https请求

已经欠了一堆的作业了&#xff0c;吼吼~拖延症是个大bug&#xff0c;废话不多说&#xff0c;马上进入咱们SoapUI的学习中来吧~现在开始我们的SoapUI集成测试学习之道&#xff1a;一、如何使用SoapUI进行附件上传做人还是不能这样&#xff0c;这不欺负银嘛。首先&#xff0c;我们…

pptv手机端html,影视资源持续更新,PPTV手机化身看片神器

移动终端的快速发展为人们的生活带来了多元化的娱乐享受&#xff0c;用手机观赏电影逐渐成为人们放松身心的重要选择之一。虽然在手机品牌呈井喷发展的态势下&#xff0c;支持观影的智能手机不在少数&#xff0c;要想突出重围&#xff0c;仅仅在硬件配置上做文章是远远不够的&a…

Java基本语法(8)--比较运算符

比较运算符的结果都是boolean型&#xff0c;也就是要么是true&#xff0c;要么是false。 注意 和 之分。

win7计算机管理找不到文件夹,Win7系统打开组策略提示找不到文件gpedit.msc怎么办...

组策略是Windows系统管理员为用户和计算机定义并控制程序、网络资源及操作系统行为的主要工具&#xff0c;不过有win7旗舰版系统用户在打开组策略的时候&#xff0c;却提示“找不到文件gpedit.msc&#xff0c;请确定文件名是否正确后&#xff0c;再试一次”&#xff0c;该如何解…

mysql 授权用户_MySQL创建用户与授权

一. 创建用户命令:CREATE USER usernamehost IDENTIFIED BY password;说明&#xff1a;username&#xff1a;你将创建的用户名host&#xff1a;指定该用户在哪个主机上可以登陆&#xff0c;如果是本地用户可用localhost&#xff0c;如果想让该用户可以从任意远程主机登陆&#…

Java基本语法(9)--逻辑运算符(逻辑短路)与或非

逻辑与——&——都为true才为true&#xff0c;有一false就为false&#xff0c;符号两边都看 短路与——&&——都为true才为true&#xff0c;有一false就为false&#xff0c;前面有false就略后面 逻辑或——|——有一true就为true&#xff0c;符合两边都看 短路或—…

计算机管理设置,win10系统打开计算机管理的设置步骤

win10系统使用久了&#xff0c;好多网友反馈说关于对win10系统打开计算机管理设置的方法&#xff0c;在使用win10系统的过程中经常不知道如何去对win10系统打开计算机管理进行设置&#xff0c;有什么好的办法去设置win10系统打开计算机管理呢&#xff1f;在这里小编教你只需要 …

Java基本语法(10)--位运算符

位运算符的使用对象是数&#xff0c;位运算是直接对整数的二进制进行的运算&#xff0c;理解必须要在二进制层面进行。 功能说明&#xff1a; 每<<左移1位&#xff0c;乘一次2&#xff08;低位补0&#xff09; 每>>右移一位&#xff0c;除一次2&#xff0c;符号位…

jsp servlet示例_Servlet和JSP中的文件上传示例

jsp servlet示例使用Servlet和JSP将文件上传到服务器是Java Web应用程序中的常见任务。 在对Servlet或JSP进行编码以处理文件上传请求之前&#xff0c;您需要了解一点有关HTML和HTTP协议中文件上传支持的知识。 如果要让用户从文件系统中选择文件并上传到服务器&#xff0c;则需…

功能Java示例 第3部分–不要使用异常来控制流程

这是称为“ Functional Java by Example”的系列文章的第3部分。 我在本系列的每个部分中发展的示例是某种“提要处理程序”&#xff0c;用于处理文档。 在前面的部分中&#xff0c;我从一些原始代码开始&#xff0c;并应用了一些重构来描述“什么”而不是“如何”。 为了帮助…

安全模式打开计算机策略,安全模式下怎么解除组策略的锁定?

2006-01-13注册表被锁住了&#xff0c;组策略也被禁用&#xff0c;安全模式也进不了&#xff0c;该如何修改注册表随便从网络上下在一个注册表编辑器,展开[hkey-current-user\software\microsoft\windows\current version\policies\system]主键,将键名 disableregistrytools 的…

Java基本语法(12)--分支结构if-else

基本格式结构 if (条件语句){条件语句为true时&#xff0c;进入执行的语句&#xff1b; }else{条件语句为false时&#xff0c;执行的语句&#xff1b; }if (条件语句1){条件语句1为true时&#xff0c;进入执行的语句&#xff1b; }else if(条件语句2){条件语句1为false&#x…

服务器硬盘 主板,服务器主板和普通主板有什么区别?

什么是工控服务器?首先我们来看专业上服务器是怎样定义的&#xff1a;工控服务器是一种高性能计算机&#xff0c;作为网络的节点&#xff0c;存储、处理网络上80%的数据、信息&#xff0c;因此也被称为网络的灵魂。也可以这样讲&#xff0c;工控服务器是指一个管理资源并为用户…

键盘输入Scanner类方法属性使用

基本步骤 1.导包&#xff1a;import java.util.Scanner 2.Scanner实例化&#xff0c;创建Scanner对象&#xff1a; Scanner scan new Scanner(System.in) 3.调用Scanner类相关方法&#xff08;next&#xff08;&#xff09;/nextXxx()&#xff09;&#xff0c;来获取指定数据类…

传奇霸业维护服务器,37传奇霸业8月18日部分区服维护计划

亲爱的玩家:您好&#xff0c;为保证服务器稳定运营&#xff0c;优化游戏体验&#xff0c;《传奇霸业》新版本“炼狱魔域”将代替旧版本“超霸传奇”继续向各大玩家提供游戏服务&#xff0c; 我们将在2016年8月18日对以下区服进行停服维护更新&#xff0c;具体维护时间将视情况提…

2018年机器学习趋势与Apache Kafka生态系统相结合

在慕尼黑举行的OOP 2018大会上&#xff0c;我介绍了有关使用Apache Kafka生态系统和诸如TensorFlow&#xff0c;DeepLearning4J或H2O之类的深度学习框架构建可扩展&#xff0c;关键任务微服务的演讲的更新版本。 我想分享更新后的幻灯片&#xff0c;并讨论一些有关最新趋势的更…

Java基本语法(13)--条件分支switch-case结构

基本格式结构&#xff1a; switch (表达式/变量) {case 常量1&#xff1a;执行语句1; //情况1case 常量2&#xff1a;执行语句2; //情况2... ... ...default: 以上情况都不满足时执行语句; }说明&#xff1a; switch结构中的表达式/变量只能是如下6种数据类型之一&#xff…

小米岭南通服务器维护,小米岭南通交通联合卡内测开启

原标题&#xff1a;小米岭南通交通联合卡内测开启IT之家12月21日消息 小米岭南通交通联合卡内测开启招募报名&#xff0c;持小米8(含探索版、屏幕指纹版)、小米MIX 系列(不含MIX 3)、小米Note 3/2、小米6/5s/5s Plus手机的用户可开通尝鲜。支持系统小米8和MIX 2S最低支持8.12.2…