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;
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

分布式光伏发电的投融资计算

分布式光伏发电项目的成功实施离不开科学、合理的投融资计算&#xff0c;为光伏项目的前期开发提供切实可行的依据。 一、分布式光伏发电项目的投资成本 分布式光伏发电项目的投资成本包括多个方面&#xff0c;主要包括光伏组件采购成本、逆变器和支架系统成本、安装和施工成本…

MyBatis 返回 Map 或 List<Map>时,时间类型数据,默认为LocalDateTime,响应给前端默认含有‘T‘字符

一、问题 MyBatis 返回 Map 或 List时&#xff0c;时间类型数据&#xff0c;默认为LocalDateTime Springboot 响应给前端的LocalDateTime&#xff0c;默认含有’T’字符&#xff0c;如何统一配置去掉 二、解决方案 1、pom.xml 增加依赖&#xff08;2024.11.6 补充&#xff…

AMD显卡低负载看视频掉驱动(chrome edge浏览器) 高负载玩游戏却稳定 解决方法——关闭MPO

问题 折磨的开始是天下苦黄狗久矣&#xff0c;为了不再被讨乞丐的显存恶心&#xff0c;一怒之下购入了AMD显卡&#xff08;20GB显存确实爽 头一天就跑了3dmark验机&#xff0c;完美通过&#xff0c;玩游戏也没毛病 但是呢这厮是一点不省心&#xff0c;玩游戏没问题&#xff0c…

记录新建wordpress站的实践踩坑:wordpress 上传源码新建站因权限问题导致无法访问、配置新站建站向导以及插件主题上传配置的解决办法

官方文档&#xff1a;How to install WordPress – Advanced Administration Handbook | Developer.WordPress.org 但是没写权限问题&#xff0c;可以下载到 wordpress官方包。 把下载的wordpresscn的包解压并上传到服务器目录下&#xff0c;但是因为是root上传导致了权限问题…

【大数据学习 | kafka】kafka的偏移量管理

1. 偏移量的概念 消费者在消费数据的时候需要将消费的记录存储到一个位置&#xff0c;防止因为消费者程序宕机而引起断点消费数据丢失问题&#xff0c;下一次可以按照相应的位置从kafka中找寻数据&#xff0c;这个消费位置记录称之为偏移量offset。 kafka0.9以前版本将偏移量信…

迪杰斯特拉算法

迪杰斯特拉算法 LeetCode 743. 网络延迟时间 https://blog.csdn.net/xiaoxi_hahaha/article/details/110257368 import sysdef dijkstra(graph, source):"""dijkstra算法:param graph: 邻接矩阵:param source: 出发点&#xff0c;源点:return:""&…

[Unity Demo]从零开始制作空洞骑士Hollow Knight第十八集补充:制作空洞骑士独有的EventSystem和InputModule

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、制作空洞骑士独有的EventSystem和InputModule总结 前言 hello大家好久没见&#xff0c;之所以隔了这么久才更新并不是因为我又放弃了这个项目&#xff0c;而…

K8S群集调度二

一、污点(Taint) 和 容忍(Tolerations) 1.1、污点(Taint) 设置在node上是对pod的一种作用 节点的亲和性&#xff0c;是Pod的一种属性&#xff08;偏好或硬性要求&#xff09;&#xff0c;它使Pod被吸引到一类特定的节点 而Taint 则相反&#xff0c;它使节点能够排斥一类特…

MySQL45讲 第十六讲 “order by”是怎么工作的?

文章目录 MySQL45讲 第十六讲 “order by”是怎么工作的&#xff1f;一、引言二、全字段排序&#xff08;一&#xff09;索引创建与执行情况分析&#xff08;二&#xff09;执行流程&#xff08;三&#xff09;查看是否使用临时文件 三、rowid 排序&#xff08;一&#xff09;参…

HTML 基础标签——结构化标签<html>、<head>、<body>

文章目录 1. <html> 标签2. <head> 标签3. <body> 标签4. <div> 标签5. <span> 标签小结 在 HTML 文档中&#xff0c;使用特定的结构标签可以有效地组织和管理网页内容。这些标签不仅有助于浏览器正确解析和渲染页面&#xff0c;还能提高网页的可…

【原创】java+ssm+mysql电费管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

浅谈QT中Tab键的切换逻辑

浅谈QT中Tab键的切换逻辑 无意中发现在输入界面中按下Tab键时&#xff0c;没有按照预想的顺序切换焦点事件&#xff0c;如下图所示 这个现象还是很有趣&#xff0c;仔细观察了下&#xff0c;默认的切换顺序是按照控件拖入顺序&#xff0c;那么知道了这个问题想要解决起来就很简…

Linux系统编程学习 NO.10——进程的概念(1)

前言 本篇文章主要了解进程的概念。 #j 冯诺依曼体系结构 什么是冯诺依曼体系结构&#xff1f; 冯诺伊曼体系结构是计算机体系结构的一种经典范式&#xff0c;由计算机科学家约翰冯诺伊曼&#xff08;John von Neumann&#xff09;提出。该体系结构在计算机设计中起到了重要…

如何查看局域网内的浏览记录?总结五种方法,按步操作!一学就会!「管理小白须知」

如何查看局域网内的浏览记录&#xff1f; 你是否也曾为如何有效监控局域网内的浏览记录而苦恼&#xff1f; 监控局域网内电脑的浏览记录是确保员工工作效率、维护网络安全以及规范上网行为的重要手段。 别担心&#xff0c;今天我们就来聊聊这个话题&#xff0c;为你揭秘五种简…

室内场景建筑构成和常见物品识别图像分割系统:完整教学

室内场景建筑构成和常见物品识别图像分割系统源码&#xff06;数据集分享 [yolov8-seg-p6&#xff06;yolov8-seg-fasternet等50全套改进创新点发刊_一键训练教程_Web前端展示] 1.研究背景与意义 项目参考ILSVRC ImageNet Large Scale Visual Recognition Challenge 项目来…

前端vue3若依框架pnpm run dev启动报错

今天前端vue3若依框架pnpm run dev启动报错信息&#xff1a; > ruoyi3.8.8 dev D:\AYunShe\2024-11-6【无锡出门证】\wuxi-exit-permit-web > vite error when starting dev server: Error: listen EACCES: permission denied 0.0.0.0:80 at Server.setupListenHand…

nacos — 动态路由

Nacos 是一个阿里巴巴开源的服务注册中心&#xff0c;广泛用于微服务架构中。它除了支持服务注册和配置管理外&#xff0c;还可以配合网关实现动态路由。动态路由能够根据配置的实时更新动态调整路由规则&#xff0c;避免应用重启&#xff0c;实现路由的灵活管理。 网关的路由…

排序 (插入/选择排序)

目录 一 . 排序概念及运用 1.1 排序的概念 1.2 排序的应用 1.3 常见的排序算法 二 . 插入排序 2.1 直接插入排序 2.1 复杂度分析 2.3 希尔排序 2.4 希尔排序时间复杂度分析 三 . 选择排序 3.1 直接选择排序 3.2 堆排序 一 . 排序概念及运用 1.1 排序的概念 排序 : 所…

Unity网络开发基础(part5.网络协议)

目录 前言 网络协议概述 OSI模型 OSI模型的规则 第一部分 物理层 数据链路层 网络层 传输层 第二部分 ​编辑 应用层 表示层 会话层 每层的职能 TCP/IP协议 TCP/IP协议的规则 TCP/IP协议每层的职能 TCP/IP协议中的重要协议 TCP协议 三次握手 四次挥手 U…

框架学习01-Spring

一、Spring框架概述 Spring是一个开源的轻量级Java开发框架&#xff0c;它的主要目的是为了简化企业级应用程序的开发。它提供了一系列的功能&#xff0c;包括控制反转&#xff08;IOC&#xff09;、注入&#xff08;DI&#xff09;、面向切面编程&#xff08;AOP&#xff09;…