【Linux】线程封装_互斥

在这里插入图片描述

欢迎来到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.mfbz.cn/a/449014.html

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

相关文章

骨传导耳机哪个牌子好?五大热门品质机型盘点,体验超赞!

在蓝牙耳机中&#xff0c;骨传导耳机凭借不入耳佩戴更健康等优点&#xff0c;迅速成为当下的热门款式&#xff0c;但随着骨传导耳机市场的品牌日渐增多&#xff0c;以及市场上不专业的骨传导耳机泛滥成灾的问题&#xff0c;有很多消费者在选择骨传导耳机的时候&#xff0c;都出…

【算法】Hash存储——开放寻址法

模拟散列表 维护一个集合&#xff0c;支持如下几种操作&#xff1a; I x&#xff0c;插入一个整数 x&#xff1b; Q x&#xff0c;询问整数 x是否在集合中出现过&#xff1b; 现在要进行 N次操作&#xff0c;对于每个询问操作输出对应的结果。 输入格式 第一行包含整数 N&am…

代码随想录 贪心算法-中等题目-序列问题

376.摆动序列 376. 摆动序列 中等 如果连续数字之间的差严格地在正数和负数之间交替&#xff0c;则数字序列称为 摆动序列 。第一个差&#xff08;如果存在的话&#xff09;可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。 例如&#xff0c; [1, 7…

spring boot 访问 static public 目录下的静态资源报404解决办法

1.前提是你没有修改spring boot 默认拦截路径&#xff0c;跟默认访问资源的目录。 在idea 设置中 把 compiler 下的 Buid project automatically 勾选上

开源的java视频处理库介绍

本文将为您详细讲解 Java 开源的视频处理库&#xff0c;以及它们的特点、区别和应用场景。Java 社区提供了多种视频处理库&#xff0c;这些库可以帮助您在 Java 应用程序中实现视频的录制、编辑、转换和播放等功能。 1. JCodec 特点 - 基于 Java 的视频编解码库。 - 支…

ChatGpt只能看,但无法发送消息的解决办法

这几天发现chatgpt没法发送消息了,我以为是网络问题,又过了几天还是不能发,我以为是梯子的问题,可给我急坏了,于是我用无痕模式发现可以访问额. 但是无痕模式毕竟不是长久之计,于是找到了一个方法 1.首先把电脑缓存全清除了 第一种方法: 快捷键是 : ctrlshiftdel (这会吧浏览…

分支需求管理方式

此文为上一篇文章的后续 我们来回顾一下&#xff0c;现在&#xff0c;你的小组负责的系统&#xff0c;有主干分支&#xff0c;每次新的需求&#xff0c;你都从主干(formal)拉取分支(dev-日期-需求名)进行修改&#xff0c;自测通过后&#xff0c;合并至测试分支(test)进行提测&a…

Python高级二

一、异常 1、定义 异常是在程序执行过程中出现的错误或意外情况。当程序遇到异常时&#xff0c;它会中断当前的执行流程&#xff0c;并尝试找到相应的异常处理机制来解决问题。 2、常见异常类型 SyntaxError&#xff1a;语法错误&#xff0c;通常是代码书写不符合Python语法规则…

VSCode单机活动栏图标无法收起

如果活动栏为展开状态&#xff0c;单击活动栏图标可以正常收起&#xff0c;但无法通过再次单击打开&#xff0c;解决方案如下&#xff1a; 设置->工作台->外观&#xff1a; Activity Bar:Icon Click Behavior: 切换为默认的toggle

C++ 作业 24/3/11

1、提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数&#xff08;要求使用C风格字符串完成&#xff09; #include <iostream>using namespace std;int main() {string str;cout << "please enter str:&…

【Flutter 面试题】如何理解Flutter中的Widget、State、Context ,他们是为了解决什么问题?

【Flutter 面试题】如何理解Flutter中的Widget、State、Context &#xff0c;他们是为了解决什么问题&#xff1f; 文章目录 写在前面解答补充说明完整代码示例运行结果如下详细说明 写在前面 &#x1f64b; 关于我 &#xff0c;小雨青年 &#x1f449; CSDN博客专家&#xff…

svg简单教程

推荐查看这个视频 一小时讲完SVG 简介 scalable 英 /ˈskeɪləbl/ 美 /ˈskeɪləbl/ adj. &#xff08;计算机&#xff09; 可扩展的&#xff1b;可改变大小的&#xff0c;可缩放的&#xff1b;可攀登的&#xff1b;可称量的&#xff1b;可去鳞的 vector 英 /ˈvektə/ 美…

CTP-API开发系列之五:SimNow环境介绍

CTP-API开发系列之五&#xff1a;SimNow环境介绍 CTP-API开发系列之五&#xff1a;SimNow环境介绍SimNow模拟测试环境第一套第二套登录关键字段可视化终端常见问题 CTP-API开发系列之五&#xff1a;SimNow环境介绍 如果你要研发一套国内期货程序化交易系统&#xff0c;从模拟测…

Valid8Proxy:一款功能强大的工作代理获取、验证和存储工具

关于Valid8Proxy Valid8Proxy是一款功能强大且用户友好的代理管理工具&#xff0c;该工具功能丰富&#xff0c;旨在帮助广大研究人员获取、验证和存储工作代理的相关信息。 无论你是需要用于网络资源爬取、网络数字匿名化还是测试网络安全的代理&#xff0c;Valid8Proxy都可以…

如何利用AWS CloudFront 自定义设置SSL

Amazon CloudFront 提供三种选项&#xff0c;可以加速整个网站并从 CloudFront 的边缘站点通过安全的 HTTPS 方式交付内容。除能够安全地从边缘站点交付内容外&#xff0c;您还可以配置 CDN 来使用针对源提取的 HTTPS 连接&#xff0c;这样您的数据就会实现从源到最终用户的端到…

OpenHarmony教程—语言基础类库

介绍 本示例集合语言基础类库的各个子模块&#xff0c;展示了各个模块的基础功能&#xff0c;包含&#xff1a; ohos.buffer (Buffer)ohos.convertxml (xml转换JavaScript)ohos.process (获取进程相关的信息)ohos.taskpool (启动任务池)ohos.uri (URI字符串解析)ohos.url (UR…

3、设计模式之工厂模式

工厂模式是什么&#xff1f;     工厂模式是一种创建者模式&#xff0c;用于封装和管理对象的创建&#xff0c;屏蔽了大量的创建细节&#xff0c;根据抽象程度不同&#xff0c;主要分为简单工厂模式、工厂方法模式以及抽象工厂模式。 简单工厂模式 看一个具体的需求 看一个…

计算机网络基础【信息系统监理师】

计算机网络基础【信息系统监理师】 1、OSI七层参考模型2、TCP/IP协议3、网络拓扑结构分类4、网络传输介质分类5、网络交换技术6、网络存储技术7、网络规划技术8、综合布线系统8.1、综合布线工程内容8.1、隐蔽工程-金属线槽安装8.2、隐蔽工程-管道安装槽道与各种管线间的最小净距…

WhatsApp模板信息申请大全:更好地触达WhatsApp客户

按照WhatsApp通话规则&#xff0c;用户主动和我们开始聊天后的24小时内&#xff0c;我们也是可以通过WhatsApp无限次数地与对方进行自定义消息对话&#xff0c;并且只计为一次服务型会话费用。 但是如果超过了24小时&#xff0c;我们还希望可以继续联系对方的话&#xff0c;只…

【ArcGIS】栅格数据进行标准化(归一化)处理

栅格数据进行标准化&#xff08;归一化&#xff09;处理 方法1&#xff1a;栅格计算器方法2&#xff1a;模糊分析参考 栅格数据进行标准化(归一化)处理 方法1&#xff1a;栅格计算器 栅格计算器&#xff08;Raster Calculator&#xff09; 方法2&#xff1a;模糊分析 空间…
最新文章