C++ Webserver从零开始:基础知识(八)——多线程编程

线程概述

现代Linux系统主要使用的线程库是NPTL,在Linux中可以通过

getconf GNU_LIBPTHREAD_VERSION

获取Linux线程库版本。谈到线程,就不得不提及线程模型

线程模型

本小节概念较多,采用问答式进行讲解,以帮助读者理解

什么是线程?

线程是程序中完成一个独立任务的完整执行序列,即一个可调度的实体。根据运行环境和调度者的身份,线程可分为内核线程和用户线程。

内核线程和用户线程的区别和联系?

内核线程运行在内核空间,由内核来调度。用户线程运行在用户空间,由线程库来调度。

当一个内核线程获得CPU的使用权时,它就会加载并运行一个用户线程,所以从某种角度上来说,内核线程可以看作用户线程运行的“容器”。

线程与进程的关系?

一个进程可以拥有多个线程,假设该进程的线程分为M个内核线程和N个用户线程,那么这些线程符合以下规律:

  • M <= N
  • 在一个系统的所有进程中,MN的比值固定

而且根据MN的比值(M:N),可以划分出三种线程模型,其分别是:

  • 完全在用户空间实现
  • 完全由内核调度

双层调度

三种线程模型?

  • 完全在用户空间实现:完全在用户空间实现的线程无需内核支持,内核也不知道这些用户空间的线程存在。由线程库负责所有线程的优先级,时间片调度。对于内核来说仍把整个进程当作最小单位来调度,不区分里面具体的线程。
    • 优点:创建和调度线程无需内核干预,不占据内核资源,速度快缺点:对于多处理器系统,一个进程的多个线程无法运行在不同的CPU上
  • 完全由内核调度:完全由内核调度的模式将创建,调度的任务全部交给了内核,内核空间的线程库无具体任务。
    • 优缺点:与完全在用户空间实现的优缺点正好相反
  • 双层调度:前两种实现模式的混合体,结合了前两种模式的优点,不会消耗太多的内核资源,切换线程速度较快,可以充分利用多处理器优势


创建线程和结束线程

现在我们来学习线程管理的API,Linux中他们都定义在phtread.h头文件中

pthread_create

#include<pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t* attr, void*(*start_routinue)(void*), void* arg);
  • 作用:创建一个线程
  • 参数
    • thread:线程标识符
    • attr:设置新线程的属性
    • start_routine:指定线程运行的函数
    • arg:指定线程运行的函数的参数
  • 返回值
    • 成功:0
    • 失败:错误码

pthread_exit

线程一旦创建好,内核就会调度内核线程执行start_coutine函数指针所指向的函数,执行完毕之后最好执行退出函数。

#include<pthread.h>
void pthread_exit(void* retval);

该函数能退出线程,并且执行完毕后不会返回,且不会执行失败。

pthread_join

一个进程内的线程都可以调用pthread_join来回收其他线程,类似于wait和waitpid调用

#include<pthread.h>
int pthread_join(pthread_t thread, void** retval);
  • 作用:回收其他线程
  • 参数:
    • thread:目标线程的标识符
    • retval:目标线程返回的退出信息
  • 返回值:
    • 成功:0
    • 失败:错误码

pthread_cancel

#include<pthread.h>
int pthread_cancel(pthread_t thread);

该函数的作用是异常终止线程,即取消线程。其中thread参数是目标线程标识符,成功返回0,失败返回错误码。

接收到取消请求的目标线程可以决定本线程是否允许被取消,以及如何取消。

#include<pthread.h>
int pthread_setcancelstate(int state, int* oldstate);
int pthread_setcanceltype(int type, int* oldtype);
  • 作用:决定本线程是否可以被取消以及如何被取消
  • 参数:
    • state:是否允许被取消状态 type:如何取消状态
    • oldstate:记录线程原来的是否允许被取消状态 oldtype:记录线程原来的如何取消状态
  • 返回值:
    • 成功:0
    • 失败:错误码


线程属性

pthread_attr_t结构体定义了一套完整的线程属性,如下:

#include<bits/pthreadtypes.h>
#define SIZEOF_PTHREAD_ATTR_T 36typedef union{char size[SIZEOF_PTHREAD_ATTR_T];long int align;
}pthread_attr_t;

Linux中各线程属性就定义在该字符数组中,线程库定义了一系列函数来操作pthread_attr_t类型的变量来获取和设置线程属性。

这部分内容实在太多,更多内容可以自行搜索,也可以把glibc git到本地目录查看

我在站内找到一个文章供参考线程属性pthread_attr_t简介_c语言中 pthread_attr_t-CSDN博客


POSIX信号量

就如多进程的同步问题一样,线程也必须考虑同步问题。在进程中,我们介绍的是IPC信号量,而线程中往往采用的是POSIX信号量,这两组接口的语义完全相同

#include<semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);int sem_post(sem_t *sem);
  • 作用:
    • sem_init:初始化一个未命名的信号量
    • sem_destroy:销毁信号量,释放其内核资源
    • sem_wait:函数以原子操作的方式将信号量值 - 1,若信号量值为0则阻塞
    • sem_trywait:函数以原子操作的方式将信号量-1,但是非阻塞,如果信号量为0则返回 -1
    • sem_post:函数以原子操作将信号量+1
  • 参数:
    • sem:指向被操作的信号量
    • pshared:信号量的类型
    • value:信号量的初始值
  • 返回值:
    • 成功:0
    • 失败:-1


互斥锁

互斥锁用于保护关键代码段,其操作类似二进制信号量。当进入关键代码段时,我们要获得锁并对其枷锁,类似于二进制信号量的P操作;当离开关键代码段时,我们需要对互斥锁解锁,以唤醒其他等待互斥锁的线程,等价于二进制信号量的V操作。

基础API

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_destroy(pthread_mutex_t *mutex);int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 作用:
    • pthread_mutex_init:初始化互斥锁
      • pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER:另一个初始化互斥锁的方式,整个宏会将互斥锁的各个字段都初始化为 0
    • pthread_mutex_destroy: 销毁互斥锁,释放其系统资源。若直接销毁一个加锁的互斥锁会导致不可预期的后果
    • pthread_mutex_lock:以原子操作给互斥锁枷锁,若已锁上会进入阻塞状态
    • pthread_mutex_trylock:同上,但不会进入阻塞状态,若已锁上会返回错误码EBUSY
    • pthread_mutex_unlock::以原子操作给互斥锁解锁
  • 参数:
    • mutex:指向要操作的目标互斥锁
    • mutexattr:指定互斥锁的属性
  • 返回值:
    • 成功:0
    • 失败:错误码

互斥锁属性

phtread_mutexattr_t结构体定义了一套完整的互斥锁属性,而线程库也提供了一系的函数来操作这些属性变量。一些主要函数:

#include <pthread.h>
/*初始化互斥锁*/
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
/*销毁互斥锁*/
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);/*获取和设置互斥锁的pshared属性*/
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr, int *pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);/*获取和设置互斥锁的type属性*/
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int *type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);

互斥锁有pshared和type两种常用属性

pshared:

PTHREAD_PROCESS_SHARED:互斥锁可以被跨进程gongxiang

PTHREAD_PROCESS_PRIVATE:互斥锁只能被和锁的初始化线程隶属于一个进程的线程共享

type:

PTHREAD_MUTEX_NORMAL:普通锁,当一个线程对一个普通锁加锁后会形成等待队列,并在该锁解锁后按优先级获得它。这中锁容易引发问题:当线程对一个以及枷锁的普通锁再次加锁,将引发死锁;对一个已经被其他线程加锁的普通锁解锁,或者对一个已经解锁的普通锁再枷锁,将导致不可预期的后果

PTHREAD_MUTEX_ERRORCHECK:检错锁,若一个线程对已经加锁的检错锁再次加锁,则返回EDEADLK,对一个已经被其他线程加锁的检错锁解锁,或者对一个已经解锁的检错锁再次解锁,将返回EPERM。

PHTREAD_MUTEX_RECURSIVE:嵌套锁,这种锁允许一个线程在释放锁之前对它多次加锁而不发生死锁。但其他线程要想获得该锁,必须等待前者执行相应次数的解锁操作。对一个已经被其他线程加锁的嵌套锁解锁,或者对一个已经解锁的嵌套锁再次解锁,则解锁操作返回EPERM

PHTREAD_MUTEX_DEFAULT:一般映射成上面三种锁之一,若不映射,一切不合理的操作都可能导致不可预期的后果。


条件变量

互斥锁用于同步线程之间对共享数据的访问,而条件变量则时用于在线程之间同步共享数据的值。条件变量提供一种新的线程之间通知机制:当某个共享数据达到某个值时,唤醒等待这个共享数据的线程

#include <pthread.h>int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
int pthread_cond_destroy(pthread_cond_t *cond);int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
  • 作用:
    • pthread_cond_init,初始化条件变量
    • pthread_cond_destroy,销毁条件变量,释放其占用的资源。销毁一个正在等待的条件变量将失败并返回EBUSY
    • pthread_cond_broadcast,以广播的方式唤醒所有等待目标条件变量的线程
    • pthread_cond_signal,以信号的方式唤醒所有等待目标条件变量的线程
    • pthread_cond_wait,等待目标条件变量
  • 参数:
    • cond:目标条件变量
    • attr:指定条件变量的属性
    • mutex:保护pthread_cond_wait操作的原子性
  • 返回值:
    • 成功:0
    • 失败:错误码


多线程环境

Linux中提供了上述的处理多线程编程的API,但在进行多线程编程的时候任然会出现许多问题。我们将这类问题中比较有代表性的问题汇总总结,并将其命名为多线程环境。

可重入函数:如果一个函数能被多个线程同时调用而不发生竞态条件,则我们称它是线程安全的,这个函数我们称其为可重入函数。Linux库函数中只有一小部分函数是不可重入的,比如inet_ntoa。具体不可重入库函数列表可以自行查询。Linux对很多不可重入库函数提供了可重入版本,可重入版本一般在函数名尾部加上_r。注意,在多线程程序中调用库函数,一定要用可重入版本,否则可能会导致不可预想的结果。

线程与进程:在Linux中,若一个多线程程序的某个线程调用了fork函数,那么新创建的子进程不会拥有和父进程一样数量的线程。子进程只会拥有一个执行线程的完整复制。同时,子进程会继承父进程中互斥锁的状态,这引发一个问题:子进程可能不清楚从父进程继承而来的互斥锁的具体状态。如:若互斥锁不是调用fork的那个线程锁住的,而是其他线程锁住的。那么子进程再对该互斥锁执行加锁就会导致死锁。对此,pthread提供了pthread_atfork函数来保证fork调用之后父进程和子进程都清楚锁的具体状态。该函数请读者自行搜索

线程和信号:我们知道,一个进程的所有线程共享该进程的信号,所以线程库会根据信号掩码决定将信号发给哪个具体的线程。若我们在每个线程中单独设置信号掩码,容易导致逻辑错误。又因为所有的信号共享信号处理函数,所以当一个线程设置了某个信号的处理函数后,其他所有线程的对该信号的处理函数都被修改,这并不是我们锁期望的。因此,在多线程程序中,我们通常定义一个专门的线程来处理所有的信号。

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

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

相关文章

Matlab:利用1D-CNN(一维卷积神经网络),分析高光谱曲线数据或时序数据

1DCNN 简介&#xff1a; 1D-CNN&#xff08;一维卷积神经网络&#xff09;是一种特殊类型的卷积神经网络&#xff0c;设计用于处理一维序列数据。这种网络结构通常由多个卷积层和池化层交替组成&#xff0c;最后使用全连接层将提取的特征映射到输出。 以下是1D-CNN的主要组成…

详细分析Redis中数值乱码的根本原因以及解决方式

目录 前言1. 问题所示2. 原理分析3. 拓展 前言 对于这方面的相关知识推荐阅读&#xff1a; Redis框架从入门到学精&#xff08;全&#xff09;Java关于RedisTemplate的使用分析 附代码java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#xff09; …

在(龙芯 3A6000)loongnix下编译syncthing

在loongnix下编译syncthing Syncthing&#xff08;https://syncthing.net/&#xff09; 是一个开源的 P2P 文件同步工具&#xff0c;可用于在多个设备&#xff08;包括 Android 手机&#xff09;之间同步文件。 – Ankush Das&#xff08;作者&#xff09;。因为工作资料保存需…

板块零 IDEA编译器基础:第二节 创建JAVA WEB项目与IDEA基本设置 来自【汤米尼克的JAVAEE全套教程专栏】

板块零 IDEA编译器基础&#xff1a;第二节 创建JAVA WEB项目与IDEA基本设置 一、创建JAVA WEB项目&#xff08;1&#xff09;普通项目升级成WEB项目&#xff08;2&#xff09;创建JAVA包 二、IDEA 开荒基本设置&#xff08;1&#xff09;设置字体字号自动缩放 &#xff08;2&am…

C# 根据USB设备VID和PID 获取设备总线已报告设备描述

总线已报告设备描述 DEVPKEY_Device_BusReportedDeviceDesc 模式 winform 语言 c# using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Window…

升级Oracle 单实例数据库19.3到19.22

需求 我的Oracle Database Vagrant Box初始版本为19.3&#xff0c;需要升级到最新的RU&#xff0c;当前为19.22。 以下操作时间为为2024年2月5日。 补丁下载 补丁下载文档参见MOS文档&#xff1a;Primary Note for Database Proactive Patch Program (Doc ID 888.1)。 补丁…

企业级Spring boot项目 配置清单

目录 一、服务基础配置 二、配置数据库数据源 三、配置缓存 四、配置日志 五、配置统一异常处理 六、配置swagger文档 七、配置用户登录模块 八、配置websocket 九、配置定时任务 十、配置文件服务器 十一、配置Nacos 十二、配置项目启动数据库默认初始化(liquibas…

Bootstrap5 图片轮播

Bootstrap5 轮播样式表使用的是CDN资源 <title>亚丁号</title><!-- 自定义样式表 --><link href"static/front/css/front.css" rel"stylesheet" /><!-- 新 Bootstrap5 核心 CSS 文件 --><link rel"stylesheet"…

Meta开源大模型LLaMA2的部署使用

LLaMA2的部署使用 LLaMA2申请下载下载模型启动运行Llama2模型文本补全任务实现聊天任务LLaMA2编程Web UI操作 LLaMA2 申请下载 访问meta ai申请模型下载&#xff0c;注意有地区限制&#xff0c;建议选其他国家 申请后会收到邮件&#xff0c;内含一个下载URL地址&#xff0c;…

【翻译】Processing安卓模式的安装使用及打包发布(内含中文版截图)

原文链接在下面的每一章的最前面。 原文有三篇&#xff0c;译者不知道贴哪篇了&#xff0c;这篇干脆标了原创。。 译者声明&#xff1a;本文原文来自于GNU协议支持下的项目&#xff0c;具备开源二改授权&#xff0c;可翻译后公开。 文章目录 Install&#xff08;安装&#xff0…

1041.困于环中的机器人(Java)

题目描述&#xff1a; 在无限的平面上&#xff0c;机器人最初位于 (0, 0) 处&#xff0c;面朝北方。注意: 北方向 是y轴的正方向。 南方向 是y轴的负方向。 东方向 是x轴的正方向。 西方向 是x轴的负方向。 机器人可以接受下列三条指令之一&#xff1a; “G”&#xff1a;直走 …

42、WEB攻防——通用漏洞文件包含LFIRFI伪协议编码算法代码审计

文章目录 文件包含文件包含原理攻击思路文件包含分类 sessionPHP伪协议进行文件包含 文件包含 文件包含原理 文件包含其实就是引用&#xff0c;相当于C语言中的include <stdio.h>。文件包含漏洞常出现于php脚本中&#xff0c;当include($file)中的$file变量用户可控&am…

88 docker 环境下面 前端A连到后端B + 前端B连到后端A

前言 呵呵 最近出现了这样的一个问题, 我们有多个前端服务, 分别连接了对应的后端服务, 前端A -> 后端A, 前端B -> 后端B 但是 最近的时候 却会出现一种情况就是, 有些时候 前端A 连接到了 后端B, 前端B 连接到了 后端A 我们 前端服务使用 nginx 提供前端 html, js…

嵌入式软件bug分析基本要求

摘要&#xff1a;软件从来不是一次就能完美的&#xff0c;需要以包容的眼光看待它的残缺。那问题究竟为何产生&#xff0c;如何去除呢&#xff1f; 1、软件问题从哪来 软件缺陷问题千千万万&#xff0c;主要是需求、实现、和运行环境三方面。 1.1 需求描述偏差 客户角度的描…

Autovue R21.1 发布

作者: justin.jin 2023年9月, Oracle发布了最新版的Autovue R21.1, 它包括了原来21.0.1 和 21.0.2的全部补丁. Autovue R21.1的关键更新如下: 1, 升级为64位: 性能有较大提升, 不会有32位应用单一线程最大使用2G的内存限制. 2, 紧随IT基础架构的变化, 支持Windows Se…

Excel设置单元格下拉框(poi)

前言 年关在即&#xff0c;还在最后的迭代处理&#xff0c;还分了个其他同事的单&#xff0c;说是导出的Excel模版的2列要修改为下拉选项&#xff0c;过程很曲折&#xff0c;不说&#xff0c;以下其实就是一个笔记而已&#xff01; 其实之前分享过阿里的EasyExcel设置单…

jsp页面,让alert弹出信息换行显示

第一种方式&#xff1a;后端拼接上换行符前端显示 1&#xff0c;java后端将信息封装成字符串时&#xff0c;在需要换行的地方拼接上一个换行符&#xff0c; 显示在HTML中的换行&#xff0c;通常需要用<br>标签替代\n&#xff0c;如下&#xff1a; String javaString &…

【教程】ESP32-CAM使用I2C驱动0.91寸OLED

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 先安装库 1. 打开 Arduino IDE&#xff0c;然后转到 Sketch > Include Library > Manage Libraries。 2. 在搜索框中输入“SSD1306”&#xff0c;然后从 Adafruit 安装 SSD1306 库。 3. 选择“install al…

各种编程语言送祝福:2024龙年大吉

我是码农一枚&#xff0c;在这里用不同编程语言中祝福大家"2024&#xff0c;龙年大吉"~ Python print("2024&#xff0c;龙年大吉")Java public class Main {public static void main(String[] args) {System.out.println("2024&#xff0c;龙年大…

文心一言 VS 讯飞星火 VS chatgpt (195)-- 算法导论14.3 3题

三、用go语言&#xff0c;请给出一个有效的算法&#xff0c;对一个给定的区间i&#xff0c;返回一个与i重叠且具有最小低端点的区间;或者当这样的区间不存在时返回T.nil。 文心一言&#xff0c;去掉math&#xff0c;代码正常运行&#xff1a; 在Go语言中&#xff0c;你可以使…