Linux多线程详解
线程也被称为轻权进程(lightweight process)。
在传统的UNIX上,一个进程让另一个实体做某个事务是用fork派生子进程的方法处理的。派生子进程的代价比线程要昂贵得多,尤其是在父子进程之间、子 进程之间传递信息需要用IPC或其他方法通信。相对比,使用线程有许多优点,如创建线程要比创建进程快的多、一个进程中的线程共享相同的全局存储区等等。
Linux系统下的多线程遵循POSIX线程接口,称为pthread,在linux中,多线程需要使用的头文件为<pthread.h>,连接时需要使用库为libpthread.a。
我们编写一个非常简单的例子:
- //example_1.c
- #include <stdio.h>
- #include <pthread.h>
- void * pthread_func_test(void * arg);
- int main(){
- pthread_t pt1,pt2;
- pthread_create(&pt1,NULL,pthread_func_test,"This is the Thread_ONE");
- pthread_create(&pt2,NULL,pthread_func_test,"This is the Thread_TWO");
- sleep(1); //不加上这句,看不到结果。
- }
- void * pthread_func_test(void * arg){
- printf("%s /n ",arg);
- }
编译源文件:
- gcc example_1.c -o example -lpthread
编译环境:
平 台:FC6
版 本:2.6.18-1.2798.fc6
编译器:gcc 4.1.1 20070105 (Red Hat 4.1.1-51)
运行可执行文件:
- ./example
在终端上的输出结果:
- This is the Thread_ONE
- This is the Thread_TWO
在example_1这个例子中,一共产生了三个线程,第一个就是main所代表的主线程,另外两个就是pt1和pt2分别代表的两个分支线程,这两个线程由pthread_create函数创建,执行的内容就是写在函数pthread_func_test里的东东。
上例涉及到的函数是:pthread_create()
函数原型如下:
- int pthread_create(pthread_t *restrict thread,
- const pthread_attr_t *restrict attr,
- void *(*start_routine)(void*), void *restrict arg);
参数点解:
1、每个线程都有自己的ID即thread ID,可以简称tid,呵呵,是不是想起什么来了?。。。对,和pid有点象。其类型为pthread_t,pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义:
- typedef unsigned long int pthread_t;
可以看成是线程的标志符。当成功创建一个新线程的时候,系统会为该线程分配一个tid,并将该值通过指针返回给调用它的程序。
2、attr申明线程的属性。
属性结构为pthread_attr_t,它在头文件/usr/include/pthread.h中定义。设为NULL,表示在这里我们只使用线程的默认属性就可以了。
3、start_routine表示新创建的线程所要执行的例程。线程以调用该函数开始,直到由该 函数返回(return)终止这个线程,或者在start_routine所指向的函数中调用pthread_exit函数终止。 start_routine只有一个参数,该参数由随后的arg指针来指出。
4、arg:也是一个指针,也就是start_routine指针所指向的函数的参数。
返回值:
当pthread_create调用成功时,该调用返回0;否则,返回一个错误代码指出错误的类型。
接下来,再看另两个重要的函数pthread_exit和pthread_join
函数原型如下:
- void pthread_exit( void * value_ptr );
线程的终止可以是调用了pthread_exit或者该线程的例程结束。也就是说,一个线程可以隐式的退出,也可以显式的调用pthread_exit函数来退出。
pthread_exit函数唯一的参数value_ptr是函数的返回代码,只要pthread_join中的第二个参数value_ptr不是NULL,这个值将被传递给value_ptr。
函数原型如下:
- int pthread_join( pthread_t thread, void * * value_ptr );
函数pthread_join的作用是,等待一个线程终止。
调用pthread_join的线程将被挂起直到参数thread所代表的线程终止时为止。pthread_join是一个线程阻塞函数,调用它的函数将一直等到被等待的线程结束为止。
如果value_ptr不为NULL,那么线程thread的返回值存储在该指针指向的位置。该返回值可以是由pthread_exit给出的值,或者该线程被取消而返回PTHREAD_CANCELED。
当一个非分离的线程终止后,该线程的内存资源(线程描述符和栈)并不会被释放,直到有线程对它使 用了pthread_join时才被释放。因此,必须对每个创建为非分离的线程调用一次pthread_join调用,以避免内存泄漏。否则当线程是可分 离的,调用pthread_exit,将终止该调用线程,并释放所有资源,没有线程等待它终止。
至多只能有一个线程等待给定的线程终止。如果已经有一个线程在等待thread线程终止了,那么再次调用pthread_join等待同一线程的线程将返回一个错误。
- //example_2.c
- #include <stdio.h>
- #include <pthread.h>
- void * pthread_func_test(void * arg);
- int main()
- {
- pthread_t pt1,pt2;
- pthread_create(&pt1,NULL,pthread_func_test,"This is the Thread_ONE");
- pthread_create(&pt2,NULL,pthread_func_test,"This is the Thread_TWO");
- pthread_join(pt1,NULL);
- pthread_join(pt2,NULL); //这行不写,会发生什么?或写成pthread_join(pt1,NULL);又会怎么样?
- }
- void * pthread_func_test(void * arg)
- {
- printf("%s ",arg);
- pthread_exit(NULL); //显式声明
- }
到此为止,我们就学习了三个重要的函数--create、exit、join,接下来讲解多线程编程中的线程互斥问题。
线程互斥
互斥操作,就是对某段代码或某个变量修改的时候只能有一个线程在执行这段代码,其他线程不能同时进入这段代码或同时修改该变量。这个代码或变量称为临界资源。
例如:有两个线程A和B,临界资源为X,首先线程A进入,将X置为加锁状态,在A将锁打开之前的这段时间里,如果此 时恰巧线程B也欲获得X,但它发现X处于加锁状态,说明有其它线程正在执行互斥部分,于是,线程B将自身阻塞。。。线程A处理完毕,在退出前,将X解锁, 并将其它线程唤醒,于是线程B开始对X进行加锁操作了。通过这种方式,实现了两个不同线程的交替操作。
记住:一个互斥体永远不可能同时属于两个线程。或者处于锁定状态;或者空闲中,不属于任何一个线程。
代码如下:
- //example_3.c
- #include <stdio.h>
- #include <pthread.h>
- void * pthread_func_test(void * arg);
- pthread_mutex_t mu;
- int main()
- {
- int i;
- pthread_t pt;
- pthread_mutex_init(&mu,NULL); //声明mu使用默认属性,此行可以不写
- pthread_create(&pt,NULL,pthread_func_test,NULL);
- for(i = 0; i < 3; i++)
- {
- pthread_mutex_lock(&mu);
- printf("主线程ID是:%lu ",pthread_self()); //pthread_self函数作用:获得当前线程的id
- pthread_mutex_unlock(&mu);
- sleep(1);
- }
- }
- void * pthread_func_test(void * arg)
- {
- int j;
- for(j = 0; j < 3; j++)
- {
- pthread_mutex_lock(&mu);
- printf("新线程ID是:%lu ",pthread_self());
- pthread_mutex_unlock(&mu);
- sleep(1);
- }
- }
终端输出结果:
- 主线程ID是 : 3086493376
- 新线程ID是 : 3086490512
- 主线程ID是 : 3086493376
- 新线程ID是 : 3086490512
- 主线程ID是 : 3086493376
- 新线程ID是 : 3086490512
注:在你机器上运行的结果很可能与这里显示的不一样。
pthread_mutex_lock声明开始用互斥锁上锁,此后的代码直至调用 pthread_mutex_unlock为止,都处于加锁状态中,即同一时间只能被一个线程调用执行。当另一个线程执行到 pthread_mutex_lock处时,如果该锁此时被其它线程使用,那么该线程被阻塞,即程序将等待到其它线程释放此互斥锁。
上述例子中,涉及到了几个函数:pthread_mutex_init/pthread_mutex_lock/pthread_mutex_unlock/pthread_mutex_destroy/pthread_self
函数原型:
- int pthread_mutex_init(pthread_mutex_t *restrict mutex,
- const pthread_mutexattr_t *restrict attr);
函数作用:
初始化互斥体类型变量mutex,变量的属性由attr进行指定。attr设为NULL,即采用默认属性,这种方式与pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER的方式等价。
函数原型:
- int pthread_mutex_lock(pthread_mutex_t *mutex);
函数作用:
用来锁住互斥体变量。如果参数mutex所指的互斥体已经被锁住了,那么发出调用的线程将被阻塞直到其他线程对mutex解锁为止。
函数原型:
- int pthread_mutex_unlock(pthread_mutex_t *mutex);
函数作用:
如果当前的线程拥有参数mutex所指定的互斥体,那么该函数调用将该互斥体解锁。
函数原型:
- int pthread_mutex_destroy(pthread_mutex_t *mutex);
函数作用:
用来释放互斥体所占用的资源。
函数原型:
- pthread_t pthread_self(void);
函数作用:获得线程自身的ID。前面我们已经提到过,pthread_t的类型为unsigned long int,所以在打印的时候要使用%lu方式,否则将产生奇怪的结果。
但是上面的代码并不完善,假设将循环次数修改得足够的长,打印后的结果可能并不是我们所希望看到的交替打印,可能象下面这样:
- 主线程ID是 : 3086493376
- 新线程ID是 : 3086490512
- 主线程ID是 : 3086493376
- 新线程ID是 : 3086490512
- 新线程ID是 : 3086490512
- 主线程ID是 : 3086493376
这是什么原因呢?因为Linux是分时操作系统,采用的是时间片轮转的方式,主线程和新线程可能因为其它因素的干扰,获得了非顺序的时间片。如果想要严格的做到“交替”方式,可以略施小计,即加入一个标志。
完整程序如下:
- //example_4.c
- #include <stdio.h>
- #include <pthread.h>
- void * pthread_func_test(void * arg);
- pthread_mutex_t mu;
- int flag = 0;
- int main()
- {
- int i;
- pthread_t pt;
- pthread_mutex_init(&mu,NULL);
- pthread_create(&pt,NULL,pthread_func_test,NULL);
- for(i = 0; i < 3; i++)
- {
- pthread_mutex_lock(&mu);
- if(flag == 0)
- printf("主线程ID是:%lu ",pthread_self());
- flag = 1;
- pthread_mutex_unlock(&mu);
- sleep(1);
- }
- pthread_join(pt, NULL);
- pthread_mutex_destroy(&mu);
- }
- void * pthread_func_test(void * arg)
- {
- int j;
- for(j = 0; j < 3; j++)
- {
- pthread_mutex_lock(&mu);
- if(flag == 1)
- printf("新线程ID是:%lu ",pthread_self());
- flag == 0;
- pthread_mutex_unlock(&mu);
- sleep(1);
- }
- }
在使用互斥锁的过程中很有可能会出现死锁:即两个线程试图同时占用两个资源,并按不同的次序锁定相应的互斥锁,例如 两个线程都需要锁定互斥锁1和互斥锁2,A线程先锁定互斥锁1,B线程先锁定互斥锁2,这时就出现了死锁。此时我们可以使用函数 pthread_mutex_trylock,该函数企图锁住一个互斥体,但不阻塞。
函数原型:
- int pthread_mutex_trylock(pthread_mutex_t *mutex);
函数pthread_mutex_trylock()用来锁住参数mutex所指定的互斥体。如果参数mutex所 指的互斥体已经被上锁,该调用不会阻塞等待互斥体的解锁,而会返回一个错误代码。通过对返回代码的判断,程序员就可以针对死锁做出相应的处理。所以在对多 个互斥体编程中,尤其要注意这一点。
经过以上的讲解,我们就学习了Linux下关于多线程方面对互斥体变量的操作。接下来将讲解有关线程同步方面的知识点。
线程同步
首先来看一下有关同步机制的概念。同步就是若干个线程等待某个事件的发生,当等待事件发生时,一起开始继续执行。可以这样简单理解同步,就是若干个线程各自对自己的数据进行处理,然后在某个点必须汇总一下数据,否则不能进行下一步的处理工作。
线程同步的函数调用有pthread_cond_init、pthread_cond_broadcast、pthread_cond_signal、pthread_cond_wait和pthread_cond_destroy
函数原型:
- int pthread_cond_init(pthread_cond_t *restrict cond,
- const pthread_condattr_t *restrict attr);
函数说明:
按attr指定的属性初始化cond条件变量。如果attr为NULL,效果等同于pthread_cond_t cond = PTHREAD_COND_INITIALIZER
函数原型:
- int pthread_cond_broadcast(pthread_cond_t *cond);
函数说明:
对所有等待cond这个条件变量的线程解除阻塞。
函数原型:
- int pthread_cond_signal(pthread_cond_t *cond);
函数说明:
仅仅解除等待cond这个条件变量的某一个线程的阻塞状态。如果有若干线程挂起等待该条件变量,该调用只唤起一个线程,被唤起的线程是哪一个是不确定的。
函数原型:
- int pthread_cond_wait(pthread_cond_t *restrict cond,
- pthread_mutex_t *restrict mutex);
函数说明:
该调用自动阻塞发出调用的当前线程,并等待由参数cond指 定的条件变量,而且为参数mutex指定的互斥体解锁。被阻塞线程直到有其他线程调用pthread_cond_signal或 pthread_cond_broadcast函数置相应的条件变量时,而且获得mutex互斥体才解除阻塞。等待状态下的线程不占用CPU时间。
函数原型:
- int pthread_cond_timedwait(pthread_cond_t *restrict cond,
- pthread_mutex_t *restrict mutex,
- const struct timespec *restrict abstime);
函数说明:
该函数自动阻塞当前线程等待参数cond指定的条件变量,并 为参数mutex指定的互斥体解锁。被阻塞的线程被唤起继续执行的条件是:有其他线程对条件变量cond调用pthread_cond_signal函 数;或有其他线程对条件变量cond调用pthread_cond_broadcast;或系统时间到达abstime参数指定的时间;除了前面三个条件 中要有一个被满足外,还要求该线程获得参数mutex指定的互斥体。
函数原型:
- int pthread_cond_destroy(pthread_cond_t *cond);
函数说明:
释放cond条件变量占用的资源。
看下面的示例:
- //example_5.c
- #include <stdio.h>
- #include <pthread.h>
- pthread_t pt1,pt2;
- pthread_mutex_t mu;
- pthread_cond_t cond;
- int i = 1;
- void * decrease(void * arg)
- {
- while(1)
- {
- pthread_mutex_lock(&mu);
- if(++i)
- {
- printf("%d ",i);
- if(i != 1) printf("Error ");
- pthread_cond_broadcast(&cond);
- pthread_cond_wait(&cond,&mu);
- }
- sleep(1);
- pthread_mutex_unlock(&mu);
- }
- }
- void * increase(void * arg)
- {
- while(1)
- {
- pthread_mutex_lock(&mu);
- if(i--)
- {
- printf("%d ",i);
- if(i != 0) printf("Error ");
- pthread_cond_broadcast(&cond);
- pthread_cond_wait(&cond,&mu);
- }
- sleep(1);
- pthread_mutex_unlock(&mu);
- }
- }
- int main()
- {
- pthread_create(&pt2,NULL,increase,NULL);
- pthread_create(&pt1,NULL,decrease,NULL);
- pthread_join(pt1,NULL);
- pthread_join(pt2,NULL);
- }
以上我们讲解过了Linux下利用pthread.h头文件的多线程编程知识。
==========================
原创作者:Frozen_socker(冰棍)
E_mail:dlskyfly@163.com
Linux多线程详解相关推荐
- 『Linux』第九讲:Linux多线程详解(三)_ 线程互斥 | 线程同步
「前言」文章是关于Linux多线程方面的知识,上一篇是 Linux多线程详解(二),今天这篇是 Linux多线程详解(三),内容大致是线程互斥与线程同步,讲解下面开始! 「归属专栏」Linux系统编程 ...
- 【Linux系统】Linux多线程详解
Linux多线程 1 前置知识 1.1 进程的概念 1.2 线程的概念 1.3 进程地址空间 1.4 由虚拟地址到物理地址的页表映射(二级页表) 1.4.1 一级页表的缺点 1.4.2 二级页表 1. ...
- linux中date使用方法,linux命令详解date使用方法(计算母亲节和父亲节日期脚本示例)...
linux命令详解date使用方法(计算母亲节和父亲节日期脚本示例) 发布于 2016-02-07 15:58:40 | 108 次阅读 | 评论: 0 | 来源: 网友投递 LinuxLinux是一 ...
- 《嵌入式Linux软硬件开发详解——基于S5PV210处理器》——1.2 S5PV210处理器
本节书摘来自异步社区<嵌入式Linux软硬件开发详解--基于S5PV210处理器>一书中的第1章,第1.2节,作者 刘龙,更多章节内容可以访问云栖社区"异步社区"公众号 ...
- Java 多线程详解(二)------如何创建进程和线程
Java 多线程详解(一)------概念的引入:https://blog.csdn.net/weixin_39816740/article/details/80089790 在上一篇博客中,我们已经 ...
- Linux进程详解 【Linux由基础到进阶】
Linux进程详解 进程的概念: 虚拟处理器: 虚拟内存: 进程的产生 进程的管理 进程描述符 分配进程描述符 进程描述符的存放 进程的状态 设置进程状态 进程上下文 系统调用与库函数的区别 进程家族 ...
- iOS多线程详解:实践篇
iOS多线程实践中,常用的就是子线程执行耗时操作,然后回到主线程刷新UI.在iOS中每个进程启动后都会建立一个主线程(UI线程),这个线程是其他线程的父线程.由于在iOS中除了主线程,其他子线程是独立 ...
- 《Linux命令详解手册》——Linux畅销书作家又一力作
关注IT,更要关心IT人,让系统管理员以及程序员工作得更加轻松和快乐.鉴于此, 图灵公司引进了国外知名出版社John Wiley and Sons出版的Fedora Linux Toolbox: 10 ...
- Linux系统详解 系统的启动、登录、注销与开关机
Linux系统详解 第六篇:系统的启动.登录.注销与开关机 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://johncai.blo ...
最新文章
- 新手搭建阿里云FTP服务器
- linux 对象管理器,Linux多安全策略和动态安全策略框架模块详细分析之函数实现机制中文件对象管理器分析(3)...
- shrinkwrap_Java EE 6测试第二部分– Arquillian和ShrinkWrap简介
- 360手机浏览器升级至chrome62 成内核版本最高的手机浏览器
- cacti批量添加脚本
- 谷歌 Chrome 同步功能可滥用于 C2 通信及数据提取
- Xen Server 7.0 一直无法退出维护模式
- Party (Standard IO)
- python接口测试覆盖率统计_pytest文档57-计算单元测试代码覆盖率(pytest-cov)
- 原来 Python 还有这些实用的功能和特点!
- 钉钉云课堂sign计算方式
- HTML调用Discuz系统变量,Discuz论坛js调用详解
- 计算机主机mac地址怎么查,怎么查看电脑的Mac地址
- 股票交易成本有哪些费用?
- react 打包体积过大_create-react-app andt 打包的 js 文件过大
- 有趣的程序代码c语言,一个有趣的小程序
- Windows 系统重装 - Mac 制作 Win10 启动盘
- 无法运行rc.exe(已解决)
- layui 输入框添加自定义图标
- 【php】注册系统和使用Xajax即时验证用户名是否被占用
热门文章
- 股票的科创板,新三板,创业板到底哪个能让你赚钱
- 星巴克饮品中竟喝出活蟑螂?官方回应了...
- 美国对特斯拉“幽灵刹车”问题展开调查 涉及41.6万辆Model 3/Y
- 应届生怒怼管理层后续:已离职、被标记永不录用?腾讯张军回应...
- 与饿了么三年“独家合作”即将到期 星巴克正与顺丰、美团等商谈配送合作
- 红魔游戏手机6S Pro星耀白版开启预约:4399元起10月15日正式首销
- 微信支付推出“中秋花灯会”新玩法 点亮花灯享大额提现免费券
- 何小鹏谈“小米造车”:我们要为勇敢者鼓掌
- 中国移动订330万台Redmi K40系列 网友:怪不得抢不到
- 荣耀法定代表人由饶俊祥变更为万飚 注册资本增长2973%