初谈Linux多线程--线程控制
创始人
2024-09-26 14:52:35
0

在这里插入图片描述

文章目录

  • 线程的概述
    • 理解线程
    • Linux中的线程
    • 重新理解的进程
    • Windows的线程
    • 线程的优点
    • 线程的缺点
      • 理解线程调度成本低
    • 进程VS线程
  • 线程控制
    • 创建线程
    • 等待线程
    • 线程函数传参
    • 线程的返回值
      • 新线程的返回值
      • 新线程返回值错误
      • 返回值为类对象
    • 创建多线程
    • 线程的终止
    • 线程的分离pthread_detach

线程的概述

理解线程

线程:线程是在进程内部(PCB)运行,是CPU内部调度的基本单位。

在这里插入图片描述


Linux中的线程

在Linux中,线程执行的是进程代码的一部分,也就是说,线程是进程的实体,可以看作是进程内的一个执行单元,我们将这些不同的执行单元称之为轻量级进程,不同线程之间可以通过共享内存来进行通信。
在这里插入图片描述

这里可以举一个例子,要想有一个幸福的家庭,家庭成员就要执行好自己的事情,这样才能成就一个幸福的家庭。

PCB大部分属性都包含了线程的属性,复用PCB,用PCB统一表示执行流,这样线程就不需要给线程单独设计数据结构和调度算法。这样维护的数据结构和调度算法的代码就少。

在CPU看来,不需要区分task_struct是进程还是线程,都叫做执行流。Linux执行流都是轻量级进程。Linux使用进程模拟线程。


重新理解的进程

以前我们学习的进程=内核数据结构+进程的数据代码,这是我们之前理解的。以前我们强调的是代码的执行流,内部只有一个执行流,而现在我们的内部有多个执行流。

因此以内核观点,给进程重新下一个定义:承担分配系统资源的基本实体。


Windows的线程

操作系统设计一个线程,需要新建、创建、销毁、管理等,线程要不要和进程产生关系呢?
操作系统需要对线程进行管理,先描述(struct tcb),再组织。

struct tcb { 	//线程的id,优先级,状态,上下文,连接属性... } 

Windows提供了真实的线程控制块,不是复用PCB属性,这样维护的数据结构和调度算法的代码就高。


线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

线程的缺点

  • 性能损失
    一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型
    线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的
    同步和调度开销,而可用的资源不变。

  • 健壮性降低
    编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了
    不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。

  • 缺乏访问控制
    进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响。

  • 编程难度提高
    编写与调试一个多线程程序比单线程程序困难得多


理解线程调度成本低

线程在同一个进程内部共享相同的地址空间和大部分资源,因此在创建、销毁或者切换线程时,无需像进程那样复制和维护额外的资源。这减少了资源管理的开销。

硬件角度:线程在同一核心上连续执行时,由于其数据可能保持在该核心的缓存中,可以更高效地利用缓存,从而提高数据访问的速度。这可以减少因缓存未命中而引起的额外延迟,进而降低线程切换的成本。


进程VS线程

  • 进程是资源分配的基本单位
  • 线程是调度的基本单位
  • 线程共享进程数据,但也拥有自己的一部分数据:
    线程ID
    一组寄存器:硬件上下文数据,体现出线程可以动态运行
    :线程在运行的时候,本质是运行函数,会形成临时变量,临时变量会被保存在每个线程的栈区
    errno
    信号屏蔽字
    调度优先级

进程的多个线程共享 同一地址空间,因此Text Segment、Data Segment都是共享的,如果定义一个函数,在各线程
中都可以调用,如果定义一个全局变量,在各线程中都可以访问到,除此之外,各线程还共享以下进程资源和环境:
文件描述符表、每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)、当前工作目录、用户id和组id。

进程和线程的关系:

在这里插入图片描述

线程控制

在Linux系统中没有线程,只有轻量级进程,这个轻量级进程实际上就是线程,因为没有单独设计TCB。因此Linux操作系统不会直接给我们提供线程的系统调用,只会提供创建轻量级进程的系统调用接口。Linux系统存在一个中间软件层,有一个pthred库,是自带的原生线程库,对该轻量级进程接口进行封装,按照线程的接口提供给用户。所以说,Linux是用户级线程,Windows是内核级线程。

创建线程

创建一个进程的函数接口:

#include   int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);   Compile and link with -pthread.  

在这里插入图片描述

参数:
thread:返回线程ID,是一个输出型参数
attr:设置线程的属性,attrNULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行的函数
arg:传给线程启动函数的参数

返回值:成功返回0;失败返回错误码,内容未定义。

等待线程

等待线程的函数接口:

#include   int pthread_join(pthread_t thread, void**retval);  Compile and link with -pthread.  

参数
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值
返回值:成功返回0;失败返回错误码

代码示例:

#include #include #include #include  void *threadRun(void *args) {     int cnt=10;     while(cnt)     {         std::cout<<"new thread run ...,cnt:"<     pthread_t tid;     int n=pthread_create(&tid,nullptr,threadRun,(void*)"thread 1");     if(n!=0)     {         std::cerr<<"create thread error"<         std::cout<<"main thread wait sucess"<

在这里插入图片描述

上述代码中,子线程完成之后,主线程才退出。

这里的tid是什么?
tid是线程库中的一个地址,是虚拟地址。
在这里插入图片描述

线程函数传参

主线程数据可传递给新线程:
代码演示:

#include #include #include #include  void *threadRun(void *args) {     //std::string name=(const char*)args;     int a=*(int*)args;     int cnt=10;     while(cnt)     {         std::cout<     char buffer[64];     snprintf(buffer,sizeof(buffer),"0x%lx",tid);     return buffer; }  int main() {     pthread_t tid;      int a=100;      int n=pthread_create(&tid,nullptr,threadRun,(void*)&a);     if(n!=0)     {         std::cerr<<"create thread error"<         std::cout<<"main thread wait sucess"<

在这里插入图片描述


这里传递参数可以是任意类型,可以传递类对象,在传递类对象时,传递的是地址。也就是说,我们可以传递多个参数。

代码示例:

#include #include #include #include  class ThreadData { public:     std::string name;     int num; };  void *threadRun(void *args) {     //std::string name=(const char*)args;     //int a=*(int*)args;     ThreadData *td=static_cast(args);//(ThreadData*)args       int cnt=10;     while(cnt)     {         std::cout<name<<" run ...,num is:"<num<<",cnt:"<     char buffer[64];     snprintf(buffer,sizeof(buffer),"0x%lx",tid);     return buffer; }  int main() {     pthread_t tid;      ThreadData td;     td.name="thread-1";     td.num=1;      int n=pthread_create(&tid,nullptr,threadRun,(void*)&td);     if(n!=0)     {         std::cerr<<"create thread error"<         std::cout<<"main thread wait sucess"<

在这里插入图片描述

上述做法不推荐,td在主线程中,破坏了主线程的完整性和独立性。td 是一个局部变量,其生命周期仅限于 main 函数。一旦 main 函数结束,td 将会被销毁,此时新线程仍然可能在尝试访问已经无效的内存,从而导致未定义行为。另外,如果此时还有一个线程,使用的也是td访问的也是主函数中td变量,那这两个线程中如果其中一个线程对td进行修改,那么就会影响另一个线程。

换一种做法:

在主线程中申请一个堆上的空间,把堆上的地址拷贝给线程,此时对空间就交给了新线程。如果还有第二个线程,那就重新new一个堆上的空间,交给新的线程。这样每一个线程都有属于自己的堆空间。另外,在线程内部处理完后需要delete

#include #include #include #include  class ThreadData { public:     std::string name;     int num; };  void *threadRun(void *args) {     //std::string name=(const char*)args;     //int a=*(int*)args;     ThreadData *td=static_cast(args);//(ThreadData*)args       int cnt=10;     while(cnt)     {         std::cout<name<<" run ...,num is:"<num<<",cnt:"<     char buffer[64];     snprintf(buffer,sizeof(buffer),"0x%lx",tid);     return buffer; }  int main() {     pthread_t tid;      ThreadData *td=new ThreadData(); //在堆上申请空间     td->name="thread-1";     td->num=1;      int n=pthread_create(&tid,nullptr,threadRun,td);     if(n!=0)     {         std::cerr<<"create thread error"<         std::cout<<"main thread wait sucess"<

在这里插入图片描述

线程的返回值

新线程的返回值

线程的返回值是void*,那么主线程的函数就必须使用void **的参数返回,int pthread_join(pthread_t thread, void**retval)void**retval是一个输出型参数,需要一个void*类型的变量即void* retvoid* ret是一个指针类型的变量,有对应的空间,在Linux中占8个字节,在传递的时候是传递&ret ,是将地址传过去。

主线程拿到新线程的退出信息:
在这里插入图片描述

在这里插入图片描述

代码:

#include #include #include #include  class ThreadData { public:     std::string name;     int num; };  void *threadRun(void *args) {     //std::string name=(const char*)args;     //int a=*(int*)args;     ThreadData *td=static_cast(args);//(ThreadData*)args       int cnt=10;     while(cnt)     {         std::cout<name<<" run ...,num is:"<num<<",cnt:"<     char buffer[64];     snprintf(buffer,sizeof(buffer),"0x%lx",tid);     return buffer; }  int main() {     pthread_t tid;      ThreadData *td=new ThreadData(); //在堆上申请空间     td->name="thread-1";     td->num=1;      int n=pthread_create(&tid,nullptr,threadRun,td);     if(n!=0)     {         std::cerr<<"create thread error"<         std::cout<<"main thread wait sucess,new thread exit code:"<<(uint64_t)code<

在这里插入图片描述

可以根据退出信息,就可以判断新线程是否完成对应的任务。

新线程返回值错误

在这里插入图片描述

上述代码故意让新线程出现野指针,是的新线程出现错误。

在这里插入图片描述
上述代码时主线程,新线程出错后让主线程等100s后再退出。

代码演示结果:

在这里插入图片描述

主线程没有等100s后退出,而是在新的进程异常后直接退出。

线程的返回值只有正确时的返回值,一旦出现异常,线程就会崩溃,线程出现异常就会发信号给进程,进程就会被杀掉,即使进程里面有多个线程,里面有一个线程出现错误,整个进程都会被杀掉。
因此线程的在退出的时候只需要考虑正确的返回,不考虑异常,一旦异常,整个进程都会崩溃,包括主线程。

返回值为类对象

主线程创建并启动了一个新的线程,通过 pthread_create 和 pthread_join 实现了线程的创建和等待。
在新线程中,通过 ThreadData 类传递了操作数,并在 threadRun 函数中计算了加法操作的结果。
最后,主线程将计算结果打印出来,展示了线程之间数据的传递和简单的同步操作。

#include #include #include #include  class ThreadData { public:     int Excute()     {         return x+y;     } public:     std::string name;     int x;     int y; };  class ThreadResult { public:     std::string print()     {         return std::to_string(x)+"+"+std::to_string(y)+"="+std::to_string(result);     } public:     int x;     int y;     int result; };  void *threadRun(void *args) {     //std::string name=(const char*)args;     //int a=*(int*)args;     ThreadData *td=static_cast(args);//(ThreadData*)args       ThreadResult *result=new ThreadResult();     int cnt=10;     while(cnt)     {         sleep(1);          std::cout<name<<" run ..."<<",cnt:"<result=td->Excute();         result->x=td->x;         result->y=td->y;         break;      }     delete td;     return (void*)result; }  std::string PrintToHex(pthread_t &tid) {     char buffer[64];     snprintf(buffer,sizeof(buffer),"0x%lx",tid);     return buffer; }  int main() {     pthread_t tid;      ThreadData *td=new ThreadData(); //在堆上申请空间     td->name="thread-1";     td->x=10;     td->y=20;      int n=pthread_create(&tid,nullptr,threadRun,td);     if(n!=0)     {         std::cerr<<"create thread error"<         std::cout<<"main thread wait sucess,new thread exit code:"<print()<

在这里插入图片描述

创建多线程

#include #include #include #include #include  using namespace std;  const int num=10;  void *threadrun(void* args) {     string name=static_cast(args);     while(true)     {         cout<     char buffer[64];     snprintf(buffer,sizeof(buffer),"0x%lx",tid);     return buffer; }  int main() {     vector tids;      for(int i=0;i         pthread_t tid;         char *name=new char[128];         snprintf(name,128,"thread-%d",i+1);//名字         pthread_create(&tid,nullptr,threadrun,name); //传递线程的名字          //保存所有线程的id信息         tids.push_back(tid);     }      //等待     for(auto tid:tids)     {         void *name=nullptr;         pthread_join(tid,&name);         //cout<

在主函数中,首先创建一个 vector 容器 tids,用于存储所有线程的 ID。使用for循环创建num个线程。在第一个for循环中,配一个新的字符数组name来存储线程名字,使用 snprintf 将线程名字格式化为 thread-i 的形式,调用 pthread_create 函数创建线程,传递线程名字作为参数,将每个线程的 ID 存入 tids 向量。第二个for循环中,等待所有进程结束,使用 pthread_join 函数等待线程结束,获取线程返回的 name,并输出线程名字加上 “quit…”,删除线程名字的内存,以防止内存泄漏。

运行结果:
在这里插入图片描述

线程的终止

新的线程如何终止?函数return

主线程如何终止?
主线程对应的main函数结束,那么主线程结束,表示整个进程结束。此时如果还有新线程还没有结束,那么新线程就会被提前终止,因此有线程等待,是的主线程最后结束。

使用线程终止函数:

#include   void pthread_exit(void *retval);  Compile and link with -pthread.  

代码测试:

#include #include #include #include #include #include  using namespace std;  const int num=10;  void *threadrun(void* args) {     string name=static_cast(args);     while(true)     {         cout<     char buffer[64];     snprintf(buffer,sizeof(buffer),"0x%lx",tid);     return buffer; }  int main() {     vector tids;      for(int i=0;i         pthread_t tid;         char *name=new char[128];         snprintf(name,128,"thread-%d",i+1);//名字         pthread_create(&tid,nullptr,threadrun,name); //传递线程的名字          //保存所有线程的id信息         //tids.push_back(tid);         tids.emplace_back(tid);     }      //等待     for(auto tid:tids)     {         void *name=nullptr;         pthread_join(tid,&name);         //cout<

在这里插入图片描述


还有一个终止线程函数:

#include   int pthread_cancel(pthread_t thread);  Compile and link with -pthread.  

这个实际上是取消一个线程,发送一个取消请求给线程,一般都是使用主线程取消其他线程。

代码示例:

#include #include #include #include #include #include  using namespace std;  const int num=10;  void *threadrun(void* args) {     string name=static_cast(args);     while(true)     {         cout<     char buffer[64];     snprintf(buffer,sizeof(buffer),"0x%lx",tid);     return buffer; }  int main() {     vector tids;      for(int i=0;i         pthread_t tid;         char *name=new char[128];         snprintf(name,128,"thread-%d",i+1);//名字         pthread_create(&tid,nullptr,threadrun,name); //传递线程的名字          //保存所有线程的id信息         //tids.push_back(tid);         tids.emplace_back(tid);     }      sleep(5);     //等待     for(auto tid:tids)     {         pthread_cancel(tid);//取消         cout<<"cancel:"<

在这里插入图片描述

线程的分离pthread_detach

线程的分离函数接口:

#include   int pthread_detach(pthread_t thread);  Compile and link with -pthread.  

如果一个线程被创建,默认是需要joinable的,必须被join;如果一个线程被分离,线程的工作状态是分离状态,不能被join

进程分离:一个线程依旧属于线程,但是不需要被主线程等待。

#include #include #include #include #include #include  using namespace std;  const int num=10;  void *threadrun(void* args) {     pthread_detach(pthread_self());     string name=static_cast(args);     while(true)     {         cout<     char buffer[64];     snprintf(buffer,sizeof(buffer),"0x%lx",tid);     return buffer; }  int main() {     vector tids;      for(int i=0;i         pthread_t tid;         char *name=new char[128];         snprintf(name,128,"thread-%d",i+1);//名字         pthread_create(&tid,nullptr,threadrun,name); //传递线程的名字          //保存所有线程的id信息         //tids.push_back(tid);         tids.emplace_back(tid);     }      //等待     for(auto tid:tids)     {         void *result=nullptr;  //现成被取消,被取消的线程的返回结果是-1         int n=pthread_join(tid,&result);         //cout<

在这里插入图片描述

上述代码表示:主线程创建一个新线程,但是新的线程被分离了,没有被等待,因此n返回为22。.

如果被分离的线程出异常依然会影响进程,会导致进程直接崩溃。

在这里插入图片描述

相关内容

热门资讯

王者充值安卓系统在哪,轻松解锁... 亲爱的小伙伴们,是不是在王者荣耀的世界里,你发现了一个小秘密——想要充值,却不知道安卓系统里的充值入...
小米安卓原生系统桌面,体验非凡 哇塞,你有没有想过,你的小米手机其实可以变身成安卓原生的样子?没错,就是那种干净利落、简洁大方的原生...
天辰诀 安卓系统,安卓系统下的... 你知道吗?最近在安卓系统上有一款游戏火得一塌糊涂,那就是《天辰诀》!这款游戏不仅画面精美,玩法多样,...
安卓记录仪系统,安卓系统下的M... 你有没有想过,你的行车记录仪其实就像是一个小小的智能助手呢?它不仅能帮你记录行车过程中的点点滴滴,还...
安卓系统解码图案忘记,找回遗忘... 亲爱的手机控们,你们有没有遇到过这种情况:手机解锁图案忘得一干二净,急得像热锅上的蚂蚁,心里那个慌啊...
谷歌研发安卓新系统,探索谷歌的... 哇塞,你知道吗?谷歌最近可是秘密研发了一款全新的笔记本电脑,而且据说这款笔记本可能会搭载安卓系统哦!...
安卓如何转换ios系统,探索系... 你有没有想过,把你的安卓手机变成苹果手机呢?想象那光滑的屏幕、流畅的系统,还有那独特的苹果风格,是不...
小米11安卓系统耗电,深度剖析... 你有没有发现,最近你的小米11手机好像有点儿“懒洋洋”的,充电宝不离身,电量像坐过山车一样忽高忽低?...
魅蓝是安卓系统,深度解析安卓系... 你有没有想过,你的手机里那个小小的屏幕,竟然能装下整个世界?今天,咱们就来聊聊魅蓝手机,看看它那小小...
安卓系统怎么替换log,And... 亲爱的安卓开发者们,你是否曾在调试过程中为找不到合适的日志替换方法而头疼?别担心,今天我要带你一起探...
安卓手机好的系统,安卓手机操作... 你有没有发现,现在手机市场上的安卓手机真是琳琅满目,让人挑花了眼。不过,你知道吗?在这些安卓手机中,...
安卓系统mac电脑配置,打造安... 亲爱的电脑迷们,你是否曾想过,你的苹果笔记本里也能装上安卓系统?是的,你没听错!今天,就让我带你一起...
状元郎平板安卓系统,引领平板教... 你有没有想过,一款平板电脑,竟然能让你在学习之余,还能畅游安卓世界的海洋?没错,今天我要跟你聊聊的就...
安卓系统哪个传奇好玩,畅玩经典... 手机里的游戏可是咱们休闲娱乐的一大法宝,尤其是安卓系统,那丰富的游戏资源简直让人挑花眼。今天,就让我...
联众支持安卓系统吗,“联众PD... 斗地主爱好者们,是不是在为找不到一款好玩的斗地主游戏而烦恼呢?别急,今天我要给大家揭秘一个好消息——...
康佳电视安卓系统太卡,康佳电视... 亲爱的电视迷们,你们有没有遇到过这样的烦恼:家里的康佳电视用着用着就变得像蜗牛一样慢吞吞的,让人抓狂...
ios对比安卓系统流畅,流畅体... 你有没有发现,用手机的时候,有时候iOS系统就像个优雅的舞者,而安卓系统则像个活力四射的少年?没错,...
安卓系统占用内存小,深度解析优... 你有没有发现,手机用久了,就像人一样,会变得“臃肿”起来?尤其是安卓系统,有时候感觉就像一个超级大胃...
安卓系统怎么下载jdk,JDK... 你有没有想过,在安卓手机上也能编写Java程序呢?没错,就是那种在电脑上写代码的感觉,现在也能在手机...
安卓系统调手机亮度,轻松掌握手... 手机屏幕亮度总是让你眼花缭乱?别急,今天就来手把手教你如何轻松调节安卓系统的手机亮度,让你的手机屏幕...