【Linux】线程控制

       🔥🔥 欢迎来到小林的博客!!
      🛰️博客主页:✈️林 子
      🛰️博客专栏:✈️ Linux
      🛰️社区 :✈️ 进步学堂
      🛰️欢迎关注:👍点赞🙌收藏✍️留言

目录

  • 线程的异常终止
  • 线程等待
  • 线程退出
  • 线程取消
  • 线程id
    • pthread如何管理线程
  • 线程分离

线程的异常终止

进程内部是可以存在多个线程的。那么如果有一个线程出现了异常(产生了信号)。那么会发生什么后果呢?我们用下面这段代码来验证一下。

#include<iostream>
#include<pthread.h>
#include<unistd.h> void* ThreadRoutine(void* args)
{int id = *(int*)args;delete (int*)args;int count  = 0 ; while(1){printf("%d thread runing.... count = %d\n",id,count);//如果线程id为2, 且count计数到3,那么制造异常,产生信号 if(id == 2 && count == 3) {int a = 10;a /= 0; }sleep(1);count++;}
}int main()
{pthread_t tids[3]; //创建三个线程for(int i = 0 ; i < 3; i  ++){int* id = new int(i);pthread_create(tids+i , nullptr, ThreadRoutine,(void*)id);}while(1){printf("main thread runing...\n");sleep(2);}return 0 ;
}

该代码的逻辑就是创建3个线程,让第三个线程计数到3的时候 /0 产生信号。

我们运行看看结果。

在这里插入图片描述

我们发现整个进程都崩溃了。所以可以得出结论:

一个线程产生异常,那么所有线程都会崩溃。本质是因为所有线程共享信号处理函数,而信号来临时选择了默认处理方式,那么就会干掉整个进程。进程都被干掉了,那么内部的线程肯定也无一幸免。

线程等待

在创建进程的时候,我们的父进程要等待子进程。否则子进程在结束时资源将无法释放,成为僵尸进程,造成内存泄漏。而线程这里也是一样的道理,如果主线程不等待子线程。子线程结束后,主线程还在运行,那么一样会造成资源的泄漏。所以等待子线程是很有必要的。

线程等待函数:

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
第一个参数是要等待线程的tid 
第二个参数是创建线程时传入的线程执行函数的返回值,在内核中会回调那个函数。而那个函数的返回值会返回给内核,再由内核输出到这个参数上。
返回值:返回0为成功,非0为错误码。如果线程已分离还进行join,那么会返回-1

我们用以下代码来测试一下这个函数。

#include<iostream>
#include<pthread.h>
#include<unistd.h> void* ThreadRoutine(void* args)
{int id = *(int*)args;delete (int*)args;int count  = 0 ; while(1){printf("%d thread runing.... count = %d\n",id,count);sleep(1);if(count++ == 3) break; }return (void*)id; //返回自己的id
}int main()
{pthread_t tids[3]; //创建三个线程for(int i = 0 ; i < 3; i  ++){int* id = new int(i);pthread_create(tids+i , nullptr, ThreadRoutine,(void*)id);}//线程等待int* ids[3] = {0};pthread_join(tids[0],(void**)ids+0);    pthread_join(tids[1],(void**)ids+1);    pthread_join(tids[2],(void**)ids+2); //打印三个线程的返回结果for(int i = 0 ; i < 3 ; i++)printf("thread %d : %ld\n",i,(long long)(ids[i]));return 0 ;
}

这段代码的逻辑就是 创建三个线程,每个线程执行三次循环,随后返回传入时的id。主线程等待三个线程,等待结束后打印三个线程的返回值。

运行结果:

在这里插入图片描述

线程退出

如果我们在线程内调用exit函数,那么整个进程都会退出。因为exit是进程退出函数,无论在哪调用,都会导致进程退出。进程退出,那么所有的线程也会被释放。

所以线程退出,我们要用指定的退出函数。

线程退出函数:

#include <pthread.h>
void pthread_exit(void *retval);
参数是传入一个当前线程的返回值。

那么我们用代码来演示一下程序退出。

#include<iostream>
#include<pthread.h>
#include<unistd.h> void* ThreadRoutine(void* args)
{int id = *(int*)args;delete (int*)args;pthread_exit((void*)(id + 10));int count  = 0 ; while(1){printf("%d thread runing.... count = %d\n",id,count);sleep(1);if(count++ == 3) break; }return (void*)id; //返回自己的id
}int main()
{pthread_t tids[3]; //创建三个线程for(int i = 0 ; i < 3; i  ++){int* id = new int(i);pthread_create(tids+i , nullptr, ThreadRoutine,(void*)id);}//线程等待int* ids[3] = {0};pthread_join(tids[0],(void**)ids+0);    pthread_join(tids[1],(void**)ids+1);    pthread_join(tids[2],(void**)ids+2); //打印三个线程的返回结果for(int i = 0 ; i < 3 ; i++)printf("thread %d : %ld\n",i,(long long)(ids[i]));return 0 ;
}

代码逻辑很简单,一进线程就退出,传入的值为 传入的id+10。

运行结果:

在这里插入图片描述

线程取消

线程退出函数必须要在要退出的线程内部执行。但如果我想在主线程中让指定的线程退出。那么我们可以用线程取消的函数。

线程取消函数:

#include <pthread.h>
int pthread_cancel(pthread_t thread);
传入的是要取消的线程tid。
返回值为0则成功,失败返回一个错误的非0数字。

我们用一段代码来验证这个函数。

#include<iostream>
#include<pthread.h>
#include<unistd.h> void* ThreadRoutine(void* args)
{int count  = 0 ; while(1){printf("%s runing.... count = %d\n",(char*)args,count);sleep(1);}return nullptr; //返回自己的id
}int main()
{pthread_t tid; pthread_create(&tid , nullptr, ThreadRoutine,(void*)"new thread ");sleep(3); pthread_cancel(tid); //取消线程 sleep(3);return 0 ;
}

这段代码逻辑很简单,创建一个线程死循环执行。主线程三秒之后取消这个线程。再过三秒之后主线程结束(整个进程结束)。 在这个过程中我们实时监视。

监视结果:

在这里插入图片描述

我们发现三面之前这个进程有2个线程,三秒之后就变成了一个线程。说明创建的线程被取消了,而三秒之后一个线程也没有了,说明主线程执行完毕,进程退出了。

线程id

我们可以用pthread_self()来返回线程id,并分别用16进制和10进制打印这个id。因为我提前知道了这个id会是个很大数(透剧怪登)。

#include <pthread.h>
pthread_t pthread_self(void);

那么我们写段代码来打印一下线程的id。

#include<iostream>
#include<pthread.h>
#include<unistd.h> void* ThreadRoutine(void* args)
{int count  = 0 ; pthread_t tid = pthread_self();while(1){printf("%s runing.... count = %d ,thread id %ld , %lX \n",(char*)args,count,tid,tid);sleep(1);}return nullptr; //返回自己的id
}int main()
{pthread_t tid; pthread_create(&tid , nullptr, ThreadRoutine,(void*)"new thread ");pthread_join(tid,nullptr) ; return 0 ;
}

这段代码的逻辑就是创建一个线程,然后通过pthread_self()函数获取当前线程的id,随后分别以10进制和16进制打印这个id。

运行结果:

在这里插入图片描述

当10进制打印进程id时,我们会发现它是一个很大的数字,并且看不出什么,但是当我们以16进制打印时。我们惊奇的发现,这是不是很像一个地址?

没错,线程id就是一个地址!

那么就有疑问了,CPU不是LWP进行调度吗?为什么线程id不是LWP,却是一个很大的数字?我们要搞清楚一点,pthread库是一个动态库!!而LWP是系统内核管理线程的标识。线程id是pthread库管理线程的标识。

在内核方面,用LWP来标识PCB进行调度,所以没有进程线程的区别。但是不要忘记了,线程是用pthread库创建出来的。那么创建出来要不要进行管理?这是必须的!!

pthread如何管理线程

首先我们都知道pthread是一个动态库,那么在程序执行的时候。会先把动态库加载进内存,随后根据页表把它映射到进程的共享空间内。

在这里插入图片描述

当pthread_create创建了一个线程时,那么就会在动态库中执行这个函数。随后就会创建一个struct pthread的结构体,线程局部存储以及线程栈。而这个结构体的地址(共享区中的虚拟地址)就是线程的id。这个结构体就是对这个线程的描述,存储着线程的信息,用来管理该线程。

在这里插入图片描述

虽然进程内部的线程都是共享进程地址空间的,那么就意味着进程地址空间中的栈(也叫主线程栈)可以被所有的线程访问。这是必然的,但访问归访问。线程本身的数据是不能存储在主线程栈中的。因为这样会导致存储十分混乱。也不能一个一个线程进行覆盖,这样会导致数据缺失。所以pthread动态库为每一个线程都提供了一个线程栈,这个线程栈是每个线程独有的(想跨线程访问也可以访问,但还是别这样做)。

线程局部存储是什么呢?

我们可以用__thread 来让一个全局变量变成线程私有的。

先看一段代码:

#include<iostream>
#include<pthread.h>
#include<unistd.h> int val = 0;void* ThreadRoutine(void* args)
{while(1){printf("new thread , val = %d , &val = %p \n",val,&val);sleep(1);}return nullptr; //返回自己的id
}int main()
{pthread_t tid; pthread_create(&tid , nullptr, ThreadRoutine,(void*)"new thread ");while(1){printf("main thread , val = %d, &val = %p\n",val,&val);val++;sleep(1);}pthread_join(tid,nullptr) ; return 0 ;
}

这段代码的逻辑就是创建一个线程不断打印全局变量,主线程也不断打印全局变量,但是主线程会在每打印一次自增一次全局变量。

这段代码的结果是这样的:

在这里插入图片描述

没有意外,因为线程之间共享全局变量。所以打印的地址是一样的,值也是同步的。

那么我们把int val换成__thread int val再来试试效果。

#include<iostream>
#include<pthread.h>
#include<unistd.h> int val = 0;void* ThreadRoutine(void* args)
{while(1){printf("new thread , val = %d , &val = %p \n",val,&val);sleep(1);}return nullptr; //返回自己的id
}int main()
{pthread_t tid; pthread_create(&tid , nullptr, ThreadRoutine,(void*)"new thread ");while(1){printf("main thread , val = %d, &val = %p\n",val,&val);val++;sleep(1);}pthread_join(tid,nullptr) ; return 0 ;
}

然后我们运行这段程序。

运行结果:

在这里插入图片描述

我们发现两个val的值不同步了,地址也不一样了。

原理很简单,本质就是在编译时,把val值的数据拷贝了一份。拷贝进了对应线程的线程局部存储中。

结论:

线程id本质是一个地址,这个地址在共享区中,与页表建立映射。最终映射到物理内存中对应的struct pthread结构体的地址起始处。在pthread_create创建了一个线程之后,pthread会在共享区创建一块内存。这个内存存储着线程的结构体,线程的局部存储以及线程栈。 而通过线程id可以找到线程结构体的起始位置。

线程分离

如果我们的主线程创建了线程之后却不想管它。那么我们可以用pthread_detach来让线程分离。 用pthread_detach来分离线程,那么被分离的线程在结束后就会自动销毁。如果不detach也不join,那么线程的一些资源就无法被释放,此时的线程就会陷入与僵尸进程相似的状态。

线程分离函数:

int pthread_detach(pthread_t thread);
参数是传入要分离线程的tid。
返回值:0为成功,非0则分离失败。

pthread_detach代码:

#include<iostream>
#include<pthread.h>
#include<unistd.h> void* ThreadRoutine(void* args)
{pthread_detach(pthread_self());int count = 5;while(count--){printf("new thread , count = %d\n",count);sleep(1);}return nullptr; //返回自己的id
}int main()
{pthread_t tid; pthread_create(&tid , nullptr, ThreadRoutine,(void*)"new thread ");while(1){printf("main thread runing.... \n");sleep(1);}return 0 ;
}

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

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

相关文章

DependsOn注解失效问题排查

文章目录 前言一、现象描述1.1.背景描述1.2.第一次修改&#xff0c;使用DependsOn注解1.3.第二次修改&#xff0c;设置方法入参 二、看看源码2.1.Spring实例化的源码2.2.调试2.3.验证 总结 前言 最近几天遇到一个比较有意思的问题&#xff0c;发现Spring的DependsOn注解失效&a…

CSS3与HTML5

box-sizing content-box&#xff1a;默认&#xff0c;宽高包不含边框和内边距 border-box&#xff1a;也叫怪异盒子&#xff0c;宽高包含边框和内边距 动画&#xff1a;移动translate&#xff0c;旋转、transform等等 走马灯&#xff1a;利用动画实现animation&#xff1a;from…

websocket拦截

python实现websocket拦截 前言一、拦截的优缺点优点缺点二、实现方法1.环境配置2.代码三、总结现在的直播间都是走的websocket通信,想要获取websocket通信的内容就需要使用websocket拦截,大多数是使用中间人代理进行拦截,这里将会使用更简单的方式进行拦截。 前言 开发者工…

【C++面向对象侯捷下】4. pointer-like classes,关于智能指针 | 5. function-like classes,所谓仿函数

文章目录 4. pointer-like classes,关于智能指针pointer-like classes,关于智能指针 shared_ptrpointer-like classes,关于迭代器5. function-like classes&#xff0c;所谓仿函数【不懂&#xff0c;跳过】 4. pointer-like classes,关于智能指针 pointer-like classes,关于智…

FFMPEG 视频类过滤器学习整理

针对FFMPEG提供视频过滤器进行了介绍,并提供使用实例 addroi 作用 在视频帧上标记一块感兴趣的区域。 帧数据被原封不动地传递,但元数据被附加到帧,指示可能影响后续编码行为的感兴趣区域。可以通过多次应用过滤器来标记多个区域。 参数 qoffset: 应用在此区域的量化偏…

C语言 - 数组

目录 1. 一维数组的创建和初始化 1.1 数组的创建 1.2 数组的初始化 1.3 一维数组的使用 1.4 一维数组在内存中的存储 2. 二维数组的创建和初始化 2.1 二维数组的创建 2.2 二维数组的初始化 2.3 二维数组的使用 2.4 二维数组在内存中的存储 3. 数组越界 4. 数组作为函数参数 4.1…

电脑上制定工作计划的软件有哪些?

在数字的时代&#xff0c;电脑成为了我们工作的得力助手&#xff0c;但在这个信息过载的年代&#xff0c;如何在电脑上制定高效的工作计划成了工作中必不可少的一项任务。我在工作中也曾经迷茫过&#xff0c;制定的各项计划不能按时完成&#xff0c;直到我找到一款可以辅助我办…

tomcat整体架构

Tomcat介绍 Tomcat是Apache Software Foundation&#xff08;Apache软件基金会&#xff09;开发的一款开源的Java Servlet 容器。它是一种Web服务器&#xff0c;用于在服务器端运行Java Servlet和JavaServer Pages (JSP)技术。它可 以为Java Web应用程序提供运行环境&#x…

Linux读写锁的容易犯的问题

Linux读写锁的容易犯的问题 读写锁是互斥锁之外的另一种用于多线程之间同步的一种方式。 多线程对于一个共享变量的读操作是安全的&#xff0c; 而写操作是不安全的。如果在一个读很多而写很少的场景之下&#xff0c;那么使用互斥锁将会阻碍大量的线程安全的读操作的进行。在…

腾讯云轻量和CVM有啥区别?怎么选择服务器配置?

腾讯云轻量服务器和云服务器有什么区别&#xff1f;为什么轻量应用服务器价格便宜&#xff1f;是因为轻量服务器CPU内存性能比云服务器CVM性能差吗&#xff1f;轻量应用服务器适合中小企业或个人开发者搭建企业官网、博客论坛、微信小程序或开发测试环境&#xff0c;云服务器CV…

关于Jupyter markdown的使用

一级标题 #空格 标题1 二级标题 ## 空格 标题2 三级标题 ###空格 标题3 无序&#xff1b; 有序&#xff1a; 数学符号&#xff1a;

记一次问题排查

1785年&#xff0c;卡文迪许在实验中发现&#xff0c;把不含水蒸气、二氧化碳的空气除去氧气和氮气后&#xff0c;仍有很少量的残余气体存在。这种现象在当时并没有引起化学家的重视。 一百多年后&#xff0c;英国物理学家瑞利测定氮气的密度时&#xff0c;发现从空气里分离出来…

Visual Studio 2019中的安全问题

最近&#xff0c;在使用Visual Studio 2019的时候遇到了一个很奇怪的问题&#xff0c;如下所示。 这里一直在说scanf函数不安全&#xff0c;导致报错&#xff0c;然后上网查了查相关资料&#xff0c;发现在代码中加那么一句就可以了&#xff0c;而且必须放在最前面。 #define …

网工内推 | IT高级运维工程师,周末双休,包吃包住,14-20k

01 深圳朗特智能控制股份有限公司 招聘岗位&#xff1a;IT高级运维工程师 职责描述&#xff1a; 1、对集团网络基础架构的建设、运维、安全制定相关标准和准则&#xff1b; 2、负责集团数据中心、核心设备、信息安全的管理和运维&#xff1b; 3、执行网络、服务器、核心交换机…

wpf中prism框架

安装prism包&#xff1a; 添加引用 using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Linq; using System.Threading.Tasks; using System.Windows; using Prism.DryIoc; using Prism.Ioc;namespace PrismDemo …

A (1087) : DS单链表--类实现

Description 用C语言和类实现单链表&#xff0c;含头结点 属性包括&#xff1a;data数据域、next指针域 操作包括&#xff1a;插入、删除、查找 注意&#xff1a;单链表不是数组&#xff0c;所以位置从1开始对应首结点&#xff0c;头结点不放数据 类定义参考 #include<…

【考研复习】union有关的输出问题

文章目录 遇到的问题正确解答拓展参考文章 遇到的问题 首次遇到下面的代码时&#xff0c;感觉应该输出65,323。深入理解union的存储之后发现正确答案是&#xff1a;67,323. union {char c;int i; } u; int main(){u.c A;u.i 0x143;printf("%d,%d\n", u.c, u.i); …

初阶数据结构(四)带头双向链表

&#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;数据结构 &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &#x1f339;&#x1f339;&#x1f339;关注我带你学习编程知识 带头双向链表 链表的相关介绍初始化链表销毁链…

XCode打包IOS应用发布App Store和Ad Hoc测试

文章目录 零、前置说明一、创建本地证书二、配置描述文件2.1 配置certificates2.1.1 配置证书2.1.2 安装cer证书2.1.2.1 打包机器和生成证书同机器2.1.2.2 打包机器和生成证书不同机器 2.2 创建Identifiers2.3 配置Devices2.4 配置Profiles2.4.1 配置生产Profile2.4.2 配置开发…

竞赛 机器视觉的试卷批改系统 - opencv python 视觉识别

文章目录 0 简介1 项目背景2 项目目的3 系统设计3.1 目标对象3.2 系统架构3.3 软件设计方案 4 图像预处理4.1 灰度二值化4.2 形态学处理4.3 算式提取4.4 倾斜校正4.5 字符分割 5 字符识别5.1 支持向量机原理5.2 基于SVM的字符识别5.3 SVM算法实现 6 算法测试7 系统实现8 最后 0…