当前位置: 首页 > news >正文

【Linux —— 线程互斥】

Linux —— 线程互斥

  • 1. 临界资源与临界区
  • 2. 互斥的定义
  • 3. 原子性
  • 4. 互斥量(Mutex)
  • 5. 互斥的实现示例
  • 6. 互斥量实现原理探究

1. 临界资源与临界区

  • 临界资源: 指的是多个线程或进程共享的资源,例如全局变量、文件、数据库等。由于这些资源的共享,可能会导致数据不一致或程序崩溃。
  • 临界区: 是指访问临界资源的代码段。 为了保护临界资源,必须控制对临界区的访问,确保在任何时刻只有一个线程或进程可以进入临界区。

2. 互斥的定义

 互斥是一种同步机制,旨在确保同一时刻只有一个执行流(线程或者进程)可以进入临界区。
互斥通常通过互斥量(mutex)来实现。 互斥量是一种锁,线程或进程在访问临界资源之前需要获取这个锁,完成后释放它。

3. 原子性

 原子性通俗讲就是指某个操作要么完全执行,要么就完全不执行,不会被其他的进程或线程打断。
原子性操作对于保证数据的一致性和安全性至关重要。比如:在抢票软件的抢票过程中,如果没有原子性则可能出现售出的票数大于总票数的情况。

4. 互斥量(Mutex)

  • 互斥量的使用:
    • 初始化: 只是用互斥量之前,必须对其进行初始化。可以使用pthread_mutex_init 函数来进行初始化。
    • 加锁: 线程或进程在进入临界区之前,需要调用pthread_mutex_lock 来获取互斥量的锁。如果锁已经被其他的线程占用了,当前线程将被阻塞,知道锁被释放。
    • 解锁: 完成对临界资源的访问之后,必须调用pthread_mutex_unlock 来释放互斥量的锁。以允许其他线程访问临界区。
    • 销毁: 在不再需要互斥量的时候,应该调用pthread_mutex_destroy来销毁互斥量,释放有关的资源。

我们需要确保每次只有一个执行流进入临界区来访问临界资源,所以就得进行加锁处理,获得锁进而访问临界资源。
在这里插入图片描述

5. 互斥的实现示例

下面简单使用自己封装的pthread库来进行演示:

//Thread.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>namespace ThreadMoudle
{typedef void (*func_t)(const std::string &name); // 函数指针class Thread{public:void Excute(){std::cout << _name << " is running ..." << std::endl;_isrunning = true;_func(_name);_isrunning = false;}public:Thread(std::string name, func_t func): _name(name), _func(func){std::cout << "create " << name << " done ..." << std::endl;}static void *ThreadRountine(void *args){Thread *self = static_cast<Thread *>(args);self->Excute();return nullptr;}bool Start(){int n = ::pthread_create(&_tid, nullptr, ThreadRountine, this);if (n != 0){return false;}return true;}std::string Status(){if (_isrunning){return "running...";}else{return "sleep...";}}void Stop(){if (_isrunning){::pthread_cancel(_tid);_isrunning = false;std::cout << _name << " Stop..." << std::endl;}}void Join(){::pthread_join(_tid, nullptr);std::cout << _name << " Joined..." << std::endl;}std::string Name(){return _name;}~Thread(){}private:std::string _name;pthread_t _tid;bool _isrunning;func_t _func; // 回调函数};
}

简单写一个抢票代码,总共有10000张票,创建出4个线程来同时抢票:

#include <iostream>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include "Thread.hpp"using namespace ThreadMoudle;static int ticket = 10000;void route(const std::string &name)
{while(true){if(ticket > 0){usleep(1000);printf("%s get a ticket: %d \n ",name.c_str(),ticket);ticket--;}else{break;}}
}int main ()
{Thread t1("thread-1", route);Thread t2("thread-2", route);Thread t3("thread-3", route);Thread t4("thread-4", route);t1.Start();t2.Start();t3.Start();t4.Start();t1.Join();t2.Join();t3.Join();t4.Join();return 0;
}

在这里插入图片描述

我们可以看到出现了抢到负数的情况。这是为什么呢?


  • 这是因为在多线程环境中,操作系统会在多个线程之间进行切换和调度,每个线程都有其自己的程序计数器和寄存器,用于记录当前执行的位置和状态。操作系统会通过某种调度算法来决定哪个线程获得CPU时间片。
  • 然而当多个线程访问同一个共享资源的时候,他们共同会读取和修改该资源。
    假设有两个线程A和B,他们都要访问ticket变量。线程A和B的执行过程如下:
  1. 线程A获取ticket的值,发现是1,同时线程B也获取ticket的值,发现同意是2。
  2. 线程A将ticket的值减 1 ,结果变为0。
  3. 但是在线程B也将进行减操作的时候,此时ticket已经变为0了,再--就变成了负数。

- -的实现中,并不是直接- -的,而是分为三步,1. 重读数据, 2.- -数据, 3.写回数据。
所以才会出现ticket为负数的情况。

进行加锁处理:

//Thread.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>namespace ThreadMoudle
{class ThreadDate{public:ThreadDate(const std::string & name,pthread_mutex_t *lock):_name(name),_lock(lock){}public:std::string _name;pthread_mutex_t * _lock;};typedef void (*func_t)(ThreadDate* td); // 函数指针class Thread{public:void Excute(){std::cout << _name << " is running ..." << std::endl;_isrunning = true;_func(_td);_isrunning = false;}public:Thread(std::string name, func_t func,ThreadDate* td): _name(name), _func(func),_td(td){std::cout << "create " << name << " done ..." << std::endl;}static void *ThreadRountine(void *args){Thread *self = static_cast<Thread *>(args);self->Excute();return nullptr;}bool Start(){int n = ::pthread_create(&_tid, nullptr, ThreadRountine, this);if (n != 0){return false;}return true;}std::string Status(){if (_isrunning){return "running...";}else{return "sleep...";}}void Stop(){if (_isrunning){::pthread_cancel(_tid);_isrunning = false;std::cout << _name << " Stop..." << std::endl;}}void Join(){::pthread_join(_tid, nullptr);std::cout << _name << " Joined..." << std::endl;delete _td;}std::string Name(){return _name;}~Thread(){}private:std::string _name;pthread_t _tid;bool _isrunning;func_t _func; // 回调函数ThreadDate* _td;};
}
//LockGuard.hpp
#pragma once#include <pthread.h>class LockGuard
{public:LockGuard(pthread_mutex_t * mutex):_mutex(mutex){pthread_mutex_lock(_mutex);}~LockGuard(){pthread_mutex_unlock(_mutex);}private:pthread_mutex_t * _mutex;
};
//main.cc
#include <iostream>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include "Thread.hpp"
#include "LockGuard.hpp"using namespace ThreadMoudle;static int ticket = 10000;// pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;// void route(ThreadDate *td)
// {
//     std::cout << td->_name << " : " << "mutex address: " << td->_lock << std::endl;
//     sleep(1);
//     while (true)
//     {
//         ptherad_mutex_lock(td->_lock);
//         if (ticket > 0)
//         {
//             usleep(1000);
//             printf("%s get a ticket: %d \n ", name.c_str(), ticket);
//             ticket--;
//         }
//         else
//         {
//             break;
//         }
//     }
// }void route(ThreadDate *td)
{while(true){LockGuard lockguard(td->_lock);if(ticket > 0){usleep(1000);printf("who %s, get a tickdt: %d\n",td->_name.c_str(),ticket);ticket--;}else{break;}}
}static int threadnum = 4;int main()
{// Thread t1("thread-1", route);// Thread t2("thread-2", route);// Thread t3("thread-3", route);// Thread t4("thread-4", route);pthread_mutex_t mutex;pthread_mutex_init(&mutex,nullptr);std::vector<Thread> threads;for (int i = 0; i < threadnum; i++){std::string name = "thread" + std::to_string(i+1);ThreadDate* td = new ThreadDate(name,&mutex);threads.emplace_back(name,route,td);}for(auto &thread:threads){thread.Start();}for(auto &thread:threads){thread.Join();}pthread_mutex_destroy(&mutex);return 0;
}

以上使用了POSIX线程(pthreads)模拟了C++的多线程模块,定义了一个Thread类来封装线程的管理,以及使用一个用于自动互斥锁和解锁的LockGuard类,以确保线程的安全。
main函数中初始化一个互斥锁并且创建多个线程,这些线程执行一个共享的函数route,该函数递减共享的票务计数器,同时确保使用LockGuard的互斥。
每个线程都会打印其名称和票的编号,直到票数被抢光。

6. 互斥量实现原理探究

  • 经过上面的例子,大家已经意识到单纯的i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题
  • 为了实现互斥锁操作,大多数体系结构都提供了swapexchange 指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的 总线周期也有先后,一个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。
    在这里插入图片描述

http://www.mrgr.cn/news/4430.html

相关文章:

  • 数据库进阶 - 可串行化隔离级别的底层原理
  • ubuntu20 vmware硬盘空间不够,进行扩容,实操成功!
  • 个人查找下载Begell House数据库文献的方法
  • 服务优雅上下线优雅停机
  • SDK 和 API
  • 迁移学习代码复现
  • golang实现windows获取加密盘符的总大小
  • 【好书推荐】值得深读的EMC参考书籍
  • spring boot自动配置
  • 英语二【00015】精选单词练习第十天
  • 使用Blender进行3D建模—基础操作笔记(移动、缩放、视角切换,旋转)
  • 考驾照需要多长时间?你考驾照用了多长时间?
  • 【Solidity】代币
  • 深入探讨 ElementUI 动态渲染 el-table
  • 【ARM 芯片 安全与攻击 5 -- 测信道攻击(Side-channel Attack)】
  • python实现人脸轮廓提取(开操作和闭操作)
  • 【流媒体】RTMPDump—AMF编码
  • 【esp32程序编译提示undefined reference to ‘xxxx‘】
  • 线程池介绍
  • 七个电脑数据恢复方法:教你如何恢复电脑上误删除的文件