Lock mechanism description
mutex
mutex lock
Used to protect resources under multi-threading. Only one thread can read and write at the same time.
std::mutex is the mutex lock (Mutex) class provided in the C++ standard library, which is used to implement mutually exclusive access between multiple threads.
It provides two main operations: lock() and unlock().
When a thread calls the lock() function, if the lock is not held by another thread, then the thread will acquire the lock.
Otherwise, it blocks until the lock is released. And when a thread calls the unlock() function, it releases the lock it acquired previously.
condition_variable
condition variable
Used for synchronization operations under multi-threads: A->B->A->B
std::condition_variable is the condition variable (Condition Variable) class provided in the C++ standard library.
Condition variables are a synchronization mechanism between threads.
It allows a thread to wait under a certain condition, and other threads can notify the waiting thread to continue execution when this condition is met.
Lock code in consumer producer mode
std::mutex mutex_; // Define mutex lock std::condition_variable cond_; // Define condition variable void producer() {<!-- --> // Production Data {<!-- --> std::unique_lock<std::mutex> lock(mutex_); // Production Data } // Automatically release the lock when leaving the scope cond_.notify_one(); // Notify a waiting consumer thread } void consumer() {<!-- --> std::unique_lock<std::mutex> lock(mutex_); cond_.wait(lock); // Wait for condition variable notification, release the lock at the same time, and reacquire the lock when notified //Consumption data }
Why two variables are needed
-
std::mutex (mutex lock): std::mutex is used to protect shared resources and ensure that only one thread can access shared resources at any time. When a thread needs to modify a shared resource, it must first lock the mutex to ensure that no other thread modifies the resource at the same time. In the above example, std::mutex is used to protect the reading and writing of shared data and prevent race conditions caused by multiple threads modifying data at the same time.
-
std::condition_variable (condition variable): std::condition_variable provides a mechanism that allows a thread to wait when a specific condition is met, while allowing other threads to notify the waiting thread to continue execution when the condition is met. In the above example, std::condition_variable is used to establish communication between threads. When a thread is waiting for a certain condition to be true, it will actively give up the lock through the wait() function of std::condition_variable and enter the waiting state. At a certain moment, another thread notifies the waiting thread that the conditions have been met through the notify_one() or notify_all() function of the condition variable and execution can continue.
C++11 new features lock_guard
and unique_lock
lock_guard
Automatically lock and unlock within scope
No need to manually manage lock release
scenes to be used:
- When mutually exclusive access to a resource within a certain scope is required, std::lock_guard can be used. Because its life cycle and scope are relatively small, it is more lightweight
Sample code:
std::mutex mutex_; {<!-- --> std::lock_guard<std::mutex> lock(mutex_); // Automatically lock and unlock within the scope //Perform operations that require mutually exclusive access } // Automatically release the lock when the scope ends
unique_lock
Automatically release the lock at the end of the scope
Manually control the locking and unlocking of locks within the scope
scenes to be used:
- When you need to more flexibly control the life cycle of the lock, or need to execute some code that may cause exceptions during the lock, you can choose to use std::unique_lock
Sample code:
std::mutex mutex_; {<!-- --> std::unique_lock<std::mutex> lock(mutex_); // Manually control the locking and unlocking of the lock //Perform operations that require mutually exclusive access lock.unlock(); // Manually release the lock //Perform operations that do not require mutually exclusive access } // Automatically release the lock when the scope ends
Lock queue example demo
avpacketqueue.h
#ifndef AVPACKETQUEUE_H #define AVPACKETQUEUE_H #include <mutex> #include <condition_variable> #include <queue> #include <list> #ifdef __cplusplus extern "C" {<!-- --> // Contains ffmpeg header files //#include "libavutil/avutil.h" //#include "libavformat/avformat.h" #include "libavcodec/avcodec.h" } #endif /* packet queue - The underlying structure uses a double linked list */ class AVPacketQueue {<!-- --> public: AVPacketQueue(); ~AVPacketQueue(); //Enter the queue int Push(AVPacket *val); //dequeue AVPacket *Pop(const int timeout); //Get the queue size int Size(); private: //Release resource interface void release(); //Control the total playback time of data packets in the queue, unit s int duration_cur=0; int duration_MAX=300;//The maximum cache is 5 minutes, 5*60=300s //Multi-thread synchronization settings std::mutex mutex_; //Mutex lock, used to prevent resource competition std::condition_variable cond_; //pv condition variable, used for thread synchronization //Linked list queue, because it is audio and video data, just save the package pointer. Do not save the package to avoid memory explosion. std::queue<AVPacket *, std::list<AVPacket *>> queue_; }; #endif // AVPACKETQUEUE_H
avpacketqueue.cpp
#include "avpacketqueue.h" //Constructor AVPacketQueue::AVPacketQueue() {<!-- --> } //destructor AVPacketQueue::~AVPacketQueue() {<!-- --> } //Enter the queue, put the package into the queue int AVPacketQueue::Push(AVPacket *val,const int timeout) {<!-- --> //Add scope mutex lock: the resource can only be accessed by one thread at the same time, and the lock will be automatically released when the scope ends. std::lock_guard<std::mutex> lock(mutex_); //Because it is a packet queue-double linked list //If the queue is full, wait for the timeout time if(duration_cur>=duration_MAX) {<!-- --> // Wait for pop or timeout to wake up cond_.wait_for(lock, std::chrono::milliseconds(timeout), [this] {<!-- --> return duration_cur<duration_MAX; }); } //If the queue is still full, just return -1 directly if(duration_cur>=duration_MAX){<!-- --> return -1; } //Enter the queue queue_.push(val); //Increase queue cache time this->duration_cur + =val->duration; cond_.notify_one();//Condition variable: notify other threads that they can continue execution return 0; } //Dequeue, dequeue the package, get the package AVPacket *AVPacketQueue::Pop(const int timeout) {<!-- --> std::lock_guard<std::mutex> lock(mutex_); //Scope mutex lock //If empty, wait for timeout time if(queue_.empty()) {<!-- --> // Wait for push or timeout to wake up cond_.wait_for(lock, std::chrono::milliseconds(timeout), [this] {<!-- --> return !queue_.empty() || abort_; }); } if(queue_.empty()){<!-- --> return nullptr; } //dequeue AVPacket * val = queue_.pop(); //Reduce queue cache time this->duration_cur-=val->duration; //Get the value of the last node return val; }