Linux(线程控制)

一 线程的操作

1. 创建线程:pthread_create

int pthread_create(pthread_t *thread,                 // 线程 idconst pthread_attr_t *attr,        // 线程属性设置void *(*start_routine) (void *),   // 回调函数void *arg                          // 传递给回调函数的参数);
// 返回值0成功,否则返回错误码

2. 等待线程:pthread_join

int pthread_join(pthread_t thread,  // pthread_create 的返回值 void **retval      // pthread_create 回调函数的返回值);
// 返回值成功0,否则返回错误码

和进程一样,线程执行完,也需要等待回收获取执行结果,否则类似僵尸进程。

示例:

#include <iostream>
#include <pthread.h>void* fun(void *arg)
{const char *s = static_cast<const char *>(arg);std::cout << s << std::endl;return (void *)"正常退出";
}
int main()
{pthread_t pid;pthread_create(&pid, nullptr, fun, (void *)"hello world");void *result;pthread_join(pid, &result);std::cout << static_cast<const char *>(result) << std::endl;return 0;
}

创建多线程,进程内部就有多个执行流,谁先执行不一定。

void* 可以接收任意类型参数,内置类型,自定义类型都可以。

二级指针存放回调函数的返回值(一级指针的地址)

3. 线程终止

#include <pthread.h>void pthread_exit(void *retval  // 终止后的信息);
// 哪个线程调用终止哪个线程

如果不想正常return返回,可以调用 pthread_exit() ,提前终止,携带退出信息。

4. 线程分离

#include <pthread.h>int pthread_detach(pthread_t thread  // 线程的PID);
// 让这个线程分离,此后不需要join

一般情况线程结束需要被等待,但可以自己分离出进程,但资源仍然共享,只是由系统来回收。

5. 取消线程

int pthread_cancel(pthread_t thread // 线程ID)
// 取消线程

可以主动取消一个线程,一般是由主线程来取消。

6. 封装原生API(简易)

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <functional>// 处理任务
using handler = std::function<void(void)>;class mythread
{
public:// 线程回调函数static void *fun(void *arg){mythread *mythis = static_cast<mythread *>(arg);// 处理任务mythis->_handler();return nullptr;}mythread(const std::string &name, handler ha) : _name(name), _handler(ha), _isdetach(false) {}~mythread() {}// 创建线程初始化tidbool start(){if (pthread_create(&_tid, nullptr, fun, this) != 0)return false;return true;}// 等待线程结束bool join(){if (_isdetach == true)return false;return pthread_join(_tid, nullptr) == 0;}// 分离线程bool setthread_detach(){_isdetach = true;return pthread_detach(_tid) == 0;}// 取消线程bool setthread_cancel(){return pthread_cancel(_tid) == 0;}pthread_t gettid() { return _tid; }private:std::string _name; // 线程名pthread_t _tid;    // 线程tidhandler _handler;  // 执行任务bool _isdetach;    // 分离线程
};

二 用户级线程

前章说过,Linux中的线程是通过pthread库对轻量级进程的封装,使用前必须携带 -lpthread 链接这个库。那么用户级线程是如何封装的?用户级线程包含哪些属性?下面来看看

既然是库,和标准库,第三方库一样,也要映射到共享区,pthread库也不例外。

当调用pthread_create(),pthread库会在内部维护一个用户级线程结构,并和其他相同的结构组织起来。

当获取线程的TID的时候,也就是pthread_create第一个参数,就是用户级线程维护的线程的起始地址,不是内核轻量级进程的LWP字段,所以在线程操作的时候,实际是在对pthread库操作,pthread库封装的轻量级进程,对库做操作,库帮你对轻量级进程做操作。

pthread维护的结构,包含很多字段:PID/LWP,回调函数/函数参数,void*退出信息,独立的栈,TLS线程局部存储.....等。

线程局部存储:线程也可以给自己创建独立的对象,维护在pthread结构中,但仅只支持内置类型,不支持自定义类型: __thread 类型。

独立的栈:进程里的栈由主线程使用,即没有创建线程的那个线程使用,其他的线程也是在pthread结构里独立开辟空间并维护自己的栈,也就不会起冲突了。

三 同步与互斥

如果在多执行流对同一个变量进行操作会有什么问题?

假设对一个变量执行 -- 操作,当减到0结束,那么if()判断和变量--都是操作,假设变量当前值为1,此时有多个执行流同时执行if(),if里的条件在内存中,先从内存拿到CPU,在由CPU执行,已经是2步操作了,当某个执行流对变量进行--操作,此时因某种原因被切换,比如:时间片到了,后面有优先级更高的....等,其他判断if()条件的执行流判断完成,此时if()内的语句已经有多条执行流了,但变量当前值为1,切走的线程又回来了进行--,后面的线程已经进来了,也会--,所以会有数据不一致问题,由并发访问导致数据不一致问题,称为线程安全问题。

上述根本原因是if()是由多种操作和切换等方面导致多执行流执行中有中间状态,称为非原子操作。

1. 概念:

原子性:执行流执行一段代码没有中间过程称为原子的,这段代码可能形成一行汇编语句,也可能是多行,只要没有中间状态,也就是原子的。

共享资源/临界资源:多执行流共享一个对象,对象身为共享资源,对共享资源保护的资源,称为临界资源。

临界区:临界资源的代码,称为临界区,其他称为非临界区。

2. 互斥量/互斥锁:

为了保证在多线程情况下,因为并发允许导致对共享资源修改造成的数据不一致问题,提供了很多互斥机制。

互斥:访问临界区的代码的时候,只有一个执行流能访问,其他等待,所以相对于其他线程,进入临界区的线程是原子的,由原来的并发,在临界区中变成了串行访问。

API:

#include <pthread.h>int pthread_mutex_init(pthread_mutex_t *mutex,               // 锁 const pthread_mutexattr_t *mutexattr  // 锁的属性);int pthread_mutex_trylock(pthread_mutex_t *mutex // 申请锁失败返回);int pthread_mutex_destroy(pthread_mutex_t *mutex // 释放锁);int pthread_mutex_lock(pthread_mutex_t *mutex // 对锁进行加锁);int pthread_mutex_unlock(pthread_mutex_t *mutex // 对锁进行解锁);

如果锁是局部变量,则需要进行初始化和手动销毁。

全局对象则直接初始化,不用销毁:pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER。

互斥锁特征:如果申请成功就往下执行,否则阻塞,等待后续锁被解锁被唤醒重新申请锁。

所以互斥锁加锁期间就是原子的。

互斥锁实现:

当对锁进行加锁的时候,CPU存在一个汇编指令:swap,exchange,作用是交换内存中的值和寄存器中的值,整个下来只有一行汇编语句,也就是原子的,而之前的++/--操作而是3条汇编指令。

首先,CPU会初始化寄存器里的值为0,对应上图第一行代码,执行完这时如果被切走。

第一:内存中的值没变,其他线程可以继续申请,如果申请到,内存中的值变了,切走的线程再回来,恢复自己的寄存器的里的值:0,交换内存中的值,此时假设内存中原来的值为1,因切走被其他线程交换,变为0,此时0和0交换,在进行if()判断,走else 阻塞等待。

第二:执行第二行代码被切走,此时内存中的值由初始1被交换到寄存器,变为0,线程切换保存寄存器的值,后续线程的寄存器初始化为0,和内存中被交换后的值:0,0和0交换,if()不成立,else阻塞。

第三:解锁重新交换内存中的值和寄存器的值,如果被切走,其他线程已经阻塞,新的线程可以申请,没被切走唤醒阻塞的线程继续申请锁。

示例:

#include "thread.hpp"
#include <vector>// pthread_mutex_t mymtu = PTHREAD_MUTEX_INITIALIZER;
int val = 10000;
void xx(std::string s)
{while (1){// pthread_mutex_lock(&mymtu);if (val > 0){std::cout << s << " :" << val-- << std::endl;// pthread_mutex_unlock(&mymtu);}else{// pthread_mutex_unlock(&mymtu);break;}}
}
int main()
{std::vector<mythread> v;for (int i = 0; i < 5; i++)v.emplace_back(std::to_string(i), xx);for (auto &e : v)e.start();for (auto &e : v)e.join();return 0;
}

因显示器本就是共享资源所以打印信息混乱正常,可以看到3号线程打印的val是负数,不加保护必定存在线程安全问题,所以要加锁进行保护。

3 同步/条件变量:

上面说的互斥只是让临界区只有一个线程可以访问。

同步:多个线程访问临界区有一定的顺序。

明显互斥也有顺序,但解锁的线程和阻塞的线程状态不一样,解锁的线程解锁完可以立即去申请锁,而阻塞的线程要先唤醒再去申请锁,这样就导致了一个线程解锁之后又能申请到锁,而后面的锁一直申请不到,导致的问题就是后面的线程干等,线程饥饿问题,也就是同步,但资源竞争不合理。

所以为了让资源竞争合理,又引入了一个锁,条件变量。

API:

// 全局不需要释放
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;// 初始化条件变量
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);// 唤醒一个线程
int pthread_cond_signal(pthread_cond_t *cond);// 唤醒全部线程
int pthread_cond_broadcast(pthread_cond_t *cond);// 线程挂到条件变量中
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);// 释放条件变量
int pthread_cond_destroy(pthread_cond_t *cond);

使用和互斥锁差不多,条件变量作用就是让线程竞争资源具有有合理性,如果资源不就绪就挂到条件变量里(队列里),唤醒依次从队列头部取一个,也就保证了每个线程能合理竞争到资源。

当调用pthread_cond_wait()的时候,会释放互斥锁,当唤醒的时候,会弹出一个线程,并重新申请锁。

示例:

#include <iostream>
#include <pthread.h>
#include <unistd.h>pthread_mutex_t mymtu=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t mycond=PTHREAD_COND_INITIALIZER;int val=0;
void* fun(void* arg)
{while(1){pthread_mutex_lock(&mymtu);pthread_cond_wait(&mycond,&mymtu);std::cout<<static_cast<const char*>(arg)<<std::endl;pthread_mutex_unlock(&mymtu);}return nullptr;
}
int main()
{pthread_t t1,t2,t3;pthread_create(&t1,nullptr,fun,(void*)"thread-1");pthread_create(&t2,nullptr,fun,(void*)"thread-2");pthread_create(&t3,nullptr,fun,(void*)"thread-3");while(1){std::cout<<"wake up"<<std::endl;pthread_cond_signal(&mycond);// pthread_cond_broadcast(&mycond);sleep(1);}pthread_join(t1,nullptr);pthread_join(t2,nullptr);pthread_join(t3,nullptr);return 0;
}

当加入条件变量控制线程资源的竞争,明显具有一定的顺序性,也就是同步。

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

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

相关文章

PL/SQLDeveloper中数值类型字段查询后显示为科学计数法的处理方式

PL/SQLDeveloper中数值类型字段查询后显示为科学计数法的处理方式 文章目录 PL/SQLDeveloper中数值类型字段查询后显示为科学计数法的处理方式1. 查询效果2. 处理方式3. 再次查询 1. 查询效果 2. 处理方式 3. 再次查询

centos 9/ubuntu 一次性的定时关机

方法一 # 15 表示15分钟以后自动关机 sudo shutdown -h 15方法二&#xff1a; sudo dnf install at -y # 晚上十点半关机 echo "shutdown -h now" | at 22:30 # 检查是否设置成功命令 atq [rootdemo-192 ~]# atq 1 Wed Jun 4 11:12:00 2025 a root # 取消定时计划…

Riverpod与GetX的优缺点对比

Riverpod 与 GetX 的优缺点对比 在 Flutter 开发领域,Riverpod 和 GetX 都是备受关注的状态管理与依赖注入框架,它们各有优劣,适用于不同的开发场景。以下从多个维度详细对比二者的优缺点。 一、Riverpod 的优缺点 (一)优点 架构清晰,数据流向明确:基于 Provider 模…

day 47

注意力可视化 训练模型 包含通道注意力模块和CNN模型的定义&#xff08;通道注意力的插入&#xff09; import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import ma…

《Vuejs设计与实现》第 8 章(挂载与更新)

目录 8.1 挂载子节点与属性 8.2 HTML Attributes 与 DOM Properties 8.3 设置元素属性的正确方式 8.4 处理 class 属性 8.5 卸载操作 8.6 区分 vnode 类型 8.7 事件处理优化 8.8 事件冒泡与更新时机问题 8.9 子节点的更新 8.10 文本节点和注释节点 8.11 片段&#xf…

自制操作系统(五、重写引导部分和C语言的使用)

为了实现其他更多功能&#xff0c;我决定重新写引导部分的内容 boot.asm ; boot.asm %include "config.inc"setuplen equ 4 bootseg equ 0x07c0 initseg equ def_initseg setupseg equ def_setupseg sysseg equ def_syssegsetupsector equ 2 syssector equ setupse…

口罩佩戴检测算法AI智能分析网关V4工厂/工业等多场景守护公共卫生安全

一、引言​ 在公共卫生安全日益受到重视的当下&#xff0c;口罩佩戴成为预防病毒传播、保障人员健康的重要措施。为了高效、精准地实现对人员口罩佩戴情况的监测&#xff0c;AI智能分析网关V4口罩检测方案应运而生。该方案依托先进的人工智能技术与强大的硬件性能&#xff0c;…

【评测】用Flux的图片文本修改的PS效果

【评测】Flux的图片文本修改的PS效果 1. 百度图库找一张有英文的图片 2. 打开https://playground.bfl.ai/image/edit上传图片 3. 输入提示词 “change brarfant to goodbeer” 图片的文字被修改了

【汇编逆向系列】三、函数调用包含单个参数之float类型-xmm0寄存器,sub,rep,stos,movss,mulss,addss指令

一、汇编代码 single_float_param:0000000000000060: F3 0F 11 44 24 08 movss dword ptr [rsp8],xmm00000000000000066: 57 push rdi0000000000000067: 48 83 EC 10 sub rsp,10h000000000000006B: 48 8B FC mov …

深入了解UDP套接字:构建高效网络通信

个人主页&#xff1a;chian-ocean 文章专栏-NET 深入了解UDP套接字&#xff1a;构建高效网络通信 个人主页&#xff1a;chian-ocean文章专栏-NET 前言&#xff1a;UDPUDP 特点&#xff1a;UDP的应用 套接字地址IP地址&#xff08;Internet Protocol Address&#xff09;IP地址…

C++课设:实现简易文件加密工具(凯撒密码、异或加密、Base64编码)

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 专栏介绍&#xff1a;《编程项目实战》 目录 一、初识文件加密&#xff1a;为什么需要…

Qt/C++学习系列之Excel使用记录

Qt/C学习系列之Excel使用记录 前言The process was ended forcefully.解决方式断点查语句问题 总结 前言 在项目中解析条目达50多条&#xff0c;并且都需要将对应的结果进行显示。为了将结果显示的更加清晰&#xff0c;考虑采用QTableWidget进行表格设置&#xff0c;而在使用过…

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…

并发编程实战(生产者消费者模型)

在并发编程中使用生产者和消费者模式能够解决绝大多数的并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序整体处理数据的速度。 生产者和消费者模式&#xff1a; 在线程的世界中生产者就是产生数据的线程&#xff0c;而消费者则是消费数据的线程。在多线程开…

力扣hot100---152.乘积最大子数组

给你一个整数数组 nums &#xff0c;请你找出数组中乘积最大的非空连续子数组&#xff08;该子数组中至少包含一个数字&#xff09;&#xff0c;并返回该子数组所对应的乘积。 测试用例的答案是一个 32-位 整数。 示例 1: 输入: nums [2,3,-2,4] 输出:6解释: 子数组 [2,3] 有最…

什么是DevOps智能平台的核心功能?

在数字化转型的浪潮中&#xff0c;DevOps智能平台已成为企业提升研发效能、加速产品迭代的核心工具。然而&#xff0c;许多人对“DevOps智能平台”的理解仍停留在“自动化工具链”的表层概念。今天&#xff0c;我们从一个真实场景切入&#xff1a;假设你是某互联网公司的技术负…

柯尼卡美能达Konica Minolta bizhub 205i打印机信息

基本参数 产品类型&#xff1a;激光数码复合机颜色类型&#xff1a;黑白涵盖功能&#xff1a;复印、打印、扫描最大原稿尺寸&#xff1a;A3内存容量&#xff1a;256MB供纸容量&#xff1a;标配 350 页&#xff0c;最大 1350 页介质重量&#xff1a;标准纸盒 64-157g/㎡&#xf…

虚拟机与宿主机应用通信配置指南

1. 选择虚拟机网络模式 桥接模式 (Bridged) 客户机获得独立局域网IP&#xff0c;与宿主机同网段。 客户机可直接访问宿主机IP&#xff08;如 192.168.1.x&#xff09;。 Host-Only 模式 仅宿主机与客户机之间通信&#xff0c;宿主机通常有一个虚拟网卡&#xff08;如 192.16…

网络库libhv介绍

libhv是一个类似于libevent、libev、libuv的跨平台网络库&#xff0c;提供了更易用的接口和更丰富的协议&#xff0c;用来开发TCP/UDP/SSL/HTTP/WebSocket/MQTT 客户端/服务端。源码地址&#xff1a;https://github.com/ithewei/libhv&#xff0c;最新发布版本为v1.3.3&#xf…

施耐德特价型号伺服电机VIA0703D31A1022、常见故障

⚙️ ‌一、启动类故障‌ ‌电机无法启动‌ ‌可能原因‌&#xff1a;电源未接通、制动器未释放、接线错误或控制器故障。‌解决措施‌&#xff1a; 检查电源线路及断路器状态&#xff1b;验证制动器是否打开&#xff08;带制动器型号&#xff09;&#xff1b;核对电机与控制器…