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

线程同步与互斥(互斥)

目录

线程互斥

进程线程间的互斥相关背景概念

互斥量mutex

互斥量的接⼝

初始化互斥量

初始化互斥量有两种⽅法

销毁互斥量

互斥量加锁和解锁

加锁的使用

互斥量实现原理探究

互斥量的封装

Mutex.hpp

Main.cc


线程互斥

进程线程间的互斥相关背景概念

  • 临界资源:多线程执行流共享的资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有⼀个执⾏流进⼊临界区,访问临界资源,通常对临界资源起 保护作⽤
  • 原⼦性(后⾯讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成, 要么未完成

互斥量mutex

  • ⼤部分情况,线程使⽤的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量 归属单个线程,其他线程⽆法获得这种变量。
  • 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完 成线程之间的交互。
  • 多个线程并发的操作共享变量,会带来⼀些问题。
// 操作共享变量会有问题的售票系统代码 
#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:1thread 2 sells ticket:0thread 1 sells ticket:-1thread 3 sells ticket:-

要解决以上问题,需要做到三点:

  • 代码必须要有互斥⾏为:当代码进⼊临界区执⾏时,不允许其他线程进⼊该临界区。
  • 如果多个线程同时要求执⾏临界区的代码,并且临界区没有线程在执⾏,那么只能允许⼀个线程 进⼊该临界区。
  • 如果线程不在临界区中执⾏,那么该线程不能阻⽌其他线程进⼊临界区。

要做到这三点,本质上就是需要⼀把锁。Linux上提供的这把锁叫互斥量

互斥量的接⼝

初始化互斥量

初始化互斥量有两种⽅法

⽅法1,静态分配:  PTHREAD_MUTEX_INITIALIZER是一个宏,用于静态初始化互斥锁。它只能用于静态存储期的对象(如全局变量或静态变量),不能用于动态分配的互斥锁。全局使用不需要销毁

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

⽅法2,动态分配:  pthread_mutex_init是一个函数,用于动态初始化互斥锁。它可以用于任何类型的互斥锁,包括动态分配的互斥锁 ,局部使用需要销毁

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const 
pthread_mutexattr_t *restrict attr);参数:mutex:要初始化的互斥量attr:NULL

销毁互斥量

销毁互斥量需要注意:

  • 使⽤ PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁 

  • 不要销毁⼀个已经加锁的互斥量

  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);返回值:成功返回0,失败返回错误号

调⽤ pthread_ lock 时,可能会遇到以下情况:

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调⽤时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到 互斥量,那么pthread_lock调⽤会陷⼊阻塞(执⾏流被挂起),等待互斥量解锁

加锁的使用

注释掉的是使用互斥量的接⼝的方法  ,保留的是C++11的加锁

1. 锁本身是全局的,那么锁也是共享资源!谁保证锁的安全??

pthread_mutex:加锁和解锁被设计成为原子的了 --- TODO??

2. 如何看待锁呢?二元信号量就是锁!

   2.1 加锁本质就是对资源展开预订!

   2.2 整体使用资源!!

3. 如果申请锁的时候,锁被别人已经拿走了,怎么办?其他线程要进行阻塞等待

4. 线程在访问临界区代码的时候,可以不可以切换??可以切换!!

   4.1 我被切走的时候,别人能进来吗??不能!我是抱着锁,被切换的!!不就是串行吗!效率低的原因!原子性!

5. 不遵守这个约定??bug!

使用C++11的锁

#include <iostream>
#include <vector>
#include "Thread.hpp"
#include <mutex>   //c++11的锁using namespace ThreadModule;#define NUM 4// 1. 锁本身是全局的,那么锁也是共享资源!谁保证锁的安全??
// pthread_mutex:加锁和解锁被设计成为原子的了 --- TODO??
// 2. 如何看待锁呢?二元信号量就是锁!
//    2.1 加锁本质就是对资源展开预订!
//    2.2 整体使用资源!!
// 3. 如果申请锁的时候,锁被别人已经拿走了,怎么办?其他线程要进行阻塞等待
// 4. 线程在访问临界区代码的时候,可以不可以切换??可以切换!!
//    4.1 我被切走的时候,别人能进来吗??不能!我是抱着锁,被切换的!!不就是串行吗!效率低的原因!原子性!
// 5. 不遵守这个约定??bug!// pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int ticketnum = 10000; // 共享资源,临界资源
std::mutex gmtx;   //定义c++11的锁 ,定义一个全局互斥量class ThreadData
{
public:std::string name;//pthread_mutex_t *lock_ptr;
};void Ticket(ThreadData &td)
{while (true){gmtx.lock();//加锁//pthread_mutex_lock(td.lock_ptr); // 加锁if (ticketnum > 0){usleep(1000);// 1. 抢票printf("get a new ticket, who get it: %s, id: %d\n", td.name.c_str(), ticketnum--);gmtx.unlock();  //解锁//pthread_mutex_unlock(td.lock_ptr); // ??// 2. 入库模拟usleep(50);}else{//pthread_mutex_unlock(td.lock_ptr); // ??gmtx.unlock();  //解锁break;}}
}int main()
{// pthread_mutex_t lock;// pthread_mutex_init(&lock, nullptr);// 1. 构建线程对象std::vector<Thread<ThreadData>> threads;for (int i = 0; i < NUM; i++){ThreadData *td = new ThreadData();//td->lock_ptr = &lock;// td->lock_ptr =nullptr;threads.emplace_back(Ticket, *td);td->name = threads.back().Name();}// 2. 启动线程for (auto &thread : threads){thread.Start();}// 3. 等待线程for (auto &thread : threads){thread.Join();}// pthread_mutex_destroy(&lock);return 0;
}

使用全局的锁

#include <iostream>
#include <vector>
#include "Thread.hpp"
#include <mutex>   //c++11的锁using namespace ThreadModule;#define NUM 4pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int ticketnum = 10000; // 共享资源,临界资源
// std::mutex gmtx;   //定义c++11的锁class ThreadData
{
public:std::string name;pthread_mutex_t *lock_ptr;
};void Ticket(ThreadData &td)
{while (true){// gmtx.lock();//加锁pthread_mutex_lock(td.lock_ptr); // 加锁if (ticketnum > 0){usleep(1000);// 1. 抢票printf("get a new ticket, who get it: %s, id: %d\n", td.name.c_str(), ticketnum--);// gmtx.unlock();  //解锁pthread_mutex_unlock(td.lock_ptr); // ??// 2. 入库模拟usleep(50);}else{pthread_mutex_unlock(td.lock_ptr); // ??// gmtx.unlock();  //解锁break;}}
}int main()
{// pthread_mutex_t lock;// pthread_mutex_init(&lock, nullptr);// 1. 构建线程对象std::vector<Thread<ThreadData>> threads;for (int i = 0; i < NUM; i++){ThreadData *td = new ThreadData();td->lock_ptr = &lock;threads.emplace_back(Ticket, *td);td->name = threads.back().Name();}// 2. 启动线程for (auto &thread : threads){thread.Start();}// 3. 等待线程for (auto &thread : threads){thread.Join();}// pthread_mutex_destroy(&lock);return 0;
}

互斥量实现原理探究

        经过上⾯的例⼦,⼤家已经意识到单纯的 i++ 或者 ++i 都不是原⼦的,有可能会有数据⼀致性 问题

为了实现互斥锁操作,⼤多数体系结构都提供了swap或exchange指令,该指令的作⽤是把寄存器和 内存单元的数据相交换,由于只有⼀条指令,保证了原⼦性,即使是多处理器平台,访问内存的总线周 期也有先后,⼀个处理器上的交换指令执⾏时另⼀个处理器的交换指令只能等待总线周期。现在 我们把lock和unlock的伪代码改⼀下

互斥量的封装

Mutex.hpp

#pragma once
#include<iostream>
#include<pthread.h>
#include <unistd.h>namespace LockModule
{class Mutex{public://防止锁被拷贝 Mutex(const Mutex&) = delete;const Mutex& operator = (const Mutex&) = delete;Mutex(){int n = ::pthread_mutex_init(&_lock ,nullptr);if(n!= 0) std::cout<<"pthread_mutex_init error"<<std::endl;}void Lock(){int n = ::pthread_mutex_lock(&_lock);if(n!= 0) std::cout<<"lock error"<<std::endl;}void Unlock(){int n =::pthread_mutex_unlock(&_lock);if(n!= 0) std::cout<<"unlock error"<<std::endl;}~Mutex(){int n = pthread_mutex_destroy(&_lock);if(n!= 0) std::cout<<"destroy error"<<std::endl;}private:pthread_mutex_t _lock;};class LockGuard{public:LockGuard(Mutex &mtx):_mtx(mtx){_mtx.Lock();}~LockGuard(){_mtx.Unlock();}private:Mutex &_mtx;};}

使用

Main.cc

#include "Mutex.hpp"using namespace LockModule;
int ticket=1000;Mutex mtx;void *route(void *arg)
{char *id = (char *)arg;while (1){//RAII 风格的加锁LockGuard  lockguard(mtx);//在代码块中定义的临时对象 ,处了这个块 就自己析构了 ,这个块就是临界区if (ticket > 0){usleep(1000);printf("%s sells ticket:%d\n", id, ticket);ticket--;}else{break;}}return nullptr;
}// void *route(void *arg)
// {
//     char *id = (char *)arg;
//     while (1)
//     {
//        mtx.Lock();
//         if (ticket > 0)
//         {
//             usleep(1000);
//             printf("%s sells ticket:%d\n", id, ticket);
//             ticket--;
//             mtx.Unlock();
//         }
//         else
//         {
//             mtx.Unlock();
//             break;
//         }
//     }
//     return nullptr;
// }int main(void)
{pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, route, (void *)"thread 1");pthread_create(&t2, NULL, route, (void *)"thread 2");pthread_create(&t3, NULL, route, (void *)"thread 3");pthread_create(&t4, NULL, route, (void *)"thread 4");pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);
}

相关文章:

  • Vue.js 的组件化开发指南
  • 【k8s】KubeProxy 的三种工作模式——Userspace、iptables 、 IPVS
  • 如何应对客户提出的不合理需求
  • 第四章: 服务集成抽象
  • 3.ArkUI Image的介绍和使用
  • JSX介绍
  • django admin 添加自定义页面
  • C++学习:六个月从基础到就业——STL算法(三)—— 数值算法(上)
  • Linux电源管理(四),设备的Runtime Power Management(RPM)
  • 网络知识:路由器静态路由与动态路由介绍
  • YCDISM2025-更新
  • 接口测试和单元测试详解
  • connection.cursor() 与 models.objects.filter
  • [web]攻防世界 easyphp
  • [U-Net]DA-TRANSUNET
  • 前端数据库缓存
  • onnx注册cpu版flashattention
  • springboot基于hadoop的酷狗音乐爬虫大数据分析可视化系统(源码+lw+部署文档+讲解),源码可白嫖!
  • 自动化测试概念及常用函数篇 [软件测试 基础]
  • GIT 使用小记
  • 石磊当选河北秦皇岛市市长
  • 嫦娥八号任务合作项目,这十个入选
  • 灰鹦鹉爆粗口三年未改?云南野生动物园:在持续引导
  • 北京潮白河大桥发生火情:部分桥体受损,现场已双向断路
  • 深一度|坚守17年,这件事姚明就算赔钱也在继续做
  • 上海优化餐企发展环境:装修拓展门店最高奖50万,建立问题协调机制