【Linux】多线程入门详解
背景知识:
1.每次进程切换,都存在资源的保持和恢复动作,即上下文切换
2.进程的引入虽然可以解决多用户的问题,但是进程频繁切换的开销会严重影响系统性能
3.同一个进程内部有多个线程,这些线程共享的是同一个进程的所有资源
4.通过线程可以支持一份应用程序内部的并发,免去了进程频繁切换的开销
5.线程的切换是轻量级的,所以可以保证足够快
6.即使是单核计算机,也可以通过不停的在多个线程的指令间切换,从而造成多线程同时运行的效果
7.操作系统一般都有一些系统调用来让一个函数运行成为一个新的线程
8.对于多线程来说,由于同一个进程空间中存在多个栈,任何一个空白区域填满都会导致栈溢出
9.多线程与栈密切相关
一.线程创建与结束
相关函数:
1)int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void*(*start_routine)(void*),void *arg)
线程创建函数
参数1:*thread,需要创建的线程ID指针
参数2:*attr,用来设置线程属性
参数3:void*,线程运行函数的起始地址,页就是告诉线程你的线程运行函数是哪一个
参数4:*arg,线程运行函数的参数
函数的返回值int是函数是否运行成功的结果,当返回值为0表示运行成功,-1表示运行失败
当线程运行函数的参数不止一个时,需要将这些参数封装成一个结构体传进去
2)int pthread_join(pthread_t thread,void **retval)
调用线程等待thread线程运行结束,并且获得thread线程的返回值
参数1:thread,被等待线程的线程ID
参数2:用来存储thread线程的返回值
该函数一般是主线程调用,用来等待子线程运行完毕,函数的返回值int是函数是否运行成功的结果,当返回值为0表示运行成功,-1表示运行失败
3)void pthread_exit(void *retval)
结束当前线程,并返回一个返回值
参数1:*retval,线程结束的返回值
一般pthread_exit和pthread_join配套使用,获得子线程的返回值
样例程序:
#include <iostream>
#include<pthread.h>
using namespace std; void* say_hello(void* args)
{
cout<<"hello from thread"<<endl;
pthread_exit((void*));
} int main()
{
pthread_t tid;
int iRet=pthread_create(&tid,NULL,say_hello,NULL);
if(iRet)
{
cout<<"pthread_create error:iRet="<<iRet<<endl;
return iRet;
}
void *retval;
iRet=pthread_join(tid,&retval);
if(iRet)
{
cout<<"pthread_join error:iRet="<<iRet<<endl;
return iRet;
}
cout<<(long)retval<<endl;
return ;
}
/*
hello from thread
666
*/
先创建并运行一个子线程,在主线程中等待子线程运行结束,并且获取子线程的返回值然后输出
ps:调用pthread_join函数,获取线程的返回值!
二.向线程传递参数
在创建线程的时候可以向线程传递参数,pthread_create函数的第四个参数即为线程运行函数的参数,当要传递的参数有多个时,需要将这些参数封装起来然后传递
#include <iostream>
#include<pthread.h>
using namespace std; void* say_hello(void* args)
{
int x=*(int*)args;
cout<<"hello from thread,x="<<x<<endl;
pthread_exit((void*));
} int main()
{
pthread_t tid;
int para=;
int iRet=pthread_create(&tid,NULL,say_hello,¶);
if(iRet)
{
cout<<"pthread_create error:iRet="<<iRet<<endl;
return iRet;
}
void *retval;
iRet=pthread_join(tid,&retval);
if(iRet)
{
cout<<"pthread_join error:iRet="<<iRet<<endl;
return iRet;
}
cout<<(long)retval<<endl;
return ;
} /*
hello from thread,x=123
666
*/
三.获取线程的ID
1)调用pthread_self函数来获取当前运行线程的id,该函数的返回值是当前运行线程的id
2)在创建线程时直接获取创建的线程的id
四.线程的属性
typedef struct
{
int etachstate; //线程的分离状态
int schedpolicy; //线程调度策略
structsched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set; //线程的栈设置
void* stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
}pthread_attr_t;
1)线程的分离状态:线程的分离状态决定一个线程以什么样的方式的来终止自己
1.非分离状态:线程的默认属性是非分离状态,这种情况下,父线程等待子线程结束,只有当pthread_join函数返回时,子线程才算终止,才能释放自己占用的系统资源
2.分离状态:分离线程没有被其他线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源,可以根据自己的需要,选择适当的分离状态
3.怎么使得线程分离?
方法1:直接将线程设置为分离线程
pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
第二个参数可选为:PTHREAD_CREATE_DETACHED(分离线程)和PTHREAD_CREATE_JOINABLE(非分离线程)
这里需要注意一点的是,如果设置一个线程为分离状态,而这个线程又运行得非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和资源交给其他线程使用,这样调用pthread_create就得到了错误的线程号,要避免这种情况可以采用一定的同步措施,比如在被创建的线程的运行函数中调用pthread_cond_timewait函数,使得线程睡眠几秒,留出足够的时间让pthread_create返回,设置一段等待时间,这是多线程编程中常见的方法,但是注意不要使用诸如wait的函数,他们是使得整个进程睡眠,并不能解决线程同步问题!
方法2:在需要分离的线程的运行函数中调用pthread_detached函数
int pthread_detach(pthread_t tid);若成功则返回0,若出错则为非零。
pthread_detach用于分离可结合线程tid。线程能够通过以pthread_self()为参数的pthread_detach调用来分离它们自己。
即:pthread_detach(pthread_self())
2)线程的栈地址
当进程栈地址空间不够时,,指定新建线程使用malloc分配的空间作为自己的栈空间,通过pthread_attr_setstackaddr和pthread_attr_getstackaddr两个函数分别设置和获取线程的栈地址,传给pthread_addr_setstackaddr函数的地址是缓冲区的低地址(不一定是栈的开始地址,栈可能从高地址往低地址增长)
3)线程的栈大小
1.当系统中有很多线程时,可能需要减小每个线程的栈空间默认大小,防止进程的地址空间不够用
2.当线程调用的函数会分配很大的局部变量或者函数调用层次很深时,可能需要增加线程默认栈的大小
3.函数pthread_attr_getstacksize和 pthread_attr_setstacksize提供设置。
4)线程的栈保护区大小
1.在线程栈的栈顶预留一段空间,防止栈溢出
2.当栈指针进入这段保护区时,系统会发出错误,通常是发送信号给线程
3.该属性的默认值是PAGESIZE大小,该属性被设置时,系统会自动将该属性大小补齐为页大小的整数倍
4.当栈改变地址属性时,栈保护区大小通常清零
5)线程的优先级
1.新线程不继承父进程的优先级,新线程的优先级默认为0
2.新线程使用SCHED_OTHER调度策略,即:线程一旦开始运行,直到被抢占或者直到线程阻塞或停止为止
6)线程的争用范围
1)PTHREAD_SCOPE_SYSTEM:此线程与系统中的所有线程进行竞争
2)PTHREAD_SCOPE_PROCESS:此线程与进程中的其他线程进行竞争
具有不停争用范围的线程可以在同一个线程甚至同一个进程中共存,进程范围只允许这种与同一进程中的其他线程争用资源,而系统范围则允许此类线程与系统中其他所有线程争用资源
7)线程的绑定状态
轻进程:可以理解为内核线程,位于用户层和系统层之间,系统对线程资源的分配,对线程的控制都是通过轻进程实现的,一个轻进程可以控制一个或多个线程
1.非绑定状态
默认情况下,启动多少个轻进程,哪些轻进程来控制哪些线程都是由系统来控制的,这种情况称之为非绑定
2.绑定状态
即某个线程绑定在一个轻进程之上,被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用,通过被绑定的轻进程的优先级和调度级可用使得绑定的线程满足诸如实时反应之类的要求
五.线程分离
1.将线程设置为结束状态分离后,线程的结束状态不能被进程中的其他线程得到,同时保存线程结束状态的存储区也将变得不能应用
样例1:
在一个线程被创建之前分离线程
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h> using namespace std;
void * thread_run(void * arg)
{
cout<<"the thread"<<endl;
return NULL;
} int main(void)
{
int flag;
pthread_t tid;
pthread_attr_t attr;//attrbute flag=pthread_attr_init(&attr);//thread init
if(flag)
{
cout<<"can‘t init attr "<<strerror(flag)<<endl;
return flag;
} flag=pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//setting thread detachment status
if(flag)
{
cout<<"can't set attr "<<strerror(flag)<<endl;
return flag;
} flag=pthread_create(&tid,&attr,thread_run,NULL);
if(flag)
{
cout<<"can't create thread "<<strerror(flag)<<endl;
return flag;
} flag=pthread_join(tid,NULL);
if(flag)
{
cout<<"can't join thread and thread has been detached"<<endl;
return flag;
}
return ;
}
//answer:can't join thread and thread has been detached
分析:由于子线程分离,因此得不到子线程的结束状态信息,pthread_join函数会出错,并且由于子线程分离,子线程运行函数中输出的字符对主线程而言也是不可见的,所以字符串”the thread“没有被打印出来!
样例二:分离一个已经创建的线程
#include <iostream>
#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h> using namespace std;
void * thread_run(void * arg)
{
cout<<"the son thread sleeping for 5 seconds"<<endl;
sleep();//sleeping 5 seconds and wait fotr the main thread to set the thread to a separate state
cout<<"the son thread done"<<endl;
return NULL;
} int main(void)
{
int flag;
pthread_t tid; flag=pthread_create(&tid,NULL,thread_run,NULL);
if(flag)
{
cout<<"can't create thread "<<strerror(flag)<<endl;
return flag;
} flag=pthread_detach(tid);//detach a thread
if(flag)
{
cout<<"can't detach thread "<<strerror(flag);
return flag;
} flag=pthread_join(tid,NULL);
if(flag)
{
cout<<"can't join thread and thread has been detached"<<endl;
} cout<<"the main thread sleeping for 8 seconds "<<endl;
sleep();
cout<<"the main thread done"<<endl; return ;
}
/*
answer:
can't join thread and thread has been detached
the main thread sleeping for 8 seconds
the son thread sleeping for 5 seconds
the son thread done
the main thread done
*/
分析:采用pthread_detach函数分离一个已经创建的线程,子线程睡眠5秒等待主线程将其分离!,这样导致pthread_join函数取不到子线程结束的状态信息
六.线程结束的四种方法
1.在线程运行函数中直接return(推荐)
在return之后,会清理函数内申请的对象,可以避免线程内存泄漏
2.调用pthread_exit(x)【返回x】,采用函数的形式则不会调用return函数,所以不会调用线程作用域内申请的对象的析构函数,会造成内存泄漏
3.使用同一进程中的其他线程终止线程(被动终止),pthread_cancel函数(使用不当会产生死锁,比如通知一个在等待队列中但是又被取消了的线程)
4.终止该线程所在的进程,调用exit()或者主线程return
【Linux】多线程入门详解的更多相关文章
- linux awk命令详解
linux awk命令详解 简介 awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大.简单来说awk就是把文件逐行的读入,以空格为默认分 ...
- Quartz 入门详解
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用.Quartz可以用来创建简单或为运行十个,百个, ...
- 转:JAVAWEB开发之权限管理(二)——shiro入门详解以及使用方法、shiro认证与shiro授权
原文地址:JAVAWEB开发之权限管理(二)——shiro入门详解以及使用方法.shiro认证与shiro授权 以下是部分内容,具体见原文. shiro介绍 什么是shiro shiro是Apache ...
- Quartz 入门详解 专题
Cron-Expressions are used to configure instances of CronTrigger. Cron-Expressions are strings that a ...
- [转帖]linux screen 命令详解,xshell关掉窗口或者断开连接,查看断开前执行的命令
linux screen 命令详解,xshell关掉窗口或者断开连接,查看断开前执行的命令 https://binwaer.com/post/12.html yun install -y screen ...
- linux awk命令详解,使用system来内嵌系统命令, awk合并两列
linux awk命令详解 简介 awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大.简单来说awk就是把文件逐行的读入,以空格为默认分 ...
- Linq之旅:Linq入门详解(Linq to Objects)
示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...
- SQL注入攻防入门详解
=============安全性篇目录============== 本文转载 毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但是对安全方面的知识依旧薄弱,事实上是没机 ...
- Linux启动过程详解(inittab、rc.sysinit、rcX.d、rc.local)
启动第一步--加载BIOS 当你打开计算机电源,计算机会首先加载BIOS信息,BIOS信息是如此的重要,以至于计算机必须在最开始就找到它.这是因为BIOS中包含了CPU的相关信息.设备启动顺序信息.硬 ...
随机推荐
- Cramer-Rao Bounds (CRB)
克拉美-罗界.又称Cramer-Rao lower bounds(CRLB),克拉美-罗下界. 克拉美罗界是对于参数估计问题提出的,为任何无偏估计量的方差确定了一个下限.无偏估计量的方差只能无限制的逼 ...
- 查vue版本号
在项目中,找到package.json文件夹 找"dependencies"然后就可以看到你装的vue的版本了.
- jsp解决大文件断点续传
我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用. 这次项目的需求: 支持大文件的上传和续传,要求续传支持所有浏览器,包括ie6,ie7,i ...
- WinDbg常用命令系列---断点操作b*
ba (Break on Access) ba命令设置处理器断点(通常称为数据断点,不太准确).此断点在访问指定内存时触发. 用户模式下 [~Thread] ba[ID] Access Size [O ...
- 使用git_stats 统计分析git 仓库代码&& 集成webhook
前几天写过一个使用gitstats 统计分析代码的,但是那个因为开发的问题,对于直接和容器集成是有问题的,统计需要进入容器执行 命令,对于自动构建的还不是很方便,所以使用了git_stats 项目 ...
- JavaScript中字符串多行编辑
常用写法: var str = 'w3c' +'标准' +'方式.' 升级版:var str = ['w3c','标准','方式.'].join('');终极版:var str = 'w3c\标准\方 ...
- 微信小程序音乐播放器组件
wxml <image bindtap="click" src="{{isPlay?'/images/':'/images/'}}"/> JS Pa ...
- Dense Semantic Labeling with Atrous Spatial Pyramid Pooling and Decoder for High-Resolution Remote Sensing Imagery(高分辨率语义分割)
对 Potsdam and Vaihingen 公开数据集进行处理,得到了SOTA的结果,超越DeepLab_v3+,提出的网络结构如下:结合了ASPP和FCN,UNet
- Java使用Jsoup之爬取博客数据应用实例
导入Maven依赖 <!-- https://mvnrepository.com/artifact/org.jsoup/jsoup --> <dependency> <g ...
- 2019暑假Java学习笔记(二)
目录 基础语法(下) 流程控制 if语句 switch语句 while语句和do-while语句 for语句 break关键字 continue关键字 数组 一维数组 二维数组 用户输入操作 练习题: ...