【Linux】————多线程(概念及控制)
https://img-blog.csdnimg.cn/direct/9efbcbc3d25747719da38c01b3fa9b4f.gif作者主页: 作者主页
本篇博客专栏:Linux
创作时间 :2024年11月19日
https://img-blog.csdnimg.cn/direct/9efbcbc3d25747719da38c01b3fa9b4f.gif
https://img-blog.csdnimg.cn/direct/b2e39275a06843c3b6c48d38a97a376e.jpeg
再谈地点空间:
https://i-blog.csdnimg.cn/direct/68cf9ba3024d480c9e125ec056976b00.png
OS对内存举行管理不是根据字节为单元,以字节为单元效率过低,是以内存块为单元的,一个内存块的巨细一般为4kb,体系和磁盘IO的基本单元是4kb--8个扇区
文件在磁盘中存储的时候都是有自己的datablock的,每个datablock的巨细都是4kb,以是内存管理时,加载就是把程序的数据块加载到指定的内存块中。
为了方便举行表述,4kb的空间+内容有一个名字,叫页框或页帖,在内核中有一个struct page来管理每一个页框,内存当作是数组,第一个page的起始地点就是数组下标,*4==0 ,第二个page的起始地点是数组下标*4==4。以是每个page都有了下标。
那我们之前所说的假造地点到底是如何转化成物理地点的呢?
https://i-blog.csdnimg.cn/direct/cead07353eac44a28eae56ff39b26051.png
假造地点的前十个比特位索引页目录,中央十个比特位索引页表,页表指向对应页框的起始地点,假造地点的低12位+页框的起始地点就能找到页框内的任意一个字节了。
这种页表也叫二级页表,用来搜刮页框。
假造地点本质是一种资源。
线程概念:
[*]在一个程序里的一个实行路线叫做线程,更正确的定义是:线程是一个进程内部的控制序列
[*]一切进程至少有一个实行线程
[*]线程在进程内部运行,本质是在进程地点空间内运行
[*]在Linux体系中,在CPU眼里,看到的PCB都要比传统的进程更加轻量化
[*]透过进程假造地点空间,可以看到进程的大部分资源,将进程合理的分配给每一个实行流,就形成了线程实行流
https://i-blog.csdnimg.cn/direct/09a06236dccd45ea8d11aa972938610c.png
线程:在进程内部运行,是CPU调度的基本单元
进程:负担分配体系资源的基本实体
我们从前讲的都是进程内部只有一个实行流的进程,Windows体系里有struct tcb结构体形貌线程,Linux体系选择复用struct pcb结构体。以是Linux是用进程模仿的线程。
Linux中CPU不区分task_struct 是进程照旧线程,都看做实行流。
CPU看到的实行流<=进程。
Linux中的实行流叫:轻量级进程。
创建进程初识:
https://i-blog.csdnimg.cn/direct/2f20d56bf9cd450bb2098b4e480924bd.png
这个函数的功能是创建一个新的线程:
参数
[*]thread:返回的线程ID
[*]attr:设置线程的属性,attr为nullptr为使用默认属性
[*]start_routine:这是一个函数指针,线程启动后要实行的函数,返回值类型为void*,参数类型为void*
[*]arg:传给线程启动函数的参数
返回值:成功返回0 失败返回错误码
看一下我们的代码:
#include <iostream>
#include <unistd.h>
#include <pthread.h>
void *routine(void *args)
{
while (true)
{
std::cout << "new thread runing...." << std::endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, routine, (void *)"pthread-1");
while (true)
{
sleep(1);
std::cout << "main thread runing..." << std::endl;
}
return 0;
}这里我们记得不可以直接编译,直接编译会出现说直接创建线程是未定义的活动,以是我们要在Makefile中加上https://i-blog.csdnimg.cn/direct/bf8441b944f34888afdaaac747fdb889.png
才可以
这个操作的意思是在编译时引入pthread库
运行之后看到:
https://i-blog.csdnimg.cn/direct/95b22392f2db4a6296bf1d960f7bc94b.png
运行程序, 因为主次线程里都是死循环打印,结果主次线程都有打印,说明有多实行流,即线程创建成功了。
打印出他们的pid,可以看到主次线程的pid都是一样的,因为这两个线程他们都属于同一个进程内部,以是对应的进程pid是一样的。https://i-blog.csdnimg.cn/direct/dc547c0fc1714c42ac1123c65d9f6491.png
如果想查看线程,可以通过指令 ps -aL 。他们的pid都是一样的。LWP就是Light Weight Process,即轻量级进程,就是线程的id。
我们把pid和lwp都相称的实行流叫主线程。
线程的优点
[*]创建一个新线程比创建一个新进程代价要小很多
[*]与进程之间的切换相比,线程之间的切换OS要做的工作要少很多
[*]线程占用的资源比进程少很多
[*]线程能充实利用好多处置惩罚器的可并行数量
[*]在等候慢速IO结束的同时,线程可以举行其他工作
[*]计算麋集型应用,为了能在多处置惩罚器体系上运行,将线程分解到多个线程中实现
[*]IO麋集型应用,为了提高性能,将IO操作重叠,线程可以同时等候不同的IO操作
线程的缺点
[*]性能损失
[*]结实性低落
[*]编写多线程的程序需要我们更全方面的思量,在一个多线程程序里,因事件分配上的渺小毛病或者因共享了不该共享的变量而造成影响的可能是很大的,换句话说就是线程之间缺乏保护(多线程程序,其中一个线程出了问题,全部线程都会出问题)
[*]缺乏访问控制
线程非常
[*]单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃
[*]线程是进程的实行分支,线程出非常,就雷同进程出非常,进而触发信号机制,终止进程,进程终止,该进程内的全部线程也就随即退出
Linux进程vs线程
[*]进程是资源分配的基本单元
[*]线程是调度的基本单元但也拥有自己的一部分数据
[*]线程ID
[*]一组寄存器(重要)
[*]栈(重要)(线程运行的时候,会形成各种临时变量,临时变量会被每个线程生存在自己的栈区)
[*]errno
[*]信号屏蔽字
[*]调度优先级
进程中的多个线程共享一个地点空间,除此之外,各线程还共享以下的进程资源和情况
[*]文件形貌符表
[*]每种信号的处置惩罚方式(SIG_ IGN、SIG_ DFL或者自定义的信号处置惩罚函数)
[*]当前工作目录
[*]用户id和组id
进程和线程的关系图如下:
https://i-blog.csdnimg.cn/direct/878fd125ee234fc3b4782b061bb9c514.png
Linux线程控制
POSIX线程库
[*]与线程有关的函数构成了一个完备的系列,绝大多数函数的名字都是以“pthread_”打头的
[*]要使用这些函数库,要通过引入头文件<pthread.h>
[*]链接这些线程函数库时要使用编译器命令的“-lpthread”选项
创建线程
前面已经简单介绍了pthread_create的使用。在创建完成后,主线程会继承向下实行代码,新线程会去实行参数3所指向的函数。此时实行流就一分为二了。
线程等候
https://i-blog.csdnimg.cn/direct/f8aa55eead4d440db457751f71a81153.png
功能:等候线程结束
参数:
thread:线程ID
retval:它指向一个指针,指向线程的返回值(输出型参数)
参数2的类型是void**,用来接收新线程函数的返回值,因为新线程函数的返回值类型是void*。未来要拿到新线程的返回值void*,放到void* retval中时,这里的参数就得传&retval。
返回值:成功返回0;失败返回错误码
https://i-blog.csdnimg.cn/direct/203c292de670419f8268d0fde8c8f4b3.png
这是线程等候的演示代码,下面是结果:
https://i-blog.csdnimg.cn/direct/bc8c2c9bd8734510a981cd2395e64d29.png
如上图,为pthread_create和pthread_join的简单使用。pthread_t类型由库提供。主线程和新线程谁先运行,这是不确定的。
https://i-blog.csdnimg.cn/direct/8c2e4388d1184cd081f0982a06b3210b.png
我们把tid以字符串的形式打印出来,发现是一个假造地点,他跟线程id不一样。
线程函数传参
https://i-blog.csdnimg.cn/direct/d5dc88db084843a38bd8d6a69fcb3dc8.png
https://i-blog.csdnimg.cn/direct/7f60b93f5caf4cbcb2bc591acaff69b1.png
线程函数传参,可以传任意类型,一定要记住还可以传类对象的地点。 有了这个,就意味着可以给线程传递多个参数,乃至方法了。
上面的td对象是在主线程的栈上的,新线程访问了主线程栈上的临时变量,我们不推荐这种做法。因为如果main函数有第二个对象,他们在读取时没有影响,但其中一个对象在修改时,另一个也会跟着修改。推荐做法如下图:
https://i-blog.csdnimg.cn/direct/d1a82904ee144703b12579f186753c8c.png我们建议在堆上申请一段空间,未来需要第二个对象时,再重新new一个对象,这样多线程就不会互相干扰了。
创建多线程
https://i-blog.csdnimg.cn/direct/023bc96cdb0c48ecb8b79b6a3d654dcf.png
https://i-blog.csdnimg.cn/direct/7c17003e774b4ec2a3d3f03ca8976b9c.png
完备代码:
const int num=10;
void* threadrun(void* args)
{
std::string name=static_cast<const char*>(args);
while(true)
{
std::cout<<name<<" is running "<<std::endl;
sleep(1);
break;
}
return args;
}
int main()
{
std::vector<pthread_t> tids;
for(int i=0;i<num;i++)
{
//1.线程的id
pthread_t tid;
//2.线程的名字
char* name=new char;
snprintf(name,128,"thread-%d",i+1);
pthread_create(&tid,nullptr,threadrun,name);
//3.保存所有线程的id信息
tids.push_back(tid);
//tids.emplace_back(tid);
}
for(auto tid:tids)
{
void* name=nullptr;
pthread_join(tid,&name);
// std::cout<<PrintToHex(tid)<<"quit ..."<<std::endl;
std::cout<<(const char* )name<<" quit..."<<std::endl;
delete (const char* )name;
}
return 0;
} 运行结果为什么线程名是乱的?因为即使我们的线程是按顺序创建的,但他们不是按顺序启动的。而且上面的name,属于main函数栈上的空间,即main函数栈空间上的公共区传给了每一个线程,以是线程名会被不停覆盖。以是上面这么写是有问题的,要在堆在开辟空间。
线程终止
https://i-blog.csdnimg.cn/direct/2a8601c1758b42ebb6a61209d474a09b.png
运行后,发现主线程没有打印quit语句。因为exit是专门用来终止进程的,不能用来终止线程。任意一个线程调用exit,都可能会导致进程退出。
https://i-blog.csdnimg.cn/direct/02641862de794e27b92b59bf8d94d6b4.png
除了用return结束线程,pthread_exit是专门用来终止一个线程的,使用如下图:
https://i-blog.csdnimg.cn/direct/47eae62e8e1947818f87209271fc0cb1.png
https://i-blog.csdnimg.cn/direct/4cca15d67a5046c49c41b4a27d22fb8b.png
下面是终止线程的另一种方法:
https://i-blog.csdnimg.cn/direct/8a320b40497e40e5ae58fa67c311d6f7.png
主线程调用pthread_cancel取消新线程。取消一个线程的前提是线程得存在。
https://i-blog.csdnimg.cn/direct/0e7fd2d382124c7eae24efe903b75376.png
https://i-blog.csdnimg.cn/direct/c98bb090dde64fef9edb1bfd05c5ef9d.png
线程取消一个就join一个。由上图可知,线程被取消后,线程的退出结果是-1。
-1对应pthread库中的一个宏
分离线程
https://i-blog.csdnimg.cn/direct/a6fe1dba74dc4dc7ad2e9c899dbe42e6.png
作用:哪个线程调用该接口,就返回他自己的线程id。相当于从前的getpid。
void* threadrun(void* args)
{
pthread_detach(pthread_self());
std::string name=static_cast<const char*>(args);
while(true)
{
std::cout<<name<<" is running "<<std::endl;
sleep(3);
break;
}
pthread_exit(args);//专门终止一个线程
}
int main()
{
std::vector<pthread_t> tids;
for(int i=0;i<num;i++)
{
//1.线程的id
pthread_t tid;
//2.线程的名字
char* name=new char;
snprintf(name,128,"thread-%d",i+1);
pthread_create(&tid,nullptr,threadrun,name);
tids.emplace_back(tid);
}
for(auto tid:tids)
{
void* result=nullptr; //线程被取消,线程的退出结果是-1
int n=pthread_join(tid,&result);
std::cout<<(long long int)result<<" quit... ,n: "<<n<<std::endl;
}
return 0;
} 运行上面代码,程序直接挂掉了。因为新线程已经分离,主线程不会卡在join,而是会继承往后走,主线程结束了,整个进程就结束了,新线程可能还没起来就死亡了。
以是分离线程后,主线程就可以做自己的事了,不消管新线程。
即使新线程分离,只要分离的线程非常了,照旧会影响整个进程。
除了可以让新线程自己分离,也可以由主线程举行分离。
C++11使用多线程
C++11里使用多线程,创建时是支持可变参数的。大致用法跟前文讲的差不多。
https://i-blog.csdnimg.cn/direct/e7f5801011884bf2ac5cd3f2a2fb258a.png
我们把makefile文件里的 -lpthread 去掉然后编译。
编译后,报错了,链接时报错。以是C++语言在Linux中要编译支持多线程,也要加 -lpthread。
C++11的多线程本质:就是对原生线程库接口的封装。
Linux中,C++11要支持多线程,底层必须封装Linux情况的pthread库,编译的时候都得带。
在Windows下要编译多线程程序不消带-lpthread。
最后:
十分感谢你可以耐着性子把它读完和我可以对峙写到这里,送几句话,对你,也对我:
1.一个冷知识:
屏蔽力是一个人最顶级的能力,任何消耗你的人和事,多看一眼都是你的不对。
2.你不消变得很外向,内向挺好的,但需要你发言的时候,一定要勇敢。
正所谓:君子可内敛不可懦弱,面不公可起而论之。
3.成年人的世界,只筛选,不教育。
4.自律不是6点起床,7点定时学习,而是不管别人怎么说怎么看,你也会对峙去做,绝不打乱自己的节奏,是一种自我的恒心。
5.你开始夸耀自己,每每都是灾难的开始,就像老子在《道德经》里写到:光而不耀,静水流深。
最后如果以为我写的还不错,请不要忘记点赞✌,收藏✌,加关注✌哦(。・ω・。)
愿我们一起加油,奔向更美好的未来,愿我们从懵懵懂懂的一枚菜鸟逐渐成为大佬。加油,为自己点赞!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]