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

【Linux】线程的互斥和同步

【Linux】线程的互斥和同步

线程间的互斥

  • 临界资源:多线程执行共享的资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源进行保护
  • 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

1. 互斥量mutex

  • 大部分情况下,线程使用的数据都是局部变量,变量的地址空间在线程的栈空间内,这种情况下,变量属于单个线程,且其他线程无法访问该变量
  • 但有时候,很多变量需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程间的交互
  • 多个线程并发操作共享变量,会带来一些问题

实验:操作共享变量会有问题的售票系统代码

#ifndef __THREAD_HPP__
#define __THREAD_HPP__
#include<iostream>
#include<pthread.h>
#include <string>
#include <unistd.h>
#include<functional>
namespace ThreadModule
{template<typename T>using func_t = std::function<void(T)>;template<typename T>class Thread{public:Thread(func_t<T> func,T const data,const std::string& threadname = "none_name")//--常量引用和非常量引用的概念:_func(func),_data(data),_threadname(threadname),_stop(true){}void Excute(){_func(_data);}static void* handler(void* args){Thread<T>* self = static_cast<Thread<T> *>(args);// self->Excute();self->_func(self->_data);return nullptr;}bool Start(){int ret = pthread_create(&_tid,nullptr,handler,this);if(ret == 0){_stop = false;return true;}else {return false;}}void Join(){if(!_stop){pthread_join(_tid,nullptr);}}std::string name(){return _threadname;}~Thread(){}private:std::string _threadname;pthread_t _tid;T _data;func_t<T> _func;bool _stop;};
}
#endif
using namespace ThreadModule;
int g_tickets = 10000; // 共享资源,没有保护的, 临界资源
class ThreadData
{public:ThreadData(const std::string& threadname,int& ticked):_threadname(threadname),_tickeds(ticked),_total(0){}~ThreadData(){}public:std::string _threadname;int& _tickeds;int _total;
};
void route(ThreadData* ptr)
{while(true){if(ptr->_tickeds > 0){//模拟一次抢票逻辑usleep(1000);printf("%s is runing ,get ticked: %d\n",ptr->_threadname.c_str(),ptr->_tickeds);ptr->_tickeds--;ptr->_total++;}else{break;}}return ;
}
const int num = 4;
int main()
{//创建一批线程std::vector<Thread<ThreadData*>> threads;std::vector<ThreadData*> datas;for(int i = 0;i < num;i++){std::string name = "thread-" + std::to_string(i+1);ThreadData* _ptr = new ThreadData(name,g_tickets);threads.emplace_back(route,_ptr,name);datas.emplace_back(_ptr);}//启动一批线程for(auto& thread:threads){thread.Start();}//等待一批线程for(auto& thread:threads){thread.Join();std::cout<<"wait thread name: "<<thread.name()<<std::endl;}for(auto data: datas){std::cout<<"name: "<<data->_threadname<<"total is: "<<data->_total<<std::endl; delete data; }return 0;
}

实验现象:

为什么会抢到票为-1,或-2的票呢?

  • if 语句判断条件为真以后,代码可以并发的切换到其他线程
  • usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
  • _tickeds-- 操作本身就不是一个原子操作

在这里插入图片描述

取出ticket--部分的汇编代码
objdump -d a.out > test.objdump
152 40064b: 8b 05 e3 04 20 00 mov 0x2004e3(%rip),%eax # 600b34 <_tickeds>
153 400651: 83 e8 01 sub $0x1,%eax
154 400654: 89 05 da 04 20 00 mov %eax,0x2004da(%rip) # 600b34 <_tickeds>

– 操作并不是原子操作,而是对应三条汇编指令:

  • load :将共享变量ticket从内存加载到寄存器中
  • update : 更新寄存器里面的值,执行-1操作
  • store :将新值,从寄存器写回共享变量ticket的内存地址

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

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

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

2. 互斥量的接口

初始化互斥量
初始化互斥量有两种方法:

  • 方法1,静态分配:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
  • 方法2,动态分配:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrictattr);参数:mutex:要初始化的互斥量attr:NULL

销毁互斥量
销毁互斥量需要注意:

  • 使用PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥量加锁和解锁

int ptread_mutex_lock(pthread_mutex* mutex);
int ptread_mutex_unlock(pthread_mutex* mutex);
返回值:成功返回0,失败返回错误号

调用pthread_lock可能会出现以下情况:

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

改进上面的售票系统:

int g_tickets = 10000; // 共享资源,没有保护的, 临界资源
void route(ThreadData* ptr)
{while(true){pthread_mutex_lock(&getmutex);//加锁:本质是将并行执行--》串行执行,加锁的粒度越细越好if(ptr->_tickeds > 0){usleep(1000);printf("%s is runing ,get ticked: %d\n",ptr->_threadname.c_str(),ptr->_tickeds);ptr->_tickeds--;pthread_mutex_unlock(&getmutex);//解锁ptr->_total++;}else{pthread_mutex_unlock(&getmutex);//解锁break;}}return ;
}

image-20241123142442036

更优雅的代码:

#ifndef __LOCK_GUARD_HPP__
#define __LOCK_GUARD_HPP__
#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;
};
#endif
#include"Lock_Guard.hpp"
#include"Thread.hpp"
#include<iostream>
#include <iostream>
#include <vector>
#include<mutex>using namespace ThreadModule;
//静态锁
//pthread_mutex_t getmutex = PTHREAD_MUTEX_INITIALIZER;//静态锁
class ThreadData
{public:ThreadData(const std::string& threadname,int& ticked,std::mutex& mutex):_threadname(threadname),_tickeds(ticked),_total(0),_mutex(mutex){}~ThreadData(){}public:std::string _threadname;int& _tickeds;int _total;// pthread_mutex_t& _mutex;std::mutex& _mutex;
};
int g_tickets = 10000; // 共享资源,没有保护的, 临界资源
void route(ThreadData* ptr)
{while(true){//加锁:本质是将并行执行--》串行执行,加锁的粒度越细越好//线程竞争锁是自由竞争的,竞争锁的能力太强,就会导致其他线程抢不到锁,---造成其他线程的饥饿问题!!!//pthread_mutex_lock(&ptr->_mutex);     动态锁//pthread_mutex_lock(&getmutex);        静态锁//LockGuard mutex(&ptr->_mutex);        自己封装的RAII锁// std::lock_guard<std::mutex> lock(ptr->_mutex);  //C++11RAII锁ptr->_mutex.lock();                     //C++11锁//模拟一次抢票逻辑              if(ptr->_tickeds > 0)         {usleep(1000);printf("%s is runing ,get ticked: %d\n",ptr->_threadname.c_str(),ptr->_tickeds);ptr->_tickeds--;//pthread_mutex_unlock(&ptr->_mutex);//解锁//pthread_mutex_unlock(&getmutex);ptr->_mutex.unlock();ptr->_total++;}else{//pthread_mutex_unlock(&ptr->_mutex);//解锁//pthread_mutex_unlock(&getmutex);ptr->_mutex.unlock();break;}}return ;
}
const int num = 4;
int main()
{//动态锁// pthread_mutex_t mutex;// pthread_mutex_init(&mutex,nullptr);//C++11锁std::mutex mutex;//创建一批线程std::vector<Thread<ThreadData*>> threads;std::vector<ThreadData*> datas;for(int i = 0;i < num;i++){std::string name = "thread-" + std::to_string(i+1);ThreadData* _ptr = new ThreadData(name,g_tickets,mutex);threads.emplace_back(route,_ptr,name);datas.emplace_back(_ptr);}//启动一批线程for(auto& thread:threads){thread.Start();}//等待一批线程for(auto& thread:threads){thread.Join();std::cout<<"wait thread name: "<<thread.name()<<std::endl;}for(auto data: datas){std::cout<<"name: "<<data->_threadname<<"total is: "<<data->_total<<std::endl; delete data; //pthread_mutex_destroy(&data->_mutex);data->_mutex.~mutex();}return 0;
}

2. 互斥的底层实现

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

加锁and解锁:

image-20241123164826838

image-20241123170200857

线程间的同步

条件变量

  • 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
  • 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情
    况就需要用到条件变量。

同步概念与竞态条件

  • 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问
    题,叫做同步
  • 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解

1. 条件变量函数接口

初始化

  • 静态初始化
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
  • 动态初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict
attr);
参数:cond:要初始化的条件变量attr:NULL
  • 销毁
int pthread_con_destroy(pthread_con_t *cond);
  • 等待条件满足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数:cond:要在这个条件变量上等待mutex:互斥量,后面详细解释
  • 唤醒等待
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

实验:线程同步

#include<iostream>
#include<vector>
#include<string>
#include<unistd.h>
#include<pthread.h>
pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;//互斥量
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//条件变量
void* Mastercore(void* args)
{sleep(3);std::cout<<"mastcore 开始唤醒..."<<std::endl;std::string name = static_cast<char*>(args);//唤醒...while(true){pthread_cond_signal(&cond);//唤醒等待队列中第一个线程sleep(1);}//pthread_cond_broadcast(&cond);//唤醒等待队列中所有线程return nullptr;
}
void* Slavercore(void* args)
{std::string name = static_cast<char*>(args);while(true){pthread_mutex_lock(&gmutex);//加锁pthread_cond_wait(&cond,&gmutex);// 等待条件变量,std::cout<<name<<"被唤醒..."<<std::endl;//TODOpthread_mutex_unlock(&gmutex);//解锁}
}
void StartMaster(std::vector<pthread_t>* tidptr)
{pthread_t tid;int n = pthread_create(&tid,nullptr,Mastercore,(void*)"master thread");if(n == 0){std::cout<<"master thread create success ..."<<std::endl;}tidptr->emplace_back(tid);
}void StartSlaver(std::vector<pthread_t>* tidptr,int threadnum)
{for(int i = 0;i< threadnum;i++){pthread_t tid;char* name = new char[64];snprintf(name,64,"thread-%d",i+1);int n = pthread_create(&tid,nullptr,Slavercore,name);if(n == 0){std::cout<<name<<" create success ..."<<std::endl;}tidptr->emplace_back(tid);}
}
void WaitThread(const std::vector<pthread_t>& tids)
{for(auto tid:tids){pthread_join(tid,nullptr);}
}
int main()
{std::vector<pthread_t> tids;StartMaster(&tids);StartSlaver(&tids,5);WaitThread(tids);return 0;
}

结果:

image-20241123205256945

生产者与消费者模型

  • 解耦
  • 支持并发
  • 支持忙闲不均:是指在一个系统中,不同组件或线程之间工作负载分配不均匀的现象。

image-20241124142954354

实验:生产者与消费者模型基础版

阻塞队列:

#ifndef __BLOCK_QUEUE_HPP__
#define __BLOCK_QUEUE_HPP__
#include <iostream>
#include <pthread.h>
#include <queue>
template <typename T>
class BlockQueue
{
public:bool is_full(){return _BlockQueue.size() == _cap;}bool is_empty(){return _BlockQueue.empty();}public:BlockQueue(int cap): _cap(cap){pthread_mutex_init(&_mutex, nullptr);pthread_cond_init(&_productor_cond, nullptr);pthread_cond_init(&_consumer_cond, nullptr);}void enqueue(T &in) // 生产者使用接口{pthread_mutex_lock(&_mutex);if (is_full()){pthread_cond_wait(&_productor_cond, &_mutex); // 如果满了,生产者入等待队列,解锁--唤醒--出等待队列,锁定}_BlockQueue.push(in);// 通知消费者来买// std::cout << "通知消费者来买" << std::endl;pthread_cond_signal(&_consumer_cond);// std::cout << "通知完成" << std::endl;pthread_mutex_unlock(&_mutex);}void pop(T *out){pthread_mutex_lock(&_mutex);if (is_empty()){// std::cout << "消费者入等待队列" << std::endl;pthread_cond_wait(&_consumer_cond, &_mutex); // 如果空了,消费者入等待队列,解锁,---被唤醒--出等待队列,锁定}*out = _BlockQueue.front();_BlockQueue.pop();// 通知生产者来卖pthread_cond_signal(&_productor_cond);pthread_mutex_unlock(&_mutex);}~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_productor_cond);pthread_cond_destroy(&_consumer_cond);}private:std::queue<T> _BlockQueue;int _cap; // 阻塞队列上限pthread_mutex_t _mutex;pthread_cond_t _productor_cond; // 生产者等待队列pthread_cond_t _consumer_cond;  // 消费者等待队列
};
#endif

主函数:

#include <iostream>
#include "BlockQueue.hpp"
#include "Thread.hpp"
using namespace ThreadModule;void producer(BlockQueue<int> &bq)
{int cnt = 3;while (true){bq.enqueue(cnt);std::cout << "producer is sell :" << cnt << std::endl;cnt++;}
}
void consumer(BlockQueue<int> &bq)
{while (true){sleep(3);int data;bq.pop(&data); // 为什么传地址,通过地址修改cntstd::cout << "consumer is buy :" << data << std::endl;}
}
void Start_Com(std::vector<Thread<BlockQueue<int>>> *threads_ptr, BlockQueue<int> &bq, int num, func_t<BlockQueue<int>> func)
{for (int i = 0; i < num; i++){std::string name = "thread-" + std::to_string(i + 1);threads_ptr->emplace_back(func, bq, name);threads_ptr->back().Start();}
}
void StartProducer(std::vector<Thread<BlockQueue<int>>> *threads_ptr, BlockQueue<int> &bq, int num)
{Start_Com(threads_ptr, bq, num, producer);
}
void StartConsumer(std::vector<Thread<BlockQueue<int>>> *threads_ptr, BlockQueue<int> &bq, int num)
{Start_Com(threads_ptr, bq, num, consumer);
}
void WaitAllThread(std::vector<Thread<BlockQueue<int>>> &threads)
{for (auto thread : threads){thread.Join();}
}
int main()
{BlockQueue<int> *ptr = new BlockQueue<int>(5);std::vector<Thread<BlockQueue<int>>> threads;StartProducer(&threads, *ptr, 1);StartConsumer(&threads, *ptr, 1);WaitAllThread(threads);return 0;
}

image-20241124143415817

升级版:传递任务1. 0

#pragma once
#include <iostream>
class Task
{
public:Task(){}Task(int a, int b): _a(a), _b(b){}void Excute(){_result = _a+_b;}std::string ResultToString(){return std::to_string(_a)+" + "+std::to_string(_b)+" = "+std::to_string(_result);}std::string DebugToString(){return std::to_string(_a)+" + "+std::to_string(_b)+" = ?";}
private: int _a;int _b;int _result;
};
#include <iostream>
#include "BlockQueue.hpp"
#include "Thread.hpp"
#include "Task.hpp"
using namespace ThreadModule;void producer(BlockQueue<Task> &bq)
{srand((unsigned)time(NULL));int cnt = 3;while (true){sleep(2);int first = rand() % 100;usleep(1234);int second = rand() % 100;Task tk(first, second);bq.enqueue(tk);std::cout << tk.DebugToString() << std::endl;}
}
void consumer(BlockQueue<Task> &bq)
{while (true){Task td;bq.pop(&td); // 为什么传地址,通过地址修改cnttd.Excute();std::cout << td.ResultToString() << std::endl;}
}
void Start_Com(std::vector<Thread<BlockQueue<Task>>> *threads_ptr, BlockQueue<Task> &bq, int num, func_t<BlockQueue<Task>> func)
{for (int i = 0; i < num; i++){std::string name = "thread-" + std::to_string(i + 1);threads_ptr->emplace_back(func, bq, name);threads_ptr->back().Start();}
}
void StartProducer(std::vector<Thread<BlockQueue<Task>>> *threads_ptr, BlockQueue<Task> &bq, int num)
{Start_Com(threads_ptr, bq, num, producer);
}
void StartConsumer(std::vector<Thread<BlockQueue<Task>>> *threads_ptr, BlockQueue<Task> &bq, int num)
{Start_Com(threads_ptr, bq, num, consumer);
}
void WaitAllThread(std::vector<Thread<BlockQueue<Task>>> &threads)
{for (auto thread : threads){thread.Join();}
}
int main()
{BlockQueue<Task> *ptr = new BlockQueue<Task>(5);std::vector<Thread<BlockQueue<Task>>> threads;StartProducer(&threads, *ptr, 1);StartConsumer(&threads, *ptr, 3);WaitAllThread(threads);return 0;
}

image-20241124154042539

传递任务2.0

#include <iostream>
#include "BlockQueue.hpp"
#include "Thread.hpp"
#include "Task.hpp"
using namespace ThreadModule;
using Task = std::function<void()>;
using bock_queue_t = BlockQueue<Task>;
void printdata()
{std::cout << "hell word" << std::endl;
}
void producer(bock_queue_t &bq)
{while (true){sleep(1);Task t = printdata;bq.enqueue(t);}
}
void consumer(bock_queue_t &bq)
{while (true){Task tk;bq.pop(&tk); // 为什么传地址,通过地址修改cnttk();}
}
void Start_Com(std::vector<Thread<BlockQueue<Task>>> *threads_ptr, bock_queue_t &bq, int num, func_t<bock_queue_t> func)
{for (int i = 0; i < num; i++){std::string name = "thread-" + std::to_string(i + 1);threads_ptr->emplace_back(func, bq, name);threads_ptr->back().Start();//创建线程}
}
void StartProducer(std::vector<Thread<bock_queue_t>> *threads_ptr, bock_queue_t &bq, int num)
{Start_Com(threads_ptr, bq, num, producer);
}
void StartConsumer(std::vector<Thread<bock_queue_t>> *threads_ptr, bock_queue_t &bq, int num)
{Start_Com(threads_ptr, bq, num, consumer);
}
void WaitAllThread(std::vector<Thread<bock_queue_t>> &threads)
{for (auto thread : threads){thread.Join();}
}
int main()
{bock_queue_t *ptr = new bock_queue_t(5);std::vector<Thread<bock_queue_t>> threads;StartProducer(&threads, *ptr, 1);StartConsumer(&threads, *ptr, 3);WaitAllThread(threads);return 0;
}

image-20241124154529305

POSIX信号量

POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。

  • 初始化信号量
#include<semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value)
参数:pshared: 0表示线程间共享,非零表示进程间共享value:表示信号量初始值
  • 销毁信号量
int sem_destroy(sem_t* sem);
  • 等待信号量
功能:等待信号量,会将信号量值减一
int sem_wait(sem_t * sem);//P()
  • 发布信号量
功能:发布信号量,表示资源使用完毕,可以归还资源了,将信号量的值叫加一
int sem_post(sem_t * sem);//V()

上一节生产者-消费者的例子是基于queue的,其空间可以动态分配,现在基于固定大小的环形队列重写这个程序
(POSIX信号量):

image-20241124200631234

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

  • 环形队列采用数组模拟,用模运算来模拟环状特性

image-20241124192711664

  • 环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来
    判断满或者空。另外也可以预留一个空的位置,作为满的状态

image-20241124192731836

  • 但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程
#ifndef __RING_QUEUE_HPP__
#define __RING_QUEUE_HPP__
#include <iostream>
#include <pthread.h>
#include <queue>
#include <semaphore.h>
#include <vector>
template <typename T>
class RingQueue
{
public:void P(sem_t &sem){sem_wait(&sem);}void V(sem_t &sem){sem_post(&sem);}void Lock(pthread_mutex_t &mutex){pthread_mutex_lock(&mutex);}void Unlock(pthread_mutex_t &mutex){pthread_mutex_unlock(&mutex);}public:RingQueue(int cap): _ring_queue(cap), _cap(cap), _productor_index(0), _consumer_index(0){sem_init(&_room_sem, 0, _cap);//信号量初始化,空间资源初始为_ring_queue的容量sem_init(&_data_sem, 0, 0);//数据资源初始为 0pthread_mutex_init(&_productor_mutex, nullptr);//互斥量初始化pthread_mutex_init(&_consumer_mutex, nullptr);}void emquue(T &in){// 生产行为//P操作用来减少信号量的值(通常是减1)。//如果_room_sem信号量的值大于0,执行P操作后,信号量的值减1,进程继续执行。//如果信号量的值为0,执行P操作后,进程会被阻塞,直到信号量的值变为大于0,这时进程才会被唤醒并继续执行。P(_room_sem);Lock(_productor_mutex);//加锁,维护生产者与生产者的竞争_ring_queue[_productor_index++] = in;//生产数据_productor_index %= _ring_queue.size();Unlock(_productor_mutex);V(_data_sem);//当_data_sem信号量的值增加后,如果有进程因为执行P(_data_sem)操作而被阻塞在该信号量上,//那么系统会选择一个或多个进程解除其阻塞状态,允许它们继续执行}void pop(T *out){// 消费行为P(_data_sem);//_dataLock(_consumer_mutex);*out = _ring_queue[_consumer_index++];_consumer_index %= _ring_queue.size();Unlock(_consumer_mutex);V(_room_sem);}~RingQueue(){sem_destroy(&_room_sem);//销毁信号量sem_destroy(&_data_sem);pthread_mutex_destroy(&_productor_mutex);//销毁互斥量pthread_mutex_destroy(&_consumer_mutex);}private:// 环形队列std::vector<T> _ring_queue;int _cap;// 生产者与消费者下标int _productor_index;int _consumer_index;// 信号量sem_t _room_sem;//空间信号量sem_t _data_sem;//数据信号量// 互斥量pthread_mutex_t _productor_mutex;//生产者互斥量pthread_mutex_t _consumer_mutex;//消费者互斥量
};
#endif
#ifndef __THREAD_HPP__
#define __THREAD_HPP__
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
#include <functional>
namespace ThreadModule
{template <typename T>using func_t = std::function<void(T &,std::string)>;template <typename T>class Thread{public:Thread(func_t<T> func, T& data, const std::string threadname = "none_name") // 为什么--常量引用和非常量引用的概念: _func(func), _data(data), _threadname(threadname), _stop(true){}void Excute(){_func(_data,_threadname);}static void *handler(void *args){Thread<T> *self = static_cast<Thread<T> *>(args);self->Excute();return nullptr;}bool Start(){int ret = pthread_create(&_tid, nullptr, handler, this);if (ret == 0){_stop = false;return true;}else{return false;}}void Join(){if (!_stop){pthread_join(_tid, nullptr);}}std::string name(){return _threadname;}~Thread() {}private:std::string _threadname;pthread_t _tid;T& _data;func_t<T> _func;bool _stop;};
}
#endif
#include <iostream>
#include "RingQueue.hpp"
#include "Thread.hpp"
using namespace ThreadModule;
using Task = std::function<void()>;
using ring_queue_t = RingQueue<Task>;
void printdata()
{std::cout << "hell word" << std::endl;
}
void producer(ring_queue_t &bq, std::string name)
{int cnt = 10;while (true){sleep(2);Task t = printdata;bq.emquue(t); // 传递任务// std::cout<< name << " in: " << cnt << std::endl;// cnt++;}
}
void consumer(ring_queue_t &bq, std::string name)
{while (true){int cnt;Task tk;bq.pop(&tk);tk(); // 执行执行任务std::cout << name << " is run : task " << std::endl;}
}
void Init_Com(std::vector<Thread<ring_queue_t>> *threads_ptr, ring_queue_t &rq, int num, func_t<ring_queue_t> func, const std::string &who)
{for (int i = 0; i < num; i++){std::string _name = "thread- " + std::to_string(i + 1) + "  " + who;threads_ptr->emplace_back(func, rq, _name);// threads_ptr->back().Start();}
}
void InitProducer(std::vector<Thread<ring_queue_t>> *threads_ptr, ring_queue_t &rq, int num)
{Init_Com(threads_ptr, rq, num, producer, "producer");
}
void InitConsumer(std::vector<Thread<ring_queue_t>> *threads_ptr, ring_queue_t &rq, int num)
{Init_Com(threads_ptr, rq, num, consumer, "consumer");
}
void StartAllThread(std::vector<Thread<ring_queue_t>> &threads)
{for (auto &thread : threads){thread.Start();}
}
void WaitAllThread(std::vector<Thread<ring_queue_t>> &threads)
{for (auto &thread : threads){thread.Join();}
}
int main()
{ring_queue_t *ptr = new ring_queue_t(10);std::vector<Thread<ring_queue_t>> threads; // 所有副线程共享ring_queueInitProducer(&threads, *ptr, 1);           // 生产者初始化InitConsumer(&threads, *ptr, 3);           // 消费者初始化StartAllThread(threads);                   // 启动所有副线程WaitAllThread(threads);                    // 等待所有副线程return 0;
}

结果

[!NOTE]

代码一定要多敲,才能明白里面的细节,加油👍👍👍

【Linux】互斥和同步—完结。

相关文章:

【Linux】线程的互斥和同步

【Linux】线程的互斥和同步 线程间的互斥 临界资源&#xff1a;多线程执行共享的资源就叫做临界资源临界区&#xff1a;每个线程内部&#xff0c;访问临界资源的代码&#xff0c;就叫做临界区互斥&#xff1a;任何时刻&#xff0c;互斥保证有且只有一个执行流进入临界区&#…...

详解Oracle表的类型(二)

1.引言&#xff1a; Oracle数据库提供了多种表类型&#xff0c;以满足不同的数据存储和管理需求。本博文将对Oracle分区表及使用场景进行详细介绍。 2. 分区表 分区表是Oracle数据库中一种重要的表类型&#xff0c;它通过将表数据分割成多个逻辑部分来提高查询性能、管理灵活…...

VSCode 下载 安装

VSCode【下载】【安装】【汉化】【配置C环境&#xff08;超快&#xff09;】&#xff08;Windows环境&#xff09;-CSDN博客 Download Visual Studio Code - Mac, Linux, Windowshttps://code.visualstudio.com/Downloadhttps://code.visualstudio.com/Download 注意&#xff0…...

java中的最小堆

概述 最小堆minHeap指的级别n的每个节点存储的值小于或等于级别n1的子节点的值。因此&#xff0c;根就存储了其中最小的值。 注意节点的值与其他兄弟节点的值之间没有必然关系。 java中最小堆的表示 利用数组 常用的是利用数组minHeap[]表示&#xff0c;将最小堆的节点或值…...

ES实用面试题

一、es是什么&#xff0c;为什么要用它&#xff1f; ES通常是Elasticsearch的简称&#xff0c;它是一个基于Lucene构建的开源搜索引擎。Elasticsearch以其分布式、高扩展性和实时数据分析能力而闻名&#xff0c;广泛用于全文搜索、日志分析、实时监控等多种场景。 基本特点&am…...

数据结构 (7)线性表的链式存储

前言 线性表是一种基本的数据结构&#xff0c;用于存储线性序列的元素。线性表的存储方式主要有两种&#xff1a;顺序存储和链式存储。链式存储&#xff0c;即链表&#xff0c;是一种非常灵活和高效的存储方式&#xff0c;特别适用于需要频繁插入和删除操作的场景。 链表的基本…...

数据结构:链表进阶

链表进阶 1. ArrayList的缺陷2. 链表2.1 链表的概念及结构2.2 链表的实现 3.链表面试题4.LinkedList的使用5.1 什么是LinkedList4.2 LinkedList的使用 5. ArrayList和LinkedList的区别 1. ArrayList的缺陷 通过源码知道&#xff0c;ArrayList底层使用数组来存储元素&#xff1…...

Can UDS 学习记录

目录 Can UDS 学习记录10 03 响应否定响应22和24 的说明27服务肯定响应抑制位 Can UDS 学习记录 10 03 响应 否定响应22和24 的说明 0x22条件不正确&#xff0c;就是不满足服务端的运行条件0x24请求顺序错误&#xff1b;停止例程的请求必须在开始例程后面才可以&#xff0c;请…...

C 语言实现的优先级队列

C 语言实现的优先级队列 priorityqueue.h /******************************************************************************* * Copyright © 2024-2025 Light Zhang <mapawarehotmail.com>, MapAware, Inc. * * ALL RIGHTS RESERVED. …...

卷积神经网络(CNN)中的批量归一化层(Batch Normalization Layer)

批量归一化层&#xff08;BatchNorm层&#xff09;&#xff0c;或简称为批量归一化&#xff08;Batch Normalization&#xff09;&#xff0c;是深度学习中常用的一种技术&#xff0c;旨在加速神经网络的训练并提高收敛速度。 一、基本思想 为了让数据在训练过程中保持同一分布…...

HDR视频技术之四:HDR 主要标准

HDR 是 UHD 技术中最重要维度之一&#xff0c;带来新的视觉呈现体验。 HDR 技术涉及到采集、加工、传输、呈现等视频流程上的多个环节&#xff0c;需要定义出互联互通的产业标准&#xff0c;以支持规模化应用和部署。本文整理当前 HDR 应用中的一些代表性的国际标准。 1 HDR 发…...

下一个阶段的生产工具更换:

开源竞争&#xff1a; 当你无法彻底掌握技术的时候&#xff0c;就开源这个技术&#xff0c;让更多的人了解这个技术&#xff0c;形成更多的技术依赖&#xff0c;你会说&#xff0c;这不就是在砸罐子吗&#xff1f;一个行业里面总会有人砸罐子的&#xff0c;你不如先砸罐子&…...

性能优化--CPU微架构

一 指令集架构 Intel X86, ARM v8, RISC-V 是当今广泛使用的指令架构的实例。 大多数现代架构可以归类为基于通用寄存器的加载和存储型架构&#xff0c;在这种架构下&#xff0c;操作数倍明确指定&#xff0c;只能使用夹在和存储指令访问内存。除提供基本的功能之外&#xff0c…...

3.3_1 差错控制(检错编码)

目录 1、差错来源 全局性 局部性 2、数据链路的差错控制 ​编辑奇偶校验码 CRC循环冗余码 接收端检错过程 1、差错来源 概括来说&#xff0c;传输中的差错都是由于噪声引起的。 全局性 由于线路本身电气特性所产生的随机噪声(热噪声)&#xff0c;是信道固有的&#xf…...

2024年wordpress、d-link等相关的多个cve漏洞poc

⚠️ 漏洞 ✅ CVE-2024-10914 在D-Link DNS-320、DNS-320LW、DNS-325和DNS-340L中发现的漏洞&#xff0c;版本直到20241028 GET /cgi-bin/account_mgr.cgi?cmdcgi_user_add&name%27;id;%27 HTTP/1.1✅ CVE-2024-11305 在Altenergy Power Control Software中发现的关键…...

影视后期学习Ⅰ~

1.DV是光盘 磁带 2.序列就是我们要制作的一个视频。 打开界面显示&#xff1a; 一号面板放的是素材&#xff0c;二号面板叫源监视器面板&#xff08;它的名字需要记住&#xff09;在一号面板点击文件之后&#xff0c;进入二号面板&#xff0c;在二号面板预览没问题后&#xf…...

如何安全删除 Linux 用户帐户和主目录 ?

Linux 以其健壮性和灵活性而闻名&#xff0c;是全球服务器和桌面的首选。管理用户帐户是系统管理的一个基本方面&#xff0c;包括创建、修改和删除用户帐户及其相关数据。本指南全面概述了如何在 Linux 中安全地删除用户帐户及其主目录&#xff0c;以确保系统的安全性和完整性。…...

【软件测试】设计测试用例的万能公式

文章目录 概念设计测试用例的万能公式常规思考逆向思维发散性思维万能公式水杯测试弱网测试如何进行弱网测试 安装卸载测试 概念 什么是测试用例&#xff1f; 测试⽤例&#xff08;Test Case&#xff09;是为了实施测试⽽向被测试的系统提供的⼀组集合&#xff0c;这组集合包…...

【MySQL】字符集与排序规则

1. 字符集 1.1 一些重要的字符集 1.1.1 ASCII 共收录128个字符&#xff0c;包括空格、标点符号、数字、大小写字母和一些不可见字符。由于总共才128个字符&#xff0c;所以可以使用1个字节来进行编码 1.1.2 ISO-8859-1 共收录 256 个字符&#xff0c;是在 ASCII 字符集的基…...

局域网与广域网:探索网络的规模与奥秘(3/10)

一、局域网的特点 局域网覆盖有限的地理范围&#xff0c;通常在几公里以内&#xff0c;具有实现资源共享、服务共享、维护简单、组网开销低等特点&#xff0c;主要传输介质为双绞线&#xff0c;并使用少量的光纤。 局域网一般是方圆几千米以内的区域网络&#xff0c;其特点丰富…...

C++从零到满绩——类和对象(中)

目录 1>>前言 2>>构造函数&#xff08;我称之为初始化函数&#xff09; 3>>析构函数&#xff08;我称之为销毁函数&#xff09; 4>>拷贝构造函数&#xff08;我称之为复制函数&#xff09; 5>>运算符重载 5.2>>赋值运算符重载 ​编辑…...

MFC工控项目实例三十一模拟量转化为工程量

实测工程量值&#xff08;变送器量程最大值-变送器量程最小值&#xff09;/&#xff08;数字量最大值-数字量最小值&#xff09;*&#xff08;当前采集工程量值-零点误差值&#xff09;。 相关程序代码 SEAL_PRESSURE.h class CSEAL_PRESSUREApp : public CWinApp { public:C…...

ESP32学习笔记_FreeRTOS(2)——Queue

摘要(From AI): 在嵌入式系统开发中&#xff0c;任务之间的高效通信是实现多任务协作的关键。FreeRTOS 提供了强大的队列机制&#xff0c;支持任务之间安全、灵活地传递数据&#xff0c;是实现任务同步和事件通知的核心工具。本篇博客将全面解析 FreeRTOS 队列的工作原理和应用…...

Project Manager工程管理器详细说明

1前言 STM32CubeMX的Project Manager工程管理器包含:Project工程管理、 Code Generator代码生成、 Advanced Settings高级设置三部分内容。 这三部分内容决定了工程的一些属性,相关的配置信息比较重要,也容易理解。初学者有必要掌握其中内容。 2Project工程管理 Project工…...

vue el-table表格点击某行触发事件操作栏点击和row-click冲突问题

文章为本新手菜鸡的问题记录&#xff0c;如有错误和不足还请大佬指正 文章目录 前言一、点击el-table表格某行&#xff0c;触发事件二、解决el-table的操作栏点击和row-click冲突问题1.问题&#xff1a;2.解决方法 前言 文章主要解决两个问题&#xff1a; 1、点击el-table表格…...

吸猫毛空气净化器哪个好?推荐除猫毛效果好的宠物空气净化器品牌

我家里养了五只猫咪&#xff0c;每天睡醒准备来杯咖啡的时候&#xff0c;总能看见猫毛。最尴尬的是这种现象&#xff0c;哪哪都有。养猫人真的每天都要生活在这个世界里面。平时和猫咪玩耍的时候也没有发现这么多猫毛啊。现在一到冬天不能开窗户&#xff0c;真的超级难受感觉每…...

Reactor 模式的理论与实践

1. 引言 1.1 什么是 Reactor 模式&#xff1f; Reactor 模式是一种用于处理高性能 I/O 的设计模式&#xff0c;专注于通过非阻塞 I/O 和事件驱动机制实现高并发性能。它的核心思想是将 I/O 操作的事件分离出来&#xff0c;通过事件分发器&#xff08;Reactor&#xff09;将事…...

VSCode 汉化教程【简洁易懂】

VSCode【下载】【安装】【汉化】【配置C环境&#xff08;超快&#xff09;】&#xff08;Windows环境&#xff09;-CSDN博客 我们安装完成后默认是英文界面。 找到插件选项卡&#xff0c;搜索“Chinese”&#xff0c;找到简体&#xff08;更具你的需要&#xff09;&#xff08;…...

cookie反爬----普通服务器,阿里系

目录 一.常见COOKIE反爬 普通&#xff1a; 1. 简介 2. 加密原理 二.实战案例 1. 服务器响应cookie信息 1. 逆向目标 2. 逆向分析 2. 阿里系cookie逆向 1. 逆向目标 2. 逆向分析 实战&#xff1a; 无限debugger原理 1. Function("debugger").call() 2. …...

【计算机网络】计算机网络概述

当我们决定要谈谈网络的时候&#xff0c;我想在谈之前&#xff0c;有必要了解一下“协议”这个词。协议&#xff0c;定义了在俩个或者多个通信实体之间交换报文的格式和次序&#xff0c;以及报文发送、接收报文或者其他的事件所采取的动作。定义都比较晦涩&#xff0c;那就让我…...

微信小程序条件渲染与列表渲染的全面教程

微信小程序条件渲染与列表渲染的全面教程 引言 在微信小程序的开发中,条件渲染和列表渲染是构建动态用户界面的重要技术。通过条件渲染,我们可以根据不同的状态展示不同的内容,而列表渲染则使得我们能够高效地展示一组数据。本文将详细讲解这两种渲染方式的用法,结合实例…...

MySQL--存储引擎

目录 1 MySQL体系结构 2 存储引擎简介 3 存储引擎特点 3.1 InnoDB 3.1.1 介绍 3.1.2 特点 3.1.3 文件 3.2 逻辑存储结构 3.3 MyISAM 3.3.1 介绍 3.3.2 特点 3.3.3 文件 3.4 Memory 3.3.1 介绍 3.3.2 特点 3.3.3 文件 4 存储引擎选择 Innodb MyISAM MEMORY …...

洛谷 B2038:奇偶 ASCII 值判断

【题目来源】https://www.luogu.com.cn/problem/B2038http://shnoip.openjudge.cn/level1/39/【题目描述】 任意输入一个字符&#xff0c;判断其 ASCII 是否是奇数&#xff0c;若是&#xff0c;输出 YES&#xff0c;否则&#xff0c;输出 NO。 例如&#xff0c;字符 A 的 ASCII…...

软件测试面试之常规问题

1.描述一下测试过程 类似题目:测试的生命周期 思路:这是一个“范围”很大的题目&#xff0c;而且回答时间一般在3分钟之内&#xff0c;不可能非常详细的描述整个过程&#xff0c;因此答题的思路要从整体结构入手&#xff0c;不要过细。为了保证答案的准确性&#xff0c;可以引…...

Android 天气APP(三十七)新版AS编译、更新镜像源、仓库源、修复部分BUG

上一篇&#xff1a;Android 天气APP&#xff08;三十六&#xff09;运行到本地AS、更新项目版本依赖、去掉ButterKnife 新版AS编译、更新镜像源、仓库源、修复部分BUG 前言正文一、更新镜像源① 腾讯源③ 阿里源 二、更新仓库源三、修复城市重名BUG四、地图加载问题五、源码 前…...

网络性能及IO性能测试工具

文章目录 简介IO性能pidstatiostatfioblktrace 网络性能ipeftrek 简介 网络性能和IO性能测试工具在现代计算环境中至关重要。无论是评估网络带宽、优化数据传输速度&#xff0c;还是检测磁盘读写性能&#xff0c;选择适合的工具至关重要。本文将介绍各种网络性能和IO性能测试工…...

1+X应急响应(网络)常见网络攻击-SQL注入:

常见网络攻击-SQL注入&#xff1a; SQL注入概述&#xff1a; 动态网站的工作流程&#xff1a; SQL注入的起源&#xff1a; SQL典型的攻击手段&#xff1a; SQL注入的危害&#xff1a; SQL注入的函数&#xff1a; SQL注入类型&#xff1a; 提交方式分类&#xff1a; Get注入&am…...

流式上传与分片上传的原理与实现

&#x1f680; 博主介绍&#xff1a;大家好&#xff0c;我是无休居士&#xff01;一枚任职于一线Top3互联网大厂的Java开发工程师&#xff01; &#x1f680; &#x1f31f; 在这里&#xff0c;你将找到通往Java技术大门的钥匙。作为一个爱敲代码技术人&#xff0c;我不仅热衷…...

基于深度学习CNN算法的花卉分类识别系统01--带数据集-pyqt5UI界面-全套源码

文章目录 基于深度学习算法的花卉分类识别系统一、项目摘要二、项目运行效果三、项目文件介绍四、项目环境配置1、项目环境库2、环境配置视频教程 五、项目系统架构六、项目构建流程1、数据集2、算法网络Mobilenet3、网络模型训练4、训练好的模型预测5、UI界面设计-pyqt56、项目…...

【数电】常见组合逻辑电路设计和分析

1.基于数据选择器设计 1.1卡诺图降维 1.2实例分析 1.2.1例题一 1.2.2例题二 1.2.2例题三 2.基于译码器设计 3.参考资料 1.《数字电子技术基础 第六版》 阎石王红 2.卡诺图的降维_哔哩哔哩_bilibili...

攻防世界-web ics-06 [解法思路]

进入环境 点击左边的列表只有报表中心有反应 注意看url直接就是index.php?id1 我先试了sqlmap不行&#xff0c;然后就沉淀了一下 想到了id后面的参数问题&#xff0c;我谁便改了几个数都没反应 就想着用bp抓包爆一下这个参数&#xff0c;用了一个数字10000的字典 发现2333…...

传智杯 3-初赛:终端

题目描述&#xff1a; 有一天您厌烦了电脑上又丑又没用的终端&#xff0c;打算自己实现一个 Terminal。具体来说&#xff0c;它需要支持如下命令: 1. touch filename&#xff1a;如果名为 filename 的文件不存在&#xff0c;就创建一个这样的文件&#xff0c;如果已经存在同名…...

《数据结构》学习系列——图(中)

系列文章目录 目录 图的遍历深度优先遍历递归算法堆栈算法 广度优先搜索 拓扑排序定义定理算法思想伪代码 关键路径基本概念关键活动有关量数学公式伪代码时间复杂性 图的遍历 从给定连通图的某一顶点出发&#xff0c;沿着一些边访问遍图中所有的顶点&#xff0c;且使每个顶点…...

网络安全,文明上网(2)加强网络安全意识

前言 在当今这个数据驱动的时代&#xff0c;对网络安全保持高度警觉已经成为每个人的基本要求。 网络安全意识&#xff1a;信息时代的必备防御 网络已经成为我们生活中不可或缺的一部分&#xff0c;信息技术的快速进步使得我们对网络的依赖性日益增强。然而&#xff0c;网络安全…...

Laravel对接SLS日志服务

Laravel对接SLS日志服务&#xff08;写入和读取&#xff09; 1、下载阿里云的sdk #通过composer下载 composer require alibabacloud/aliyun-log-php-sdk#对应的git仓库 https://github.com/aliyun/aliyun-log-php-sdk2、创建sdk请求的service <?phpnamespace App\Ser…...

Kafka 工作流程解析:从 Broker 工作原理、节点的服役、退役、副本的生成到数据存储与读写优化

Kafka&#xff1a;分布式消息系统的核心原理与安装部署-CSDN博客 自定义 Kafka 脚本 kf-use.sh 的解析与功能与应用示例-CSDN博客 Kafka 生产者全面解析&#xff1a;从基础原理到高级实践-CSDN博客 Kafka 生产者优化与数据处理经验-CSDN博客 Kafka 工作流程解析&#xff1a…...

基于 Flink 的车辆超速监测与数据存储的小实战

基于 Flink 的车辆超速监测与数据存储的小实战 一、实战背景与目标 在智能交通管理领域&#xff0c;实时监控车辆行驶速度并精准识别超速行为对于保障道路交通安全和维护交通秩序具有至关重要的意义。本项目旨在构建一个高效的数据处理系统&#xff0c;能够从 Kafka 的 topic…...

Shell 脚本基础(7):重定向详解

内容预览 ≧∀≦ゞ Shell 脚本基础&#xff08;7&#xff09;&#xff1a;重定向详解声明1. 重定向基础概念1.1 输出重定向&#xff08;> 和 >>&#xff09;覆盖写入&#xff08;>&#xff09;追加写入&#xff08;>>&#xff09;输出到终端和文件&#xff0…...

04. 流程控制

一、流程控制 流程控制就是用来控制程序运行中各语句执行顺序的语句。基本的流程结构为&#xff1a;顺序结构&#xff0c;分支结构&#xff08;或称选择结构&#xff09;&#xff0c;循环结构。 顺序结构&#xff1a;程序自上到下执行&#xff0c;中间没有任何判断和跳转&…...

基于卡尔曼滤波器的 PID 控制

基于卡尔曼滤波器的PID控制算法结合了经典控制理论和现代信号处理技术。卡尔曼滤波器&#xff08;Kalman Filter, KF&#xff09;可以对噪声数据进行平滑处理&#xff0c;从而改善PID控制器的性能&#xff0c;特别是在处理具有噪声和不确定性的系统时。以下是详细的设计过程&am…...