【Linux操作系统】线程控制

embedded/2024/9/23 20:26:58/

目录

  • 一、线程创建
  • 二、线程等待
  • 三、线程退出
  • 四、线程的优缺点
  • 五、多线程的创建
  • 六、C++11的多线程
  • 七、线程分离

一、线程创建

使用接口pthread_create创建新线程,头文件是pthread.h
在这里插入图片描述

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;void* newthreadrun(void* arg)
{while(true){cout << "I am new thread" << endl;sleep(1);}
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthreadrun, nullptr);while(true){cout << "I am main thread" << endl;sleep(1);}return 0;
}

主线程和新线程的pid是一样的,因为属于同一个进程;要查看线程的id,可以获取变量tid打印出来,这个是新线程的id返回给主线程;如果要像getpid一样获取自己的id,可以使用接口pthread_self
在这里插入图片描述

string Tohex(pthread_t tid)
{char id[1024];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}
void* newthreadrun(void* arg)
{int cnt = 5;while(cnt){cout << "I am new thread, cnt: "<<cnt <<" pid: "<< getpid()<<" threadid: "<<Tohex(pthread_self())<< endl;sleep(1);cnt--;}return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthreadrun, nullptr);int cnt = 10;while(cnt){cout << "I am main thread, cnt: "<< cnt <<" pid: "<<getpid()<< " tid: "<<Tohex(tid)<<" threadid: "<<Tohex(pthread_self())<<endl;sleep(1);cnt--;}return 0;
}

在这里插入图片描述
创建线程的接口的第四个参数可以传参,传入一个字符串作为新线程的名字,注意类型转换。
在这里插入图片描述
在这里插入图片描述
新线程与主线程谁先运行?不确定,由调度器决定
如果主线程先退出,那么其他的线程都要退出,因为主线程等于进程,进程退出了所有的资源要释放,而所有的线程是共享这些资源的。

把主线程的cnt改为1秒,1秒后新线程也退出
在这里插入图片描述
所以一般情况下,往往是主线程最后一个退出,线程也要wait,否则会出现资源泄漏问题。

二、线程等待

使用线程等待的接口:
在这里插入图片描述
主线程进行等待,直到其他线程结束才退出

代码:

	int n = pthread_join(tid, nullptr);cout << "主线程退出..., n = " << n << endl;sleep(4);

在这里插入图片描述
第二个参数可以获取线程的退出信息
在这里插入图片描述
在这里插入图片描述

三、线程退出

一个进程里面的资源,是所有线程可以共享的。定义一个全局变量,主线程和新线程都可以看到。

int g_val = 100;
void *newthreadrun(void *args)
{string threadname = (char *)args;while (true){printf("g_val: %d, &g_val: %p\n", g_val, &g_val);sleep(1);g_val++;}return (void *)123;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthreadrun, (void *)"thread-1");while (true){printf("g_val: %d, &g_val: %p\n", g_val, &g_val);sleep(1);}void *ret = nullptr;int n = pthread_join(tid, &ret);cout << "主线程退出..., n = " << n << " ret: " << (long long)ret << endl;sleep(4);return 0;
}

在这里插入图片描述
如果任何一个线程出现问题,比如出现了野指针,那么整个进程都会被终止

void *newthreadrun(void *args)
{string threadname = (char *)args;int cnt = 1;while (true){printf("g_val: %d, &g_val: %p\n", g_val, &g_val);sleep(1);if(cnt == 5){int* p = nullptr;*p = 10;}g_val++;cnt++;}return (void *)123;
}

在这里插入图片描述
野指针错误是异常信号,也就是说,一旦产生信号,整个进程都会被干掉。

线程退出有三种方式:return、pthread_exit、pthread_cancel
1️⃣return
返回退出码
在这里插入图片描述

2️⃣pthread_exit
在这里插入图片描述
在新线程中设置pthread_exit
在这里插入图片描述
3秒后新线程退出,主线程还在
在这里插入图片描述

3️⃣pthread_cancel
在这里插入图片描述
指定新线程3秒后退出,主线程继续运行
在这里插入图片描述
在这里插入图片描述

四、线程的优缺点

优点:

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

缺点:

  • 性能损失。一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
  • 健壮性降低。编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的
  • 缺乏访问控制。进程是访问控制的基本粒度,在一个线程中调用某些 OS 函数会对整个进程造成影响

为什么线程切换与进程切换相比需要操作系统做到工作少了很多?
在这里插入图片描述
进程运行时,CPU读取进程的上下文信息,用内部的寄存器进行读取。有一个寄存器CR3是指向页表的首地址。读取方式:用地址空间的虚拟地址通过页表映射得到物理内存的数据,然后交给CPU。

线程是进程的其中一个执行流,CPU也要做相同类似的工作,线程切换也要保存上下文数据,只是比进程少一点。进程切换,每次CPU处理某条代码都要到内存中读取,得到数据后,到下一条代码又要做同样的工作,这样效率就低了一些。而线程也要到内存中读取,但是在CPU中,有一个硬件设备叫cache,作用是可以将当前读到的代码的周围其他代码和数据也一并交给CPU,这样下次遇到时,就可以直接在CPU处理了,效率提高了一些,工作也就少了。

那为什么线程可以这样,进程不行呢?因为进程切换时CPU内部的cache的上下文数据也要跟着切换,即清空掉然后换新的一批。线程不一样是因为多个线程在一个进程内部,每个线程是一个进程的其中一个执行流,进程的所有资源(地址空间、页表、内存)都是可以给这些线程共享的,所以线程切换时,cache里面的上下文数据还在。

线程私有:

  1. 线程的硬件上下文(CPU寄存器的值)
  2. 线程的独立栈结构

线程共享:

  1. 代码和全局数据
  2. 进程文件描述符表

线程安全问题:一个线程出问题,导致其他线程也出问题,整个进程就会退出
公共函数被重入:多线程中,函数被多个线程同时进入

五、多线程的创建

用for循环5次,创建5个线程,然后打印它们的名字

const int threadnum = 5;
void* taskthread(void* args)
{char* threadname = static_cast<char*>(args);while(true){cout << threadname << endl;sleep(2);}delete []threadname;return nullptr;
}
int main()
{vector<pthread_t> threads;for(int i=0;i<threadnum;i++){char threadname[64];snprintf(threadname, sizeof(threadname), "thread-%d", i+1);pthread_t tid;pthread_create(&tid, nullptr, taskthread, threadname);threads.push_back(tid);}for(auto e:threads){pthread_join(e, nullptr);}return 0;
}

在这里插入图片描述
发现后面都变成5了,因为这些线程用的是同一个缓冲区,所以后面的线程名字把前面的覆盖了。

修改:每个线程有自己的空间
在这里插入图片描述
在这里插入图片描述

pthread_create的第四个参数不仅可以传递整型变量,还可以传对象,自定义的类型

让线程完成加法:

class Task
{
public:Task(){}void SetData(int x, int y){_x = x;_y = y;}int Excute(){return _x + _y;}~Task(){}
private:int _x;int _y;
};
class DataThread
{
public:DataThread(int x, int y, const string& threadname):_threadname(threadname){_t.SetData(x, y);}int run(){return _t.Excute();}string getName(){return _threadname;}~DataThread(){}
private:Task _t;string _threadname;
};
const int threadnum = 5;
void* taskthread(void* args)
{DataThread* td = static_cast<DataThread*>(args);string threadname = td->getName();int ret = td->run();cout << threadname<<" ret is: " <<ret<<endl;delete td;return nullptr;
}
int main()
{vector<pthread_t> threads;for(int i=0;i<threadnum;i++){char threadname[64];snprintf(threadname, sizeof(threadname), "thread-%d", i+1);DataThread* td = new DataThread(10, 20, threadname);pthread_t tid;pthread_create(&tid, nullptr, taskthread, td);threads.push_back(tid);}for(auto e:threads){pthread_join(e, nullptr);}return 0;
}

在这里插入图片描述

六、C++11的多线程

C++11的多线程是对原生线程的封装,为了语言的跨平台性

void newthread(int num)
{while (true){cout << "I am new thread-" << num << endl;sleep(1);}
}
int main()
{thread t1(newthread, 1);thread t2(newthread, 2);thread t3(newthread, 3);while (true){cout << "I am main thread-" << endl;sleep(1);}t1.join();t2.join();t3.join();return 0;
}

在这里插入图片描述

对原生线程做封装:

#ifndef __THREAD_HPP__
#define __THREAD_HPP__
#include <iostream>
#include <string>
#include <vector>
#include <pthread.h>
#include <unistd.h>
#include <functional>using namespace std;namespace yss
{template <class T>using func_t = function<void(T &)>;template <class T>class Thread{public:void Excute(){_func(_data);}Thread(func_t<T> func, const T &data, const string &threadname): _func(func), _data(data), _threadname(threadname), _stop(true){}// 注意staticstatic void *runthread(void *args) // 类成员函数,形参有this指针{Thread<T> *self = static_cast<Thread<T> *>(args);self->Excute();return nullptr;}// 创建bool Satrt(){int n = pthread_create(&_tid, nullptr, runthread, this);if (!n) // 创建成功{_stop = false;return true;}else{return false;}}// 分离void Detach(){if (!_stop){pthread_detach(_tid);}}// 等待void Join(){if (!_stop){pthread_join(_tid, nullptr);}}void Stop(){_stop = true;}string name(){return _threadname;}~Thread(){}private:pthread_t _tid;string _threadname;bool _stop;func_t<T> _func;T _data;};
}
#endif

在这里插入图片描述

七、线程分离

默认情况下,新创建的线程是 joinable 的,线程退出后,需要对其进行pthread_join 操作,否则无法释放资源,从而造成系统泄漏

如下是主线程等待新线程

void* newthread(void* args)
{int cnt = 5;while(cnt){cout << "I am new thread" << endl;sleep(1);cnt--;}return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthread, nullptr);cout << "thread wait block" << endl;pthread_join(tid, nullptr);cout << "thread wait return" << endl;return 0;
}

在这里插入图片描述

如果不要等待,必须对新线程进行分离(可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离)

接口:
在这里插入图片描述
分离的线程不需要等待,主线程可以做自己的事情

void* newthread(void* args)
{int cnt = 5;while(cnt){cout << "I am new thread" << endl;sleep(1);cnt--;}return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthread, nullptr);//分离新线程pthread_detach(tid);while(true){cout << "I am main thread" << endl;sleep(1);}// cout << "thread wait block" << endl;// pthread_join(tid, nullptr);// cout << "thread wait return" << endl;return 0;
}

在这里插入图片描述

如果让主线程5秒结束,分离的线程死循环:

void* newthread(void* args)
{while(true){cout << "I am new thread" << endl;sleep(1);}return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthread, nullptr);//分离新线程pthread_detach(tid);int cnt = 5;while(cnt--){cout << "I am main thread" << endl;sleep(1);}return 0;
}

在这里插入图片描述
线程分离了,但是资源依然是共享的,主线程先退出,那么整个进程就会结束,分离的线程也同样退出。

如果分离的线程中的某个函数有异常(比如除0错误),整个进程会直接退出,并打印报错信息

void* newthread(void* args)
{while(true){cout << "I am new thread" << endl;sleep(1);int a = 10;a /= 0;}return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, newthread, nullptr);//分离新线程pthread_detach(tid);while(true){cout << "I am new thread" << endl;sleep(1);}return 0;
}

在这里插入图片描述


http://www.ppmy.cn/embedded/108018.html

相关文章

7.统一网关-Gateway

文章目录 1.统一网关介绍2.网关开发3.predicate4.Route Predicate Factories(路由断言工厂)4.1Path 路由断言工厂4.2.Method 路由断言工厂4.3 Header 路由断言工厂4.4 Query 路由断言工厂4.5 Host 路由断言工厂4.6 After 路由断言工厂4.7 Before 路由断言工厂4.8 Between 路由断…

NISP 一级 —— 考证笔记合集

该笔记为导航目录&#xff0c;在接下来一段事件内&#xff0c;我会每天发布我关于考取该证书的相关笔记。 当更新完成后&#xff0c;此条注释会被删除。 第一章 信息安全概述 1.1 信息与信息安全1.2 信息安全威胁1.3 信息安全发展阶段与形式1.4 信息安全保障1.5 信息系统安全保…

VScode 的下载安装及常见插件 + Git的下载和安装

目录 一、VScode 的下载安装及常见插件 1、VSCode下载 2、VSCode安装 3、VSCode常见扩展插件及介绍 二、Git的下载和安装 1、Github 和 Gitee的区别 2、Git下载&#xff08;以Win为例&#xff09; 3、Git安装 一、VScode 的下载安装及常见插件 1、VSCode下载 &#x…

Spring Boot如何解决跨域问题?

1.什么是跨域&#xff1f; 跨域请求&#xff0c;就是说浏览器在执行脚本文件的ajax请求时&#xff0c;脚本文件所在的服务地址和请求的服务地址不一样。说白了就是ip、网络协议、端口都一样的时候&#xff0c;就是同一个域&#xff0c;否则就是跨域。这是由于Netscape提出一个…

(十五)SpringCloudAlibaba-Sentinel持久化到Nacos

前言 在前面我们已经将Sentinel配置的规则持久化到系统的文件中。本章节我们将Sentinel持久化到Nacos中; 传送门(Sentinel数据持久化到文件)https://blog.csdn.net/weixin_45876411/article/details/140742963 默认情况下 Sentinel 只能接收到 Nacos 推送的消息&#xff0c;但…

关于武汉高芯coin417G2红外机芯的二次开发

文章目录 前言一、外观和机芯参数二、SDK的使用1、打开相机2、回调函数中获取全局温度和图像3、关闭相机 前言 最近工作中接触了一款基于武汉高芯科技有限公司开发的红外模组,即coin417g2(测温型)9.1mm镜头.使用此模组,开发了一套红外热成像检测桌面应用程序.下面简单记录下该…

rancher搭建k8s及jenkins自动化部署

1、准备环境 角色IP用途k8s-rancher-master192.168.3.63master节点k8s-rancher-node01192.168.3.64node节点k8s-rancher-node02192.168.3.66node节点k8s-rancher-server192.168.2.33rancher-server节点注: 服务器名需要配置不同,相同服务器名不能加入node节点 在所有节点进行…

【深入解析】AI工作流中的HTTP组件:客户端与服务端执行的区别

在当今快速发展的技术环境中&#xff0c;AI工作流的设计和实现变得愈发重要。尤其是在处理HTTP组件时&#xff0c;前端执行与后端执行之间的区别&#xff0c;往往会对系统的安全性和数据的准确性产生深远的影响。今天&#xff0c;我们就来深入探讨这一话题&#xff0c;揭示前端…