<Linux> 多线程

news2024/11/16 21:41:18

文章目录

  • 线程
  • 线程互斥
    • 死锁
  • 线程同步
  • 生产者消费者模型
  • POSIX信号量
    • 基于环形队列的生产消费模型
  • 线程池

线程

线程是进程内部可以独立运行的最小单位

进程是资源分配的基本单位,线程是调度器调度的基本单位

线程在进程的地址空间内运行

进程内的大部分资源线程是共享的,但也有属于线程自己的独立资源,主要是寄存器和栈(位于共享区)

为什么引入线程?

创建新线程的工作要比创建新进程的工作少得多

线程的切换要比进程的切换所作的工作少得多

线程异常

单个线程出现崩溃会导致整个进程奔溃,因为线程是在进程内部运行的

Linux下的线程

Linux将线程视为一种特殊类型的进程,称作轻量级进程(Lightweight Process,LWP)

Linux并没有提供系统调用来对线程进行操作,对线程操作使用的是第三方库

来段代码感受一下

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

void *routine(void *args)
{
    while (true)
    {
        printf("sub process running ...  pid:%d\n", getpid());
        sleep(1);
    }
}

int main()
{
    pthread_t t;

    pthread_create(&t, nullptr, routine, nullptr);

    while (true)
    {
        printf("main process running ...  pid:%d\n", getpid());
        sleep(3);
    }

    pthread_join(t, nullptr);
    return 0;
}

运行结果如下

请添加图片描述

程序运行起来之后检测可以看到,有两个线程,其中一个pid和lwp相等,这就是主线程,下面的就是新线程

在这里插入图片描述

每个进程内部至少有一个线程(主线程)

线程互斥

多个线程在对同一份资源进行访问时,很可能会造成数据紊乱的问题,来看个例子

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

int num = 10000;

void *routine(void *args)
{

    while (true)
    {
        if (num > 0)
        {
            usleep(100);
            printf("%d\n", num);
            num--;
        }
        else
        {
            break;
        }
        usleep(1000);
    }

}

int main()
{

    const int n = 100;
    pthread_t t[n];
    for (size_t i = 0; i < n; i++)
    {
        pthread_create(t + i, nullptr, routine, nullptr);
    }

    for (size_t i = 0; i < n; i++)
    {
        pthread_join(t[i], nullptr);
    }


    return 0;
}

创建两个新线程,让这两个线程对同一个数字进行争夺,类似于抢票,没有票就退出

只看最后的几个结果

请添加图片描述

这里n==0时明显不能进入if语句,但是后面居然打印出负数
这就是多线程对于同一份资源在进行访问时造成的数据紊乱

临界区

临界区是指一段代码,当一个线程(或进程)进入这段代码并开始执行时,其他线程(或进程)不能同时进入执行该段代码的区域。这是为了确保共享资源在同一时间只能被一个线程访问,避免数据竞争和不一致的状态。

临界资源

临界资源是指在多线程或多进程环境中被共享访问的数据、对象或资源。因为多个线程或进程可能同时访问这些资源,所以需要在访问它们时确保数据的一致性和正确性。

如何对临界资源进行保护?------‘锁’

锁的使用可以确保当一个线程在访问共享资源时,其他线程无法同时访问该资源,从而保证数据的一致性和正确性。

对刚才代码稍作修改

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int num = 10000;

void *routine(void *args)
{

    while (true)
    {
        pthread_mutex_lock(&mutex);
        if (num > 0)
        {
            usleep(100);
            printf("%d\n", num);
            num--;
            pthread_mutex_unlock(&mutex);
        }
        else
        {
            pthread_mutex_unlock(&mutex);
            break;
        }
        usleep(1000);
    }
}

int main()
{

    const int n = 100;
    pthread_t t[n];
    for (size_t i = 0; i < n; i++)
    {
        pthread_create(t + i, nullptr, routine, nullptr);
    }

    for (size_t i = 0; i < n; i++)
    {
        pthread_join(t[i], nullptr);
    }
    pthread_mutex_destroy(&mutex);

    return 0;
}

这次运行结果就正常了

有了锁就能保证每次只有一个线程能够访问到临界资源

死锁

死锁是指两个或多个线程互相等待对方持有的资源而无法继续执行的情况

死锁的4个必要条件

  • 互斥:一个资源每次只能被一个执行流使用
  • 请求与保持:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
  • 循环等待:若干执行流之间形成一种头尾相接的循环等待资源的关系

避免死锁

  • 破坏死锁的四个必要条件
  • 加锁顺序一致

线程同步

在多个线程互斥的访问临界资源时,只有一个能获得临界资源,其他线程只能忙等待,什么也做不了

两个问题:

  • 存在多个线程竞争同一个临界资源的情况,每次都是其中一个线程获得临界资源,造成其他线程的饥饿问题
  • 在临界资源没有就绪的时候,线程不停的申请锁—判断临界资源事否就绪—释放锁,一直重复,不合理

如何解决?条件变量
条件变量允许一个或多个线程等待满足某些条件时继续执行。

void push(const T &val)
{
    pthread_mutex_lock(&_mtx);
    while (is_full())
    {
        pthread_cond_wait(&_full, &_mtx);
    }

    _q.push(val);
    cout << "Producer Thread [ " << pthread_self() << " ]  Produced " << val << endl;
    pthread_mutex_unlock(&_mtx);
    pthread_cond_signal(&_empty);
}

截取一段代码举例

要点

  • 等待时线程自动挂起,并且释放手中的锁,如果收到唤醒信号,再在被阻塞的位置唤醒,重新去申请锁
  • 为什么使用while循环,不用if。要避免伪唤醒,if出现伪唤醒的情况时会直接向下运行,而while循环会再次检查资源事否就绪,防止伪唤醒

生产者消费者模型

请添加图片描述

核心点

  • 生产者在阻塞队列已满时不能继续放入数据
  • 消费者在阻塞队列为空时不能继续取出数据
  • 生产者与生产者之间互斥
  • 消费者与消费者之间互斥

阻塞队列

为了代码的健壮性,使用泛型编程

字段

阻塞队列是个队列,所以封装STL的队列

阻塞队列属于临界区,对临界区访问应该似乎互斥的,所以需要一把锁来控制生产者和消费者的互斥访问

条件变量,不让临界区外的资源忙等待

队列容量

函数

构造、析构

基本的入队列和出队列

判断是否空,是否满

#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>

using namespace std;
#define BLOCK_SIZE 10

template <class T>
class BlockQueue
{
public:
    BlockQueue(size_t capacity = BLOCK_SIZE)
        : _capacity(capacity)

    {
        pthread_mutex_init(&_mtx, nullptr);
        pthread_cond_init(&_full, nullptr);
        pthread_cond_init(&_empty, nullptr);
    }

    void push(const T &val)
    {
        pthread_mutex_lock(&_mtx);
        while (is_full())
        {
            pthread_cond_wait(&_full, &_mtx);
        }

        _q.push(val);
        cout << "Producer Thread [ " << pthread_self() << " ]  Produced " << val << endl;
        pthread_mutex_unlock(&_mtx);
        pthread_cond_signal(&_empty);
    }

    void pop()
    {
        pthread_mutex_lock(&_mtx);
        while (isEmpty())
        {
            pthread_cond_wait(&_empty, &_mtx);
        }

        T s = _q.front();
        _q.pop();
        cout << "Consumer Thread [ " << pthread_self() << " ]  Consumed " << s << endl;

        pthread_mutex_unlock(&_mtx);
        pthread_cond_signal(&_full);
    }

    ~BlockQueue()
    {
        pthread_cond_destroy(&_full);
        pthread_cond_destroy(&_empty);
        pthread_mutex_destroy(&_mtx);
    }

private:
    bool isEmpty()
    {
        return _q.empty();
    }
    bool is_full()
    {
        return _capacity == _q.size();
    }

private:
    queue<T> _q;
    size_t _capacity;
    pthread_mutex_t _mtx;
    pthread_cond_t _full;
    pthread_cond_t _empty;
};

POSIX信号量

信号量的两个操作

  • p操作:申请资源
  • v操作:释放资源

基于环形队列的生产消费模型

允许生产者和消费者在同一个数据结构上进行操作

请添加图片描述

核心点

  • 生产者不能套圈消费者
  • 消费者不能超过生产者
  • 生产者与生产者之间互斥
  • 消费者与消费者之间互斥

环形队列

字段

封装vector

容量

生产者在队列的位置

消费者在队列的位置

消费信号

生产信号

消费锁

生产锁

函数

构造(这里没有实现析构,因为封装的锁和信号量各自的析构已经实现,析构时会自动调用)

入队列和出队列

#include <iostream>
#include <vector>
#include "Sem.hpp"
#include "Mutex.hpp"
#define SIZE 10

template <class T>
class RingQueue
{
public:
    RingQueue(size_t size = SIZE)
        : _size(size),
          _rq(size),
          _p_sem(size),
          _c_sem(0),
          _c_pos(0),
          _p_pos(0)
    {
    }

    void push(const T &val)
    {
        _p_sem.p();
        _p_mtx.lock();
        _rq[_p_pos++] = val;

        _p_pos %= _size;
        _p_mtx.unlock();

        _c_sem.v();
    }
    void pop(T *pv)
    {
        _c_sem.p();
        _c_mtx.lock();
        *pv = _rq[_c_pos++];
        _c_pos %= _size;
        _c_mtx.unlock();
        _p_sem.v();
    }

private:
    std::vector<T> _rq;
    size_t _size;
    size_t _c_pos;
    size_t _p_pos;
    Sem _c_sem;
    Sem _p_sem;
    Mutex _c_mtx;
    Mutex _p_mtx;
};

要点

  • 为什么是先申请资源(p操作),再加锁?申请信号量实际上一种“预定”,先买票后入座,这样可以确保每个进入临界区的线程要访问的资源已经就绪,可以提升效率。

对锁进行封装

#pragma once
#include <pthread.h>
class Mutex
{

public:
    Mutex()
    {
        pthread_mutex_init(&_mtx, nullptr);
    }

    ~Mutex()
    {
        pthread_mutex_destroy(&_mtx);
    }

    void lock()
    {
        pthread_mutex_lock(&_mtx);
    }

    void unlock()
    {
        pthread_mutex_unlock(&_mtx);
    }

private:
    pthread_mutex_t _mtx;
};

对信号量进行封装

#pragma once

#include <semaphore.h>
class Sem
{
public:
    Sem(int value)
    {
        sem_init(&_sem, 0, value);
    }

    ~Sem()
    {
        sem_destroy(&_sem);
    }

    void p()
    {
        sem_wait(&_sem);
    }

    void v()
    {
        sem_post(&_sem);
    }

private:
    sem_t _sem;
};

线程池

详情代码见:实现简易线程池

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

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

相关文章

C++ | Leetcode C++题解之第214题最短回文串

题目&#xff1a; 题解&#xff1a; class Solution { public:string shortestPalindrome(string s) {int n s.size();vector<int> fail(n, -1);for (int i 1; i < n; i) {int j fail[i - 1];while (j ! -1 && s[j 1] ! s[i]) {j fail[j];}if (s[j 1] …

SpringBoot 集成Swagger在线接口文档 接口注解

介绍 Swagger接口文档是一种自动生成、描述、调用和可视化的RESTful风格Web服务接口文档的工具。它通过一系列的规范和自动化工具&#xff0c;极大地简化了后端开发人员与前端开发人员之间的协作。 依赖 <!--swagger--> <dependency><groupId>io.springfo…

24年河南特岗教师招聘流程+报名流程

河南特岗教师报名流程如下 1.登录河南省特岗招聘网 登录河南省特岗招聘网注册账号和密码&#xff0c;账号可以是手机号或者身份证号&#xff0c;密码自己设置 2.注册登录账号 注册完账号重新登录账号&#xff0c;输入身份证号、手机号、密码、验证码 3.浏览考试须知 填写个人信…

基于惯性加权PSO优化的目标函数最小值求解matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于惯性加权PSO优化的目标函数最小值求解matlab仿真。 2.测试软件版本以及运行结果展示 MATLAB2022A版本运行 &#xff08;完整程序运行后无水印&#xff09;…

机器学习原理之 -- 最近邻算法分类:由来及原理详解

最近邻算法&#xff08;k-Nearest Neighbors&#xff0c;k-NN&#xff09;是一种简单且直观的分类算法&#xff0c;广泛应用于分类和回归问题。由于其易于理解和实现&#xff0c;k-NN在数据挖掘、模式识别和机器学习领域中占据重要地位。本文将详细介绍最近邻算法的由来、基本原…

使用 bend-ingest-kafka 将数据流实时导入到 Databend

作者&#xff1a;韩山杰 Databend Cloud 研发工程师 https://github.com/hantmac Databend是一个开源、高性能、低成本易于扩展的新一代云数据仓库。bend-ingest-kafka 是一个专为 Databend 设计的实时数据导入工具&#xff0c;它允许用户从 Apache Kafka 直接将数据流导入到 D…

【UML用户指南】-27-对体系结构建模-制品

目录 1、组成结构 2、制品的种类 2.1、部署制品 &#xff08;deployment artifact&#xff09; 2.2、工作产品制品 &#xff08;work product artifact&#xff09; 2.3、执行制品 &#xff08;execution artifact&#xff09; 3、标准元素 4、常用建模技术 4.1、对可执…

CGLib动态代理技术

基于CGLib的动态代理机制&#xff0c;ProxyFactoryy无需再像JDK动态代理那样实现一个interface&#xff0c;实际情况下可能这个interface并不存在&#xff0c;只需要实现另外一个接口MethodInterceptor即可 package com.hmdp.service.尚硅谷的代理模式3; //CGlib代理import …

UE5 03-物体碰撞检测

在你需要碰撞的物体上添加一个碰撞检测组件 碰撞预设 设置为NoCollision,这样移动过程中就不会有物理碰撞阻挡效果,只负责检测是否碰撞,比较难解释,如果学过Unity的话,可以把它理解成 Collision 为 Trigger

INFINI Console 使用介绍

上次在《INFINI Easysearch尝鲜Hands on》中我们部署了两个节点的Easysearch&#xff0c;并且也设置了Console对集群进行监控。那么今天我们再来介绍下INFINI Console的使用。 INFINI Console 仪表盘功能介绍 INFINI Console 是一个功能强大的数据管理和分析平台&#xff0c;…

conda env pip install error:No space left on device

conda 环境 pip install error&#xff1a;No space left on device 文章目录 conda 环境 pip install error&#xff1a;No space left on device现象1 实验2 分析和解决办法 现象 非root用户的服务器&#xff0c;需要安装环境&#xff0c;安装的环境超过2GB sudo pip insta…

Roboflow自动标定数据集

最近需要自己打数据集&#xff0c;记录一下用Roboflow来打标签。 https://roboflow.com/&#xff08;官网&#xff09; 进入官网先注册&#xff0c;注册完成后进入这个界面。 我先讲如果不想让数据集公开怎么办&#xff0c;因为这里每个新建的都是公开的。新账号进去应该进去…

Python | Leetcode Python题解之第214题最短回文串

题目&#xff1a; 题解&#xff1a; class Solution:def shortestPalindrome(self, s: str) -> str:n len(s)fail [-1] * nfor i in range(1, n):j fail[i - 1]while j ! -1 and s[j 1] ! s[i]:j fail[j]if s[j 1] s[i]:fail[i] j 1best -1for i in range(n - 1,…

LIS2DH12

LIS2DH12 是属于“nano”系列的超低功耗高性能 3 轴线性加速度计&#xff0c;具有数字 I 2C、SPI 串行接口标准输出。 器件具有超低功耗工作模式&#xff0c;可实现高级节能、智能睡眠唤醒以及恢复睡眠功能。 LIS2DH12 具有2g/4g/8g/16g 的动态用户可选满量程&#xff0c;并能通…

Adobe Acrobat添加时间戳服务器

文章目录 前言一、Adobe Acrobat添加时间戳服务器1.打开Adobe Acrobat软件2.点击【菜单】→ 【首选项】3.点击【安全性】→【更多】4.点击【新建】5.输入【名称】→【服务器URL】 前言 一、Adobe Acrobat添加时间戳服务器 1.打开Adobe Acrobat软件 2.点击【菜单】→ 【首选项…

汽车电子零部件(15):车载TFT LCD显示模组

前言: 车载显示越来越受到重视,屏的使用越来越大且多,车载显示屏模组技术也在快速发展。 在复杂的显示技术世界中,薄膜晶体管(TFT,Thin Film Transistor)液晶显示器(LCD,Liquid Crystal Display)模块的制造证明了现代工程的奇迹。 TFT显示器是全彩色LCD,提供明亮、生…

WPF 3D绘图 点云 系列五

基本概念:点云是某个坐标系下的点的数据集。 可能包含丰富的信息,包括三维坐标X,Y,Z、颜色、分类值、强度值、时间等等 点云可以将现实世界原子化,通过高精度的点云数据可以还原现实世界。万物皆点云。 通过三维激光扫描仪进行数据采集获取点云数据,其次通过二维影像进行…

新手教学系列——使用uWSGI对Flask应用提速

在构建和部署Flask应用时,性能和稳定性是两个关键的因素。为了提升Flask应用的性能,我们可以借助uWSGI这个强大的工具。本文将详细介绍为什么要使用uWSGI、uWSGI的底层原理,并提供一个实例配置,帮助你更好地理解和应用这个工具。 为什么要使用uWSGI uWSGI 是一个应用服务…

docker部署简单的Kafka

文章目录 1. 拉取镜像2. 运行创建网络运行 ZooKeeper 容器运行 Kafka 容器 3. 简单的校验1. 检查容器状态2. 检查 ZooKeeper 日志3. 检查 Kafka 日志4. 使用 Kafka 命令行工具检查5. 创建和删除测试主题 1. 拉取镜像 选择一组兼容性好的版本。 docker pull bitnami/kafka:3.6…

【C++】类和对象(中)--上篇

个人主页~ 类和对象上 类和对象 一、类的六个默认成员函数二、构造函数1、构造函数基本概念2、构造函数的特性 三、析构函数1、析构函数的概念2、特性 四、拷贝构造函数1、拷贝构造函数的概念2、特征 一、类的六个默认成员函数 如果有个类中什么成员都没有&#xff0c;那么被称…