【Linux】线程封装_互斥

news2024/11/16 18:46:49

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:题目解析
🌎推荐文章:【LeetCode】winter vacation training

在这里插入图片描述


目录

  • 👉🏻线程封装
    • Thread.cpp
  • 👉🏻线程互斥
    • 多个线程操作共享变量带来的问题
  • 👉🏻 互斥量(mutex)的接口函数
    • pthread_mutex_init
    • pthread_mutex_lock函数
    • pthread_mutex_unlock函数
    • pthread_mutex_destory函数
    • pthread_mutex_trylock函数
    • 线程互斥访问共享变量代码示例
  • 👉🏻关于互斥的一些总结与小问题

👉🏻线程封装

Thread.cpp

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>

// 设计方的视角
//typedef std::function<void()> func_t;
template<class T>
using func_t = std::function<void(T)>;

template<class T>
class Thread
{
public:
    Thread(const std::string &threadname, func_t<T> func, T data)
    :_tid(0), _threadname(threadname), _isrunning(false), _func(func), _data(data)
    {}

    static void *ThreadRoutine(void *args) // 类内方法,
    {
        // (void)args; // 仅仅是为了防止编译器有告警
        Thread *ts = static_cast<Thread *>(args);

        ts->_func(ts->_data);

        return nullptr;
    }

    bool Start()//线程创建
    {
        int n = pthread_create(&_tid, nullptr, ThreadRoutine, this/*?*/);
        if(n == 0) 
        {
            _isrunning = true;
            return true;
        }
        else return false;
    }
    bool Join()//线程等待
    {
        if(!_isrunning) return true;
        int n = pthread_join(_tid, nullptr);
        if(n == 0)
        {
            _isrunning = false;
            return true;
        }
        return false;
    }
    std::string ThreadName()
    {
        return _threadname;
    }
    bool IsRunning()
    {
        return _isrunning;
    }
    ~Thread()
    {}
private:
    pthread_t _tid;
    std::string _threadname;
    bool _isrunning;
    func_t<T> _func;
    T _data;
};

👉🏻线程互斥

🌈线程互斥基本概念
线程互斥是指在多线程编程中,为了避免多个线程同时访问共享资源而导致数据不一致的情况,需要采取措施来保证同一时间只有一个线程可以访问共享资源。这种机制可以通过使用互(mutex)来实现。

当一个线程要访问共享资源时,它首先尝试获取互斥量的锁。如果这个锁已经被其他线程占用,那么当前线程就会被阻塞,直到锁被释放为止。一旦线程成功获取了锁,它就可以安全地访问共享资源,并在完成操作后释放锁,以便其他线程可以继续访问这个资源。

通过使用线程互斥机制,可以有效地避免多个线程之间发生竞争条件(race condition),从而确保数据的一致性和程序的正确性。

🌈以下是一些关于线程互斥相关的名词介绍:
1.临界资源:多线程执行流共享的资源就叫做临界资源
2.临界区:每个线程内部,访问临界资源的代码,就叫做临界区
3.互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
4.原子性不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

多个线程操作共享变量带来的问题

示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int ticket = 100;
void *route(void *arg)
{
char *id = (char*)arg;
while ( 1 ) {
if ( ticket > 0 ) {
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
} else {
break;
}
}
}
int main( void )
{
pthread_t t1, t2, t3, t4;
pthread_create(&t1, NULL, route, "thread 1");
pthread_create(&t2, NULL, route, "thread 2");
pthread_create(&t3, NULL, route, "thread 3");
pthread_create(&t4, NULL, route, "thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
}
//一次执行结果:
thread 4 sells ticket:100
...
thread 4 sells ticket:1
thread 2 sells ticket:0
thread 1 sells ticket:-1
thread 3 sells ticket:-2

这里对共享变量ticket进行减减的操作属于非原子操作,而非原子操作的指令与只有一条指令的原子操作不同的是,非原子操作的指令有三条:

  • load :将共享变量ticket从内存加载到寄存器中
  • update : 更新寄存器里面的值,执行-1操作
  • store :将新值,从寄存器写回共享变量ticket的内存地址
    在这里插入图片描述
    这里的ticket减到-1,主要问题是多个线程同时进行了if ( ticket > 0 ) 的判断,并进入了语句中,而数据在内存中,本质是被线程共享的,数据被读取到寄存器中,本质变成了线程的上下文,属于线程私有数据!
    所以为什么可以减减到-1,按理来说只有ticket>0的情况下才可以进入语句执行减减操作,但是因为每个线程的上下文数据都是独立的,而进行判断的数据是从CPU的寄存器中读取的,此时每个同时进来的线程在进来前存储在CPU上寄存器上的数据,也即是自己的上下文中的ticket值都是为1,也就是>0,所以才会判断合法。

那么如何解决这种线程挤占共享资源的情况呢,这里我们就要引入互斥的一些接口函数了。

👉🏻 互斥量(mutex)的接口函数

pthread_mutex_init

pthread_mutex_init 函数是 POSIX 线程库中用于初始化互斥锁(mutex)的函数。互斥锁是一种线程同步机制,用于保护临界区(critical section)代码,防止多个线程同时访问共享资源而导致的竞争条件(race condition)。调用 pthread_mutex_init 函数可以对互斥锁进行初始化,设置其属性等。

这个函数的原型如下:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
  • mutex 参数是指向要初始化的互斥锁的指针。
  • attr 参数是一个指向互斥锁属性的指针,通常可以设置为 NULL,表示使用默认的属性。

成功初始化互斥锁后,可以使用 pthread_mutex_lockpthread_mutex_unlock 来分别加锁和解锁互斥锁。

需要注意的是,在使用完互斥锁后,应该使用 pthread_mutex_destroy 函数来销毁互斥锁以释放资源。

pthread_mutex_lock函数

函数原型:

int pthread_mutex_lock(pthread_mutex_t *mutex);

参数解释:

  • mutex:指向要获取锁的互斥量的指针。互斥量是一种用于线程同步的对象,通过对互斥量加锁和解锁来控制线程对共享资源的访问。

使用方法总结:

  1. 首先,定义并初始化一个互斥量变量 pthread_mutex_t mutex;
  2. 在需要对共享资源进行保护的临界区内,使用 pthread_mutex_lock(&mutex); 来获取互斥量的锁。
  3. 在临界区内执行对共享资源的操作。
  4. 最后,使用 pthread_mutex_unlock(&mutex); 来释放互斥量的锁,允许其他线程访问共享资源

pthread_mutex_unlock函数

函数原型:

int pthread_mutex_unlock(pthread_mutex_t *mutex);

参数解释:

  • mutex:指向要释放锁的互斥量的指针。该参数是一个 pthread_mutex_t 类型的指针,表示需要释放锁的互斥量。

使用方法:

  1. 在临界区内使用 pthread_mutex_unlock(&mutex); 来释放互斥量的锁。这样做可以让其他线程获取该互斥量的锁,继续访问共享资源。
  2. 通常情况下,pthread_mutex_unlock 应该与 pthread_mutex_lock 配对使用,以确保正确的互斥访问共享资源。

pthread_mutex_destory函数

函数原型:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数解释:

  • mutex:指向要销毁的互斥量的指针。该参数是一个 pthread_mutex_t 类型的指针,表示需要销毁的互斥量。

使用方法:

  1. 在不再需要使用互斥量时,可以调用 pthread_mutex_destroy(&mutex); 来销毁互斥量。
  2. 在销毁互斥量之前,确保所有线程已经停止使用该互斥量。

pthread_mutex_trylock函数

函数原型:

int pthread_mutex_trylock(pthread_mutex_t *mutex);

参数解释:

  • mutex:指向要尝试获取锁的互斥量的指针。互斥量是一种用于线程同步的对象,通过对互斥量加锁和解锁来控制线程对共享资源的访问。

使用方法:

  1. pthread_mutex_trylock 函数尝试获取互斥量的锁,如果互斥量当前未被其他线程占用,则获取锁成功并返回 0;如果互斥量已经被其他线程占用,则立即返回一个非零值。
  2. 通过检查 pthread_mutex_trylock 的返回值来确定是否成功获取了互斥量的锁。
  3. 相较于 pthread_mutex_lock 函数,pthread_mutex_trylock 是非阻塞的,不会使线程进入等待状态,而是立即返回结果。

线程互斥访问共享变量代码示例

下面是一个简单的示例代码,演示了如何使用 pthread_mutex_init、pthread_mutex_lock、pthread_mutex_unlock 和 pthread_mutex_destroy 来实现线程互斥访问共享变量的情况:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NUM_THREADS 2
#define MAX_COUNT 10000

int shared_variable = 0;
pthread_mutex_t mutex;

void* thread_function(void* arg) {
    for (int i = 0; i < MAX_COUNT; i++) {
        pthread_mutex_lock(&mutex);
        shared_variable++;
        pthread_mutex_unlock(&mutex);
    }

    return NULL;
}

int main() {
    pthread_t threads[NUM_THREADS];

    pthread_mutex_init(&mutex, NULL);

    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_create(&threads[i], NULL, thread_function, NULL);
    }

    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }

    pthread_mutex_destroy(&mutex);

    printf("Final value of shared_variable: %d\n", shared_variable);

    return 0;
}

在这个示例中,我们定义了一个共享变量 shared_variable,并通过两个线程对其进行累加操作。在每次对共享变量进行操作之前,线程会先获取互斥量的锁,操作完成后释放锁。这样确保了只有一个线程可以访问临界资源,避免了竞争条件的发生。

当程序执行完毕后,会输出最终的 shared_variable 的值。由于两个线程对其进行递增操作,最终的结果应该是 MAX_COUNT * NUM_THREADS,即 20000。

👉🏻关于互斥的一些总结与小问题

🌕加锁:
1.我们要尽可能的给少的代码块加锁,会导致效率变慢

加锁会导致效率变慢的原因主要有两个方面:

  1. 线程阻塞和切换:当一个线程获得了锁,其他试图获取锁的线程会被阻塞,直到锁被释放。在多个线程同时竞争一个锁的情况下,会导致线程频繁地进入阻塞状态和切换上下文,这会带来较大的开销。此外,线程在阻塞和唤醒过程中的切换可能会导致缓存失效,影响程序的性能。
  1. 串行执行:在使用锁的情况下,只有一个线程可以访问临界区,其他线程需要等待锁的释放。这意味着多个线程无法并行地执行对共享资源的操作,而是被强制按顺序进行。这种串行化的执行方式会降低程序的并发性和并行度,从而影响整体的执行效率。

虽然加锁会带来一定的性能开销,但是在多线程环境下确保数据的一致性和避免竞态条件是至关重要的。因此,在设计并发程序时,需要权衡锁的使用,避免不必要的锁竞争和锁粒度过大的问题,以最大限度地提高程序的性能和并发性。


🍉线程切换
线程切换是指在多线程环境下,操作系统将 CPU 的执行权从一个线程转移到另一个线程的过程。当一个线程无法继续执行(例如被阻塞主动让出 CPU时间片用完),操作系统会进行线程切换以确保其他线程能够得到执行机会。

线程切换通常包括以下几个步骤

(1). 保存上下文:操作系统会保存当前线程的上下文信息,包括寄存器的值、程序计数器、堆栈指针等。这样做是为了在将来重新执行该线程时能够从切换前的状态继续执行。

(2). 选择新线程:操作系统会选择一个新的就绪线程,并将 CPU 的执行权分配给它。选择的方式可以基于调度算法,如先来先服务、轮转法、优先级调度等。

(3). 恢复上下文:操作系统会恢复所选线程的上下文信息,将寄存器的值、程序计数器、堆栈指针等设置为该线程切换前保存的值。

(4). 执行新线程:CPU 开始执行新线程的指令,从上一次线程切换的位置或者新线程的起始位置开始执行。

线程切换是操作系统实现并发的重要手段之一。它使得多个线程能够共享 CPU 的执行时间,实现并发执行。然而,线程切换也会带来一定的开销,包括保存和恢复上下文的开销、缓存失效等。因此,在设计高效的多线程应用程序时,需要尽量减少线程切换的次数,提高 CPU 利用率和系统性能。


2.一般加锁,都是给临界区(共享资源)加锁

3.根据互斥的定义,任何时刻,只允许一个线程申请锁成功,多个线程申请锁失败,失败的线程怎么办?
答:失败的线程会在mutex 上进行阻塞,本质就是等待

4.一个线程在访问临界资源的时候,可不可能发生线程切换?
答:当然可以;只是不能同时访问临界区,而不是不允许切换,切换也没有用,因为被上锁了,不能对临界资源进行操作


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

给 spyter/all-spark-notebook 添加scala支持

spyter/all-spark-notebook默认没有安装scala notebook&#xff0c;需要手动添加。 你可以创建一个新的 Dockerfile&#xff0c;在其中添加你需要的配置和组件。以下是一个简单的例子&#xff1a; FROM jupyter/all-spark-notebook:x86_64-ubuntu-22.04 #冒号后可以是latest&a…

Masked Generative Distillation(MGD)2022年ECCV

Masked Generative Distillation&#xff08;MGD&#xff09;2022年ECCV 摘要 **目前的蒸馏算法通常通过模仿老师的输出来提高学生的表现。本文表明&#xff0c;教师还可以通过引导学生特征恢复来提高学生的代表性。从这个角度来看&#xff0c;我们提出的掩模生成蒸馏&#x…

扩展学习|系统理解数字经济

文献来源&#xff1a;[1]肖静华,胡杨颂,吴瑶.成长品&#xff1a;数据驱动的企业与用户互动创新案例研究[J].管理世界,2020,36(03):183-205.DOI:10.19744/j.cnki.11-1235/f.2020.0041. [2]陈晓红,李杨扬,宋丽洁等.数字经济理论体系与研究展望[J].管理世界,2022,38(02):208-22413…

微信小程序跳转到其他小程序

有两种方式&#xff0c;如下&#xff1a; 一、appid跳转 wx.navigateToMiniProgram({appId: 目标小程序appid,path: 目标小程序页面路径,//不配的话默认是首页//develop开发版&#xff1b;trial体验版&#xff1b;release正式版envVersion: release, success(res) {// 打开成功…

yudao-cloud 学习笔记

前端代码 浏览器打开 https://cloud.iocoder.cn/intro/ F12 执行代码 var aaa $(".sidebar-group-items").find("a"); var ll[]; var tt[]; for(var i0;i<aaa.length;i ){ ll.push("https://doc.iocoder.cn" $(aaa[i]).attr("href&quo…

电动车窗开关中MOS管的应用解析

随着科技的不断发展&#xff0c;电动车窗系统已经成为现代汽车中不可或缺的一部分。而MOS&#xff08;金属氧化物半导体&#xff09;管的应用&#xff0c;为电动车窗开关注入了新的活力&#xff0c;极大地提高了其使用寿命和安全性。 一、MOS的优越性能 MOS管以其卓越的开关…

记录西门子:IO隔离SCL编程

在PLC变量中创建IO输入输出 在PLC类型中创建输入和输出&#xff0c;并将PLC变量的输入输出名称复制过来 创建一个FC块或者FB块 创建一个DB块 MAIN主程序中&#xff1a;

【JavaEE初阶系列】——计算机是如何工作的

目录 &#x1f388;冯诺依曼体系 ❗外存和内存的概念 ❗CPU中央处理器—人类当今科技领域巅峰之作之一 &#x1f6a9;如何衡量cpu &#x1f6a9;指令&#xff08;Instruction&#xff09; &#x1f388;操作系统&#xff08;Operating System&#xff09; &#x1f388;…

超越基础:提升你的数据采集策略与IP代理的高级应用

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

软件测试 需求

文章目录 1. 需求1.1 什么是需求1.2 为什么要有需求1.3 测试人员眼中的需求1.4 如何深入理解需求 2. 测试用例的概念2.1 什么是测试用例2.2 为什么要有测试用例 3. 软件错误&#xff08;BUG&#xff09;的概念4. 开发模型和测试模型4.1 软件的生命周期4.2 瀑布模型&#xff08;…

[SAP] ABAP注释快捷键修改

在使用ABAP编辑器的时候&#xff0c;原有的添加代码注释和取消代码注释的快捷键未生效&#xff0c;这时我们可以考虑对注释快捷键进行修改 在事务码SE38(ABAP编辑器)屏幕右下角&#xff0c;点击【Options选项】图标 在【键盘】|【命令】输入栏中输入"comment"关键字…

mac上更改vscode快捷键

以移动当前行代码为例 mac上的vscode&#xff0c;默认移动当前行代码的快捷键是⌥↑即option↑按键 现在我想改成command↑ 步骤如下 1.打开vscode-code-首选项-键盘快捷键 2.打开快捷键列表 3.输入move line&#xff0c;找到要改动的这个快捷键 当前行-右键-更改键绑定&…

LTspice(14) Noise仿真

LTspice(14) Noise仿真 好久没有更新LTspice的教程了&#xff0c;大家想了没&#xff1f; 截止目前LTspice已经更新到24.0.9。界面发生了一些变化&#xff0c;但主要功能并不受影响&#xff0c;新的版本改了UI&#xff0c;找东西更加方便了&#xff0c;界面如下图1所示。 图1…

JavaEE+springboot教学仪器设备管理系统o9b00-springmvc

本文旨在设计一款基于Java技术的教学仪器设备销售网站&#xff0c;以提高网站性能、功能完善、用户体验等方面的优势&#xff0c;解决现有教学仪器设备销售网站的问题&#xff0c;并为广大教育工作者和学生提供便捷的教学仪器设备销售渠道。本文首先介绍了Java技术的相关基础知…

checking file system on C

1、win7系统 开机检查C盘&#xff0c;虽然可以ESC取消检查&#xff0c;每次操作很麻烦&#xff0c;且没有意思 2、注册表清空BootExecute数值数据 1&#xff09;打开注册表 WinR &#xff08;快捷键&#xff09;输入“regedit”&#xff0c;回车 2&#xff09;位置HKEY_LOCAL…

怎么在运行框执行脚本和软件?

1.新建一个文件夹&#xff08;随便命名&#xff09; 2.右击此电脑点击属性 3.点击高级系统设置 4.点击环境变量 5.选中Path,点击编辑 6.点击编辑&#xff0c;粘贴你刚刚文件夹的地址 7.所有窗口全部点击确定 8.你可以把常用的软件快捷方式复制到文件夹里&#xff08;比如微…

QT画图功能

QT画图功能 每个QWidget都自带的功能&#xff0c;继承了QPainteDevice都可以使用QPainter来进行绘图。 画图需要调用paintEvent绘制事件&#xff0c;paintEvent事件时QWidget类自带的事件。 重写paintEvent事件。&#xff08;重写事件&#xff1a;如果父类有某个方法&#xff…

协程库项目—协程类模块

ucontext_t结构体、非对称协程 协程类 ucontext_t结构体 头文件中定义的四个函数&#xff08;getcontext(), setcontext(), makecontext(), swapcontext()&#xff09;和两个结构类型&#xff08;mcontext_t, ucontext_t&#xff09;在一个进程中实现用户级的线程切换。 其中…

MySQL进阶之(五)InnoDB数据存储结构之表空间

五、InnoDB数据存储结构之表空间 5.1 数据页加载的三种方式5.1.1 内存读取5.1.2 随机读取5.1.3 顺序读取 5.2 区5.2.1 为什么要有区&#xff1f;5.2.2 碎片区5.2.3 区的分类 5.3 段5.4 表空间5.4.1 独立表空间5.4.2 系统表空间 在数据页结构中提到过&#xff0c;页的上层结构中…

RN开发搬砖经验之-如何处理FlashList组件加载后调用scrollToIndex没有滚动指定位置

前言 如题&#xff0c;这里只能说是处理&#xff0c;起正向作用的临时方案&#xff0c;因为我也着实没搞懂这个BUG的具体原因&#xff0c;看github上有提相关的issuesFor long lists with different item types scrollToIndex does not work reliable&#xff0c;但看官方没有…