Linux 线程控制

一. 线程互斥

1.1 线程互斥相关概念

  • 临界资源:多线程执行流共享的资源就叫做临界资源。
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区。
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
  • 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

1.2 多线程的互斥问题

大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。多个线程并发的操作共享变量,会带来一些问题。

#include <cstdio>
#include <cstdint>
#include <pthread.h>
#include <unistd.h>// 共享资源
int tickets = 10;// 线程函数
void* sell_ticket(void* arg) {int sold = 0;while (true) {// 模拟售票过程if (tickets > 0) {printf("Thread %ld sold ticket %d, remaining tickets: %d\n", (long)arg, sold, tickets);tickets--;sold++;usleep(1000);}}pthread_exit(NULL);
}int main() {pthread_t thread[5];// 创建售票线程for(uint64_t i = 0; i < 5; i++){pthread_create(&thread[i], NULL, sell_ticket, (void *)i);}// 等待线程结束for(uint64_t i = 0; i < 5; i++){pthread_join(thread[i], NULL);}printf("All tickets sold out.\n");return 0;
}

在这里插入图片描述

为什么可能无法获得正确结果?

  1. –ticket操作本身就不是一个原子操作
  2. if语句判断条件为真以后,代码可以并发的切换到其他线程

要解决该问题, 需要做到以下三点:

  1. 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
  2. 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
  3. 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

本质上就是需要一把锁, 而Linux上提供的是互斥量!
在这里插入图片描述

1.3 互斥量

1.3.1 初始化互斥量

静态分配:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

动态分配:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

参数说明:

  1. pthread_mutex_t *restrict mutex:指向要初始化的互斥锁的指针。

  2. const pthread_mutexattr_t *restrict attr:指向互斥锁属性对象的指针。这个属性对象可以用来设置互斥锁的一些特殊属性,如类型、协议和优先级继承等。如果不需要特殊的属性设置,可以传入 NULL,使用默认属性。

返回值:

  1. 0:表示成功。
  2. 非零值:表示失败。常见的错误码包括:
    EINVAL:attr 指定的属性无效。
    ENOMEM:内存不足,无法初始化互斥锁。
1.3.2 销毁互斥量
int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 参数说明:
    1. pthread_mutex_t *mutex:指向要销毁的互斥锁的指针。
  • 返回值:
    1. 0:表示成功。
    2. 非零值:表示失败。常见的错误码包括:
      EINVAL:指定的互斥锁无效。
      EBUSY:互斥锁当前被锁定,无法销毁。
1.3.3 互斥量加锁和解锁

加锁:

int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 参数说明:
    1. pthread_mutex_t *mutex:指向要锁定的互斥锁的指针。
  • 返回值:
    1. 0:表示成功。
    2. 非零值:表示失败。常见的错误码包括:
      EINVAL:指定的互斥锁无效。
      EDEADLK:当前线程已经锁定了该互斥锁,导致死锁。

解锁:

int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 参数说明:
    1. pthread_mutex_t *mutex:指向要解锁的互斥锁的指针。
  • 返回值:
    1. 0:表示成功。
    2. 非零值:表示失败。常见的错误码包括:
      EINVAL:指定的互斥锁无效。
      EPERM:当前线程没有锁定该互斥锁。
1.3.4 线程安全的售票系统
#include <cstdio>
#include <cstdint>
#include <pthread.h>
#include <unistd.h>// 共享资源
int tickets = 10;
pthread_mutex_t mutex;// 线程函数
void* sell_ticket(void* arg) {int sold = 0;while (true) {pthread_mutex_lock(&mutex); // 锁定互斥锁if (tickets > 0) {tickets--;sold++;printf("Thread %ld sold ticket %d, remaining tickets: %d\n", (long)arg, sold, tickets);} else {pthread_mutex_unlock(&mutex); // 解锁互斥锁break;}pthread_mutex_unlock(&mutex); // 解锁互斥锁usleep(1000); // 模拟耗时操作}pthread_exit(NULL);
}int main() {pthread_t thread[5];// 初始化互斥锁pthread_mutex_init(&mutex, NULL);// 创建售票线程for(uint64_t i = 0; i < 5; i++) {pthread_create(&thread[i], NULL, sell_ticket, (void *)i);}// 等待线程结束for(uint64_t i = 0; i < 5; i++) {pthread_join(thread[i], NULL);}// 销毁互斥锁pthread_mutex_destroy(&mutex);printf("All tickets sold out.\n");return 0;
}

在这里插入图片描述

1.3.4 互斥量的实现原理

为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令, 该指令的作用是把寄存器和内存单元的数据相交换, 由于只有一条指令, 保证了原子性, 即使是多处理器平台, 访问内存的总线周期也有先后, 一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。

在这里插入图片描述

1.4 线程安全和可重入函数

  • 线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。

  • 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

1.4.1 可重入与线程安全的联系
  • 函数是可重入的,那就是线程安全的。
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题。
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。
1.4.2 可重入与线程安全的区别
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
  • 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。

二. 线程同步

2.1 线程同步相关概念

  • 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题。
  • 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。

2.2 条件变量

2.2.1 初始化条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
  • 参数说明:
    1. pthread_cond_t *restrict cond:指向要初始化的条件变量的指针。restrict 关键字表示该指针是唯一的,不会与其他指针别名绑定,这有助于编译器优化。
    2. const pthread_condattr_t *restrict attr:指向条件变量属性对象的指针。这个属性对象可以用来设置条件变量的一些特殊属性,如时钟选择等。如果不需要特殊的属性设置,可以传入 NULL,使用默认属性。
  • 返回值:
    1. 0:表示成功。
    2. 非零值:表示失败。常见的错误码包括:
      EINVAL:指定的条件变量或属性无效。
      ENOMEM:内存不足,无法初始化条件变量。
2.2.2 销毁条件变量
int pthread_condattr_destroy(pthread_condattr_t *attr);
  • 参数说明:
    1. pthread_condattr_t *attr:指向要销毁的条件变量属性对象的指针。
  • 返回值:
    1. 0:表示成功。
    2. 非零值:表示失败。常见的错误码包括:
      EINVAL:指定的属性对象无效。
2.2.3 等待条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
  • 参数说明
    1. pthread_cond_t *restrict cond:指向要等待的条件变量的指针。restrict 关键字表示该指针是唯一的,不会与其他指针别名绑定,这有助于编译器优化。
    2. pthread_mutex_t *restrict mutex:指向与条件变量关联的互斥锁的指针。这个互斥锁必须在调用 pthread_cond_wait() 之前已经被当前线程锁定。
  • 返回值
    1. 0:表示成功。
    2. 非零值:表示失败。常见的错误码包括:
      EINVAL:指定的条件变量或互斥锁无效。
      EPERM:当前线程没有锁定指定的互斥锁。
2.2.4 唤醒条件变量

单个唤醒:

int pthread_cond_signal(pthread_cond_t *cond);
  • 参数说明:
    1. pthread_cond_t *cond:指向要发送信号的条件变量的指针。
  • 返回值:
    1. 0:表示成功。
    2. 非零值:表示失败。常见的错误码包括:
      EINVAL:指定的条件变量无效。

广播唤醒:

int pthread_cond_broadcast(pthread_cond_t *cond);
  • 参数说明:
    1. pthread_cond_t *cond:指向要发送广播信号的条件变量的指针。
  • 返回值
    1. 0:表示成功。
    2. 非零值:表示失败。常见的错误码包括:
      EINVAL:指定的条件变量无效。

三. 生产者消费者模型

3.1 生产者消费者模型的概念

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

在这里插入图片描述

3.2 生产者消费者模型优点

  • 解耦生产者和消费者
  • 支持并发运行
  • 支持忙闲不均

3.3 基于BlockingQueue的生产者消费者模型

  • 在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构:
    1. 当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素。
    2. 当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出。
      在这里插入图片描述
#include <iostream>
#include <pthread.h>
#include <vector>
#include <unistd.h>
#include <cstdlib>
#include <ctime>const int producer_size = 2;
const int consume_size = 2;
const int queue_size = 8;
const int task_size = 6;
const int push_rand_time = 100000;
const int push_stand_time = 100000;
const int pop_rand_time = 100000;
const int pop_stand_time = 100000;class BlockQueue {
private:std::vector<int> queue;int capacity;pthread_mutex_t mutex;pthread_cond_t not_empty;pthread_cond_t not_full;public:BlockQueue(int cap) : capacity(cap), queue(0) {pthread_mutex_init(&mutex, NULL);pthread_cond_init(&not_empty, NULL);pthread_cond_init(&not_full, NULL);}~BlockQueue() {pthread_mutex_destroy(&mutex);pthread_cond_destroy(&not_empty);pthread_cond_destroy(&not_full);}void push(int item) {pthread_mutex_lock(&mutex);while (queue.size() == capacity) {pthread_cond_wait(&not_full, &mutex);}queue.push_back(item);std::cout << "Producer " << pthread_self() % INT16_MAX << " push data: " << item << std::endl;usleep(rand() % push_rand_time + push_stand_time); // 模拟随机耗时操作if (queue.size() == capacity) {pthread_cond_broadcast(&not_empty); // 队列满时,唤醒所有消费者}else{pthread_cond_signal(&not_empty); // 只有一个元素时,唤醒一个消费者}pthread_mutex_unlock(&mutex);}int pop() {pthread_mutex_lock(&mutex);while (queue.empty()) {pthread_cond_wait(&not_empty, &mutex);}int item = queue.front();queue.erase(queue.begin());std::cout << "Consumer " << pthread_self() % INT16_MAX << " get data: " << item << std::endl;usleep(rand() % pop_rand_time + pop_stand_time); // 模拟随机耗时操作if (queue.size() == 0) {pthread_cond_broadcast(&not_full); // 队列空时,唤醒所有生产者}else {pthread_cond_signal(&not_full); // 队列接近满时,唤醒一个生产者}pthread_mutex_unlock(&mutex);return item;}
};void* producer(void* arg) {BlockQueue* queue = static_cast<BlockQueue*>(arg);for (int i = 0; i < task_size; ++i) {queue->push(i);}pthread_exit(NULL);
}void* consumer(void* arg) {BlockQueue* queue = static_cast<BlockQueue*>(arg);for (int i = 0; i < task_size; ++i) {int item = queue->pop();}pthread_exit(NULL);
}int main() {srand(time(NULL)); // 初始化随机数生成器pthread_t producer_thread[producer_size], consumer_thread[consume_size];BlockQueue queue(queue_size);// 创建生产者和消费者线程for (int i = 0; i < producer_size; i++) {pthread_create(&producer_thread[i], NULL, producer, &queue);}for (int i = 0; i < consume_size; i++) {pthread_create(&consumer_thread[i], NULL, consumer, &queue);}// 等待线程结束for (int i = 0; i < producer_size; i++) {pthread_join(producer_thread[i], NULL);}for (int i = 0; i < consume_size; i++) {pthread_join(consumer_thread[i], NULL);}std::cout << "All tasks completed." << std::endl;return 0;
}

在这里插入图片描述
————————————————————
感谢大家观看,不妨点赞支持一下吧喵~
如有错误,随时纠正喵~

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

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

相关文章

Centos安装ZooKeeper教程(单机版)

本章教程介绍,如何在Centos7中,安装ZooKeeper 3.9.3版本。 一、什么是ZooKeeper ? Apache ZooKeeper 是一个分布式协调服务,用于大型分布式系统中的管理和协调。它为分布式应用提供了一个高性能的通信框架,简化了开发人员在构建复杂分布式系统的任务。ZooKeeper 能够解决一…

企业CRM管理系统PHP源码/PHP客户关系CRM客户管理系统源码

系统功能实现 1、 公海管理:公海类型、客户公海。 2、 线索管理:我的线索、线索列表、线索状态、线索来源。 3、 客户管理:我的客户、客户列表、成交客户、行业类别、预查、地区列表、客户状态、客户级别。 4、 业绩订单:订单列表、我的订单。 5、 系统设置:系统设置…

设置JAVA以适配华为2288HV2服务器的KVM控制台

华为2288HV2服务器比较老旧了&#xff0c;其管理控制台登录java配置比较麻烦&#xff0c;华为的ibmc_kvm_client_windows客户端测试了几个版本&#xff0c;连接控制台也有问题&#xff0c;最终安装JDK解决。 一、测试环境 主机为WindowsServer2012R2,64位系统 二、Java软件包…

10大软件使用感受分享,数据恢复的得力助手!!

在找数据恢复软件&#xff1f;&#xff01;是不是存在误删重要文件或遭遇硬盘故障&#xff0c;想要找回丢失的数据&#xff1f;别担心&#xff0c;今天我就来给大家分享10款我亲自使用过的数据恢复软件&#xff0c;分别给你说说它们各自的优缺点&#xff0c;希望能帮你们在数据…

一周模电速成(3) 超详细!入门小白速成!!!

目录 稳压二极管 整流二极管 晶体三极管 三极管结构图 三极管的特点 1、如何让它工作在放大状态呢 2、如何工作在截止状态呢&#xff1f; 3、如何让三极管工作在饱和状态呢&#xff1f; 在电路中要如何实现呢&#xff1f;工作在各个状态有什么特点呢&#xff1f; 截止…

Python的条件语句if与match...case

一、定义 条件语句&#xff0c;也叫作选择语句、判断语句。根绝特定条件判断是否成立&#xff0c;执行不同的语句段。简单来说&#xff0c;满足条件执行&#xff0c;不满足不执行。 条件语句是使用关键字 if 做判断&#xff0c;根据不同情况结合不同的关键字else 或者 elif来…

SpringBoot基础系列学习(二):日志

文章目录 一丶日志控制台介绍二丶日志的用法三丶日志级别四丶配置文件参数及介绍五丶slf4j 一丶日志控制台介绍 只要引用了spring-boot-starter依赖,就无需引入日志依赖,里面自带了logging依赖,默认情况下,springBoot使用Logback来记录日志,并用INFO级别输出到控制台 二丶日…

Bert模型介绍

简介 BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;是一个基于Transformer的双向编码器表示模型&#xff0c;它通过预训练学习到了丰富的语言表示&#xff0c;并可以用于各种自然语言处理任务。 模型结构&#xff1a;BERT基于Transf…

AI驱动无人驾驶:安全与效率能否兼得?

内容概要 如今&#xff0c;人工智能正以其神奇的魔力驱动着无人驾驶的浪潮&#xff0c;带来了无数令人兴奋的可能性。这一领域的最新动态显示&#xff0c;AI技术在车辆的决策过程和实时数据分析中发挥着重要作用&#xff0c;帮助车辆更聪明地应对复杂的交通环境。通过实时监测…

Windows、Linux系统上进行CPU和内存压力测试

CPU和内存压力测试 1. Linux环境 Linux环境下&#xff0c;我们可以用 stress 工具进行内存、CPU等的压力测试。 【1】. stress工具说明 [kalamikysrv1 ~]$ stress --help stress imposes certain types of compute stress on your systemUsage: stress [OPTION [ARG]] ...-…

从零开始的c++之旅——多态

1. 多态的概念 通俗来说就是多种形态。 多态分为编译时多态&#xff08;静态多态&#xff09;和运行时多态&#xff08;动态多态&#xff09;。 编译时多态主要就是我们之前提过的函数重载和函数模板&#xff0c;同名提高传不同的参数就可以调 用不同的函数&#xff0c…

linux node vue3 部署手册

第一步&#xff1a;在linux 系统中安装node 1、在网址&#xff1a;https://nodejs.org/dist/ 下载对应版本的安装包。 2、解压缩下载的压缩包到任意位置&#xff0c;推荐home下。 样例路径为&#xff1a;/home/syl/node-v20.17.0-linux-x64.tar.xz 样例&#xff1a; tar -xv…

探索C/C++的奥秘之string类

string叫串&#xff0c;是一个管理字符数组的类&#xff0c;其实就是一个字符数组的顺序表&#xff0c;通过成员函数对字符串进行增、删、查、改。 C标准库里面的东西都在std这个命名空间中。 int main() { string s1; std:: string s2; std::string name("x…

【刷题】优选算法

优选算法 双指针 202. 快乐数 链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 【思路】 第一个实例是快乐数&#xff0c;因为会变为1且不断是1的循环 第二个实例不可能为1&#xff0c;因为会陷入一个没有1的循环 根据两个实例和鸽巢原理可以发现不断的平方和最…

openEuler的aarch64操作系统上安装k3s

1、需要安装docker容器引擎&#xff08;省略&#xff09; 2、安装ks3命令 curl -sfL https://rancher-mirror.rancher.cn/k3s/k3s-install.sh | INSTALL_K3S_MIRRORcn INSTALL_K3S_SKIP_SELINUX_RPMtrue INSTALL_K3S_SELINUX_WARNtrue sh -s -- --docker 其中&#xff1a…

Synchronized锁、锁的四种状态、锁的升级(偏向锁,轻量级锁,重量级锁)

目录 1. Synchronized锁 1.1 介绍 1.2 三种应用方式★ 1.2.1 synchronized同步方法 1.2.2 synchronized 同步静态方法 1.2.3 synchronized 同步代码块 1.3 Synchronized锁底层原理 1.3.1 简答 1.3.2 详述 1. Monitor对象 2. Monitor与对象锁关联时 具体的流程&#…

【网络】数据链路层

目录 以太网 以太网的帧格式 MSS 交换机 MTU对UDP的影响 ARP协议 数据链路层是软件层的最底层协议&#xff0c;它的下面就是物理层&#xff0c;那么下面我们就来介绍一下它负责在网络通信中完成什么工作 我们前面说的IP协议是解决如何进行跨网络转发的&#xff0c;也就是…

零基础‘自外网到内网’渗透过程详细记录(cc123靶场)——下

细节较多&#xff0c;篇幅较大&#xff0c;分为上/下两部分发布在两篇文章内 另一部分详见下面文章 零基础‘自外网到内网’渗透过程详细记录(cc123靶场)——上https://blog.csdn.net/weixin_62808713/article/details/143572185 八、第二层数据库服务器权限获取 猜到新闻资…

13-鸿蒙开发中的综合实战:华为登录界面

大家好&#xff0c;欢迎来到鸿蒙开发系列教程&#xff01;今天&#xff0c;我们将通过一个综合实战项目来实现一个华为登录界面。这个项目将涵盖输入框组件、按钮组件、文本组件和布局容器的使用&#xff0c;帮助你更好地理解和应用这些组件。无论你是初学者还是有一定经验的开…

告别复杂协作:Adobe XD的简化替代方案

Adobe XD是一款集成UI/UX设计和原型创建功能的设计平台。它允许用户进行网页、移动应用的设计&#xff0c;以及原型的绘制&#xff0c;并且能够将静态设计转化为动态的交互原型。尽管Adobe XD提供了这些功能&#xff0c;但它依赖于第三方插件&#xff0c;且插件库有限&#xff…