c++ 多线程知识汇总
一、std::thread
std::thread
是 C++11 引入的标准库中的线程类,用于创建和管理线程
1. 带参数的构造函数
template <class F, class... Args>
std::thread::thread(F&& f, Args&&... args);F&& f:线程要执行的函数;Args&&... args:可变参数,用于将参数转发到函数 f
2. 方法
void join(); 等待线程结束。 注:线程必须是可 join 的(即线程正在运行且未被分离)
void detach(); 分离线程,使其独立运行。 注:线程必须正在运行且未被分离
void swap(std::thread& other); 交换两个线程对象
std::thread::id get_id() const; 获取线程的唯一标识符
bool joinable() const; 检查线程是否可以被 join
std::thread& operator=(std::thread&& other); 移动赋值运算符,用于将一个线程对象移动到另一个线程对象
3. 简单例子
3.1 普通函数作为线程执行函数
#include <iostream>
#include <thread>void threadFunction(int id) {std::cout << "Thread " << id << " is running" << std::endl;
}int main() {std::thread t(threadFunction, 1); // 创建线程并传递参数if (t.joinable()) {t.join(); // 等待线程结束}// t.dedetach(); // 分离线程 ,分离的线程会在后台独立运行,即使创建它的线程已经结束return 0;
}注意 : 如果需要传引用时,临时变量不行,可以用std::ref(变量名)把他转成引用类型传递指针或引用时不能用临时变量。离开作用域就销毁了,线程函数里就取不到了临时的类对象也不行,都会导致错误,总之,要控制好声明周期,保证线程执行时能正确使用。
3.2 类的成员函数作为线程执行函数
#include <iostream>
#include <thread>
#include <functional>class MyClass {
public:void memberFunction(int id) {std::cout << "Thread " << id << " is running in MyClass" << std::endl;}
};int main() {MyClass obj;std::thread t(std::bind(&MyClass::memberFunction, &obj, 1)); // 使用 std::bind 绑定成员函数和对象t.join();return 0;
}
4. 互斥量
线程安全:线程安全的代码能够保证在并发执行时,程序的行为符合预期,且不会因线程之间的竞争条件(Race Condition)而产生错误。下面代码,线程不安全,输出是不固定的。
int shared_data = 0;void myThread()
{for (int i=0; i < 10000; ++ i){shared_data++;}
}int main()
{std::thread t1(myThread);std::thread t2(myThread);t1.join();t2.join();std::cout<<"shared_data = "<<shared_data<<std::endl;return 0;
}
互斥量是一种同步原语,用于保护共享资源,防止多个线程同时访问。互斥量确保同一时间只有一个线程可以持有互斥量,从而保证对共享资源的互斥访问。确保线程安全。
C++11 引入了 <mutex>
头文件,其中定义了多种类型的互斥量。
4.1 std::mutex
std::mutex
是最基本且最常用的互斥量类型,它不支持递归加锁。
int shared_data = 0;
std::mutex mtx; // 定义互斥量void myThread()
{mtx.lock();for (int i=0; i < 10000; ++ i){shared_data++;}mtx.unlock();
}int main()
{std::thread t1(myThread);std::thread t2(myThread);t1.join();t2.join();std::cout<<"shared_data = "<<shared_data<<std::endl;return 0;
}此时无论运行多少次,输出都是相同的。
4.2 std::lock_guard
为了简化互斥量的使用 C++ 提供了 std::lock_guard
它是一个 RAII 风格的工具,用于自动管理互斥量的加锁和解锁。lock_guard 对象
不能复制或移动,因此他只能在局部作用域中。
#include <iostream>
#include <thread>
#include <mutex>int shared_data = 0;
std::mutex mtx; // 定义互斥量void myThread()
{std::lock_guard<std::mutex> lock(mtx); // 自动加锁(析构中自动解锁)for (int i=0; i < 10000; ++ i){shared_data++;}
}int main()
{std::thread t1(myThread);std::thread t2(myThread);t1.join();t2.join();std::cout<<"shared_data = "<<shared_data<<std::endl;return 0;
}
4.3 std::unique_lock
4.3.1 概念
std::unique_lock 是 C++ 标准库中用于管理互斥锁的一种智能锁类,它提供了比 std::lock_guard
更灵活的锁管理功能。std::unique_lock 不仅可以自动管理锁的获取和释放,还可以在需要时手动控制锁的行为,例如延迟锁的获取、尝试锁的获取以及在特定条件下释放锁等。
4.3.2 特点
std::unique_lock允许在构造时延迟锁的获取,或者在运行时尝试获取锁,而 std::lock_guard
只能在构造时立即获取锁
4.3.3 方法
// 绑定到互斥量的构造函数
std::unique_lock(std::mutex& m):立即获取互斥量 m。
std::unique_lock(std::mutex& m, std::defer_lock_t):延迟获取互斥量 m,需要后续手动调用 lock()。
std::unique_lock(std::mutex& m, std::try_to_lock_t):尝试立即获取互斥量 m,如果失败则不会阻塞。
std::unique_lock(std::mutex& m, std::adopt_lock_t):假设互斥量 m 已经被当前线程锁定,std::unique_lock 只是接管锁的所有权。成员函数void lock(); 尝试获取互斥量。如果互斥量已被其他线程锁定,则当前线程会阻塞,直到互斥量可用。
bool try_lock(); 尝试立即获取互斥量。如果成功获取,则返回 true;如果失败,则返回 false,且不会阻塞。template <class Rep, class Period>
bool try_lock_for(const std::chrono::duration<Rep, Period>& rel_time);
尝试在指定的相对时间 rel_time 内获取互斥量。template <class Clock, class Duration>
bool try_lock_until(const std::chrono::time_point<Clock, Duration>& abs_time);
尝试在指定的绝对时间 abs_time 之前获取互斥量void unlock();
释放互斥量。如果互斥量未被当前线程锁定,则行为未定义。
bool owns_lock() const;
返回 true 如果当前 std::unique_lock 对象拥有互斥量的所有权,否则返回 false。
std::mutex* mutex() const;
返回绑定的互斥量的指针,如果没有绑定互斥量,则返回 nullptr。
void swap(std::unique_lock& other);
交换当前对象和另一个 std::unique_lock 对象的状态
std::mutex* release();
返回指向它所管理的互斥量的指针,并释放所有权。
4.3.4 代码示例
① 立即获取锁,与lock_guard相同,立即获取锁
int shared_data = 0;
std::mutex mtx;void myThread()
{std::unique_lock<std::mutex> u_lock(mtx);for (int i=0; i < 10000; ++ i){shared_data++;}
}int main()
{std::thread t1(myThread);std::thread t2(myThread);t1.join();t2.join();std::cout<<"shared_data = "<<shared_data<<std::endl;return 0;
}
② 尝试立即加锁
std::mutex mtx;
void foo() {std::unique_lock<std::mutex> lock(mtx, std::try_to_lock); // 尝试立即加锁if (lock.owns_lock()) {// 成功获取锁// 临界区代码} else {// 未能获取锁}
}
③ 尝试在指定的时间段内获取互斥锁
需要结合可超时互斥量,后面介绍。
int shared_data = 0;
std::timed_mutex mtx; // 可超时互斥量void myThread()
{// 构造函数传入defer_lock 表示构造但是不加锁,需要手动加锁,结合延迟使用std::unique_lock<std::timed_mutex> u_lock(mtx, std::defer_lock);//尝试加锁,只等待1秒,1秒内没获取到资源直接返回了if (u_lock.try_lock_for(std::chrono::seconds(1))){std::this_thread::sleep_for(std::chrono::seconds(1));// 睡三秒for (int i=0; i < 10000; ++ i){shared_data++;}}
}int main()
{std::thread t1(myThread);std::thread t2(myThread);t1.join();t2.join();std::cout<<"shared_data = "<<shared_data<<std::endl;return 0;
}
// 输出1000 或两千
⑤ try_lock_until 等待获取资源,超过了指定时间,就不阻塞了,直接返回。(不常用)
4.4 std::timed_mutex
4.4.1 概念
std::timed_mutex 是 C++ 标准库中 <mutex>
头文件定义的一种互斥量类型,它是一种可超时的互斥量,提供了在尝试锁定时支持超时的功能。它继承自 std::mutex
,和普通互斥量一样,std::timed_mutex
保证同一时刻只有一个线程可以持有锁,用于保护共享资源。
4.4.2 主要成员函数
1. lock()
阻塞当前线程,直到获取锁为止。
如果其他线程已经持有锁,则当前线程会阻塞,直到锁被释放。
2. try_lock()
尝试立即获取锁。
如果锁当前可用,则获取锁并返回 true;如果锁已被占用,则返回 false,不会阻塞。
3. try_lock_for(std::chrono::duration)
尝试在指定的时间间隔内获取锁。
如果在超时期间内获取到锁,则返回 true;否则返回 false。
4. try_lock_until(std::chrono::time_point)
尝试在指定的时间点之前获取锁。
如果在指定时间点之前获取到锁,则返回 true;否则返回 false。
5. unlock()
释放锁,允许其他线程获取锁。
4.5 互斥量死锁
4.5.1 死锁概念
死锁指的是两个或多个线程因为相互等待对方持有的资源而无法继续执行的状态。
死锁的四个必要条件
根据死锁的理论,发生死锁需要同时满足以下四个必要条件:
互斥条件(Mutual Exclusion):
资源不能被共享,只能被一个线程独占使用。
例如,互斥量(std::mutex)确保同一时间只有一个线程可以访问资源。
请求和保持条件(Hold and Wait):
一个线程已经持有了某个资源,但又请求其他资源,而这些资源已经被其他线程持有。
例如,线程 A 持有资源 X,但请求资源 Y;线程 B 持有资源 Y,但请求资源 X。
不可剥夺条件(No Preemption):
已经分配给线程的资源不能被强制剥夺,只能由持有资源的线程主动释放。
例如,互斥量一旦被线程加锁,其他线程无法强制解锁。
循环等待条件(Circular Wait):
存在一个线程等待资源的循环链,即线程 A 等待线程 B 持有的资源,线程 B 等待线程 C 持有的资源,线程 C 等待线程 A 持有的资源。
例如,线程 A 等待资源 Y,线程 B 等待资源 X,而资源 X 和 Y 分别被线程 B 和线程 A 持有。
4.5.2 死锁的示例
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx1, mtx2;void threadFunction1() {std::cout << "Thread 1: Trying to lock mtx1" << std::endl;mtx1.lock();std::cout << "Thread 1: Locked mtx1, trying to lock mtx2" << std::endl;mtx2.lock();std::cout << "Thread 1: Locked mtx2" << std::endl;// 业务逻辑std::this_thread::sleep_for(std::chrono::seconds(1));mtx2.unlock();mtx1.unlock();
}void threadFunction2() {std::cout << "Thread 2: Trying to lock mtx2" << std::endl;mtx2.lock();std::cout << "Thread 2: Locked mtx2, trying to lock mtx1" << std::endl;mtx1.lock();std::cout << "Thread 2: Locked mtx1" << std::endl;// 业务逻辑std::this_thread::sleep_for(std::chrono::seconds(1));mtx1.unlock();mtx2.unlock();
}int main() {std::thread t1(threadFunction1);std::thread t2(threadFunction2);t1.join();t2.join();return 0;
}
4.5.3 避免死锁的方法
① 固定加锁顺序
② 使用 std::try_lock
或超时机制
5. std::call_once(只能在线程里使用)
5.1 概念
std::call_once 是 C++11 引入的一个非常有用的工具,用于确保某个函数或操作只被调用一次,即使在多线程环境中也能保证线程安全。它常用于实现单例模式、初始化全局资源等场景。
5.2 单例模式,多线程情况下,初始化可能被执行多次,违背了单例模式,可以把初始化操作封到一个成员函数里,然后把这个函数加上 call_once 。避免被多次初始化。
6. std::condition_variable 条件变量
6.1 概念
std::condition_variable 是 C++ 标准库中用于线程同步的工具之一,它允许线程在某些条件尚未满足时进入休眠状态,并在条件满足时被唤醒。 通常与互斥锁(std::mutex
或 std::timed_mutex
)配合使用,以实现线程间的协调和同步。
6.2 生产者与消费者模型
生产者与消费者之间存在一个任务队列,生产者负责产生任务,存入任务队列,并通知消费者需要干活了,消费者在任务队列为空时就会等待。直到有数据了并接到了生产者的通知(唤醒),被唤醒后则从队列中取出任务执行。
6.3 代码示例
wait的实现原理:函数接收一个锁定的对象,和一个谓词。当调用时,先释放锁,当前线程进入休眠,等待被唤醒,被唤醒时尝试获取锁,判断谓词是否为真,都满足了该线程继续执行,不满足则继续调用wait睡眠(谓词就是可以避免虚假唤醒)。
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>
#include <atomic>// 共享资源
std::queue<int> data_queue; // 数据队列
std::mutex mtx; // 互斥锁
std::condition_variable cv; // 条件变量
std::atomic<bool> done(false); // 标志位,表示生产者是否完成// 生产者函数
void producer() {for (int i = 0; i < 10; ++i) {std::unique_lock<std::mutex> lock(mtx);data_queue.push(i); // 生产数据并放入队列std::cout << "Produced: " << i << std::endl;cv.notify_one(); // 唤醒一个消费者std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟生产时间}done = true; // 生产者完成cv.notify_all(); // 唤醒所有消费者
}// 消费者函数
void consumer() {while (true) {std::unique_lock<std::mutex> lock(mtx);cv.wait(lock, [] { return !data_queue.empty() || done.load(); }); // 等待队列非空或生产者完成if (done.load() && data_queue.empty()) {break; // 如果生产者完成且队列为空,退出循环}int data = data_queue.front(); // 从队列中取出数据data_queue.pop();std::cout << "Consumed-------: " << data << std::endl;std::this_thread::sleep_for(std::chrono::milliseconds(150)); // 模拟消费时间}
}int main() {std::thread producer_thread(producer); // 创建生产者线程std::thread consumer_thread(consumer); // 创建消费者线程producer_thread.join(); // 等待生产者线程完成consumer_thread.join(); // 等待消费者线程完成return 0;
}
7. 线程池
7.1 概述
线程池是一种用来管理和复用线程的技术。它通过提前创建一定数量的线程(工作线程),这些线程处于等待状态,等待从任务队列中获取任务并执行。任务执行完毕后,线程返回线程池等待处理下一个任务。线程池的核心思想是减少线程创建和销毁的开销,提高程序的性能和资源利用率。
优势:
①减少线程创建和销毁的开销:
线程的创建和销毁是一个相对耗时的操作,线程池通过复用线程避免了频繁创建和销毁线程的开销。
②提高程序的并发性能:
线程池可以合理控制线程的数量,避免因线程过多导致系统资源耗尽,从而提高程序的并发性能。
③简化线程管理:
线程池提供了一种统一的线程管理机制,使得线程的创建、调度和销毁更加方便和高效
7.2 示例代码
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <functional>
#include <vector>
#include <memory>
#include <future>class ThreadPool {
public:ThreadPool(size_t num_threads);~ThreadPool();template <class F, class... Args>auto enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type>;private:std::vector<std::thread> workers;std::queue<std::function<void()>> tasks;std::mutex queue_mutex;std::condition_variable condition;bool stop;
};ThreadPool::ThreadPool(size_t num_threads) : stop(false) {for (size_t i = 0; i < num_threads; ++i) {workers.emplace_back([this] {while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(this->queue_mutex);this->condition.wait(lock, [this] { return this->stop || !this->tasks.empty(); });if (this->stop && this->tasks.empty()) {return;}task = std::move(this->tasks.front());this->tasks.pop();}task();}});}
}ThreadPool::~ThreadPool() {{std::unique_lock<std::mutex> lock(queue_mutex);stop = true;}condition.notify_all();for (std::thread& worker : workers) {worker.join();}
}template <class F, class... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args) -> std::future<typename std::result_of<F(Args...)>::type> {using return_type = typename std::result_of<F(Args...)>::type;auto task = std::make_shared<std::packaged_task<return_type()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));std::future<return_type> res = task->get_future();{std::unique_lock<std::mutex> lock(queue_mutex);if (stop) {throw std::runtime_error("enqueue on stopped ThreadPool");}tasks.emplace([task]() { (*task)(); });}condition.notify_one();return res;
}// 示例用法
int main() {ThreadPool pool(4);auto result1 = pool.enqueue([](int answer) { return answer; }, 42);auto result2 = pool.enqueue([](int a, int b) { return a + b; }, 5, 7);std::cout << "Result 1: " << result1.get() << std::endl;std::cout << "Result 2: " << result2.get() << std::endl;return 0;
}
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <vector>
#include <functional>class TheradPool
{
public:TheradPool(int thread_num) : stop_(false){for (int i=0; i < thread_num; ++i){threads_.emplace_back([this](){while(1){std::unique_lock<std::mutex> uptr_lock(mtx_);condition_.wait(uptr_lock, [this](){return !tasks_.empty() || stop_;});if (stop_ && tasks_.empty()){return;}std::function<void()> task(std::move(tasks_.front()));tasks_.pop();uptr_lock.unlock();task();}});}}// F 是函数参数类型,表示可调用对象的类型(如函数指针、lambda 表达式、函数对象等)
// Args... 是可变参数模板,表示可调用对象的参数类型列表。... 表示参数包,可以接受任意数量和类型的参数template <typename F, typename... Args>void enqueue(F&& f, Args&&... args){// F&& f 是一个右值引用,表示可调用对象。使用右值引用是为了支持完美转发,//即可以将 f 作为左值或右值传递给后续的调用std::function<void()> task = std::bind(std::forward<F> (f), std::forward<Args>(args)...);{std::unique_lock<std::mutex> uptr_lock(mtx_);tasks_.emplace(std::move(task));}condition_.notify_one();}~TheradPool(){// 控制锁的作用域,设置完stop_立即释放锁,确保其他线程能拿到锁,避免死锁{std::unique_lock<std::mutex> uptr_lock(mtx_);stop_ = true;}condition_.notify_all();for (auto &thread : threads_){thread.join();}}private:std::vector<std::thread> threads_;std::queue<std::function<void()>> tasks_;std::mutex mtx_;std::condition_variable condition_;bool stop_;};
8. 异步并发 (async , future)
8.1 async
std::
async 是 C++11 引入的一个用于异步任务执行的函数模板,它提供了一种简单的方式来启动一个任务,并通过 std::future
获取任务的返回值。
std::async
:用于启动一个异步任务,返回一个 std::future
对象,通过该对象可以获取任务的返回值或处理任务中的异常;std::future
:表示异步操作的结果,可以用来获取异步任务的返回值或检查任务是否完成。
std::future<R> std::async(std::launch policy, F&& f, Args&&... args);
返回值:std::async 返回一个 std::future<R> 对象,其中 R 是函数 f 的返回类型。std::future 是一个用于获取异步操作结果的对象。
参数:
std::launch policy:启动策略,用于指定任务的执行方式。
F&& f:可调用对象(如函数指针、lambda 表达式、函数对象等)。
Args&&... args:可调用对象的参数列表,支持可变参数。
#include <iostream>
#include <future>
#include <thread>
#include <chrono>int add(int a, int b) {std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作return a + b;
}int main() {std::future<int> result = std::async(add, 5, 3); // 使用默认策略std::cout << "Waiting for the result..." << std::endl;int sum = result.get(); // 阻塞等待任务完成std::cout << "Result: " << sum << std::endl;return 0;
}
8.2 std::packaged_task
std::packaged_task 是 C++11 引入的一个模板类,用于包装可调用对象(如函数、lambda 表达式、函数对象等),使其能够异步执行,并通过 std::future
获取任务的执行结果。
#include <iostream>
#include <future>
#include <thread>// 一个简单的函数,计算整数的平方
int square(int x) {return x * x;
}int main() {// 创建一个 std::packaged_task,包装 square 函数std::packaged_task<int(int)> task(square);// 获取与 packaged_task 关联的 std::future 对象std::future<int> result = task.get_future();// 将任务交给一个线程异步执行std::thread t(std::move(task), 10); // 传递参数 10 给 square 函数// 等待任务完成并获取结果int value = result.get();std::cout << "The square of 10 is " << value << std::endl;t.join(); // 等待线程结束return 0;
}
8.3 std::promise
std::promise 是 C++11 引入的一个模板类,用于存储异步操作的结果,并通过 std::future
将结果传递给其他线程。std::promise
和 std::future
通常一起使用,用于实现线程间的异步通信和结果传递。
主要功能
存储异步操作的结果:std::promise 可以存储一个值或异常,作为异步操作的结果。
与 std::future 关联:通过 std::promise 的 get_future() 方法,可以获取一个与之关联的 std::future 对象。
设置结果:通过 std::promise 的 set_value() 方法设置操作的返回值,或通过 set_exception() 方法设置异常。
线程间通信:std::promise 和 std::future 提供了一种机制,允许在不同线程之间安全地传递异步操作的结果。
#include <iostream>
#include <future>
#include <thread>
#include <chrono>void worker(std::promise<int> promise) {std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作promise.set_value(42); // 设置异步操作的结果
}int main() {std::promise<int> promise; // std::promise 对象,用于存储异步操作的结果std::future<int> future = promise.get_future(); // 获取与 promise 关联的 future 对象std::thread t(worker, std::move(promise)); // 启动一个线程,将 promise 传递给 worker 函数std::cout << "Waiting for the result..." << std::endl;int result = future.get(); // 阻塞等待结果std::cout << "Result: " << result << std::endl;t.join(); // 等待线程结束return 0;
}
8.4 std::async 和 std::thread 有什么区别?
std::async
和 std::thread
都是 C++ 标准库中用于实现并发和多线程的工具,但它们在功能、使用方式和用途上存在一些重要区别。
std::thread
它直接创建一个线程,并且完全由用户控制线程的生命周期。适合需要手动管理线程的场景,例如需要精确控制线程的创建、启动、销毁以及线程间的同步;
std::async
它用于启动一个异步任务,并且返回一个 std::future
对象,用于获取异步任务的结果。适合用于启动一个异步任务,并且希望在任务完成后获取结果的场景。它隐藏了线程管理的细节,更侧重于任务的异步执行。
std::thread
没有返回值 std::async
返回一个 std::future
对象 通过 std::future
的 get()
或 wait()
方法可以获取异步任务的返回值。
-
std::thread
-
性能:创建和销毁线程的开销较大,特别是频繁创建和销毁线程时。
-
资源管理:需要手动管理线程资源,容易出现资源泄漏或线程死锁等问题。
-
-
std::async
-
性能:内部可能使用线程池,减少了线程的创建和销毁开销。
-
资源管理:由标准库管理,减少了资源管理的复杂性。
-
9. 原子操作 atomic
std::atomic
是 C++11 标准库中引入的一个模板类,用于实现原子操作。它确保对变量的读取、修改和存储操作是不可分割的(即原子的),即使在多线程环境下也不会出现中间状态被其他线程干扰的情况.
常用类型
C++ 标准库提供了多种原子类型,包括:
std::atomic<bool>:原子布尔类型
std::atomic<int>:原子整数类型
std::atomic<T*>:原子指针类型
std::atomic<std::shared_ptr<T>>:原子共享指针类型
#include <iostream>
#include <atomic>
#include <thread>std::atomic<int> counter(0);void increment_counter() {for (int i = 0; i < 100000; ++i) {counter.fetch_add(1, std::memory_order_relaxed); // 原子加1}
}int main() {std::thread t1(increment_counter);std::thread t2(increment_counter);t1.join();t2.join();std::cout << "Counter value: " << counter.load() << std::endl; // 安全地读取值return 0;
}
10. 线程间通信的方式
除了 前面提到的std::promise
和 std::future
,C++ 提供了多种线程间通信机制,每种机制都有其适用场景和特点。以下是一些常见的替代方案:
1. 共享内存 + 同步原语
共享内存是最直接的线程间通信方式,通过全局变量、静态变量或堆上分配的对象实现。为了防止数据竞争,需要使用同步原语(如互斥锁 std::mutex
)来保护对共享变量的访问。
2. 消息队列
消息队列允许线程通过发送和接收消息来交换信息,而不是直接操作共享内存。可以使用 std::queue
和条件变量 std::condition_variable
实现一个简单的线程安全消息队列。
3. 条件变量
条件变量是一种同步原语,允许线程等待某个条件成立,然后被其他线程唤醒。它通常与互斥锁 std::mutex
配合使用,适用于复杂的同步逻辑。
4. 信号量
信号量是一种计数器,用于控制对共享资源的访问。线程可以通过 acquire()
请求资源,通过 release()
释放资源。C++20 引入了 std::counting_semaphore
,可以方便地实现线程间的同步。
5. 读写锁
读写锁(std::shared_mutex
)允许多个线程同时读取共享资源,但写操作需要独占访问。它适用于读多写少的场景。
6. 事件
事件是一种简单的同步机制,线程可以等待某个事件的发生,而其他线程可以触发事件。可以通过条件变量实现。
相关文章:

c++ 多线程知识汇总
一、std::thread std::thread 是 C11 引入的标准库中的线程类,用于创建和管理线程 1. 带参数的构造函数 template <class F, class... Args> std::thread::thread(F&& f, Args&&... args);F&& f:线程要执行的函数&…...

day09_实时类标签/指标
文章目录 day09_实时类标签/指标一、日志数据实时采集2、Flume简介2.3 项目日志数据采集Flume配置2.3.1 涉及的Flume组件和参数2.3.2 Nginx日志采集2.3.3 用户行为日志采集 二、Nginx日志数据统计1、日志格式说明2、数据ETL2.1 日志抽取2.1.1 正则表达式2.1.2 基于Spark实现Ngi…...
【前端开发学习笔记16】Vue_9
文章分类架子 多个页面复用,封装成组件: props 定制标题默认插槽 default 定制内容主体具名插槽 extra 定制按钮 <template><el-card class"page-container"><template #header><div class"header"><s…...

Bash 中的运算方式
目录 概述: 1. (()) 运算符 2. let 命令 3. expr 命令 4. $[] 直接运算 5. bc(计算器,支持浮点数) 6. awk(强大的文本处理工具,也可计算) 概述: Bash 本身只支持整数运算&am…...
2025年3月营销灵感日历
2025年的第一场营销大战已经拉开帷幕了! 三月可是全年最值钱的营销黄金月——妇女节、植树节、315消费者日三大爆点连击,还有春分、睡眠日、世界诗歌日等20隐藏流量密码。 道叔连夜扒了18个行业数据,整理了这份《2025年3月营销灵感日历》&a…...
MySQL的innoDB引擎
一、逻辑存储结构 表空间:ibd文件,一个MySQL实例可以对应多个表空间,用于存储记录,索引等数据; 段:分为数据段(leaf node segment)、索引段(non-leaf node segment)、回滚段(rollback segment),innodb是索引组织表,数据段就是B+树的非叶子节点。段用来管理多个e…...

HCIA项目实践---OSPF的知识和原理总结
9.5 OSPF 9.5.1 从哪些角度评判一个动态路由协议的好坏? (1)选路佳(是否会出环) OSPF 协议采用链路状态算法,通过收集网络拓扑信息来计算最短路径,从根本上避免了路由环路的产生。 (…...

hexo 魔改 | 修改卡片透明度
hexo 魔改 | 修改卡片透明度 ** 博客食物用更佳 博客地址 ** 这是笔者自己瞎倒腾的。作为前端菜鸡一枚,大佬们随便看看就好~ 我用的主题是 butterfly 4.12.0 分析 通过开发者工具可以看出来卡片的背景和 --card-bg 变量有关 再在 sources 下的 css 文件夹下的…...
今日AI和商界事件(2025-02-13)
今日AI领域的主要事件包括: 一、OpenAI相关动态 取消独立发布o3模型计划: OpenAI首席执行官奥尔特曼宣布,公司取消独立发布o3模型的计划。未来几个月内,OpenAI将推出GPT-5,该模型将整合多项技术,并应用于C…...
38.日常算法
1.最短无序连续子数组 题目来源 给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。请你找出符合题意的 最短 子数组,并输出它的长度。 示例 1: 输入…...

如何构建有效的人工智能代理
目录 什么是 AI 代理? 何时应使用 AI 代理? 人工智能代理的构建模块 构建 AI 代理的常用方法 1. 提示链接(分步说明) 2.路由(将任务发送到正确的地方) 3.并行处理(同时做多件事) 4. 协调者和工作者 AI(团队合作) 5. 评估器和优化器(修复错误) 如何让人工…...
qt 事件的传递顺序
在 Qt 中,事件的传递顺序遵循以下基本规则: 事件的产生:当用户与界面交互时,操作(如鼠标点击、键盘输入等)会生成相应的事件(如 QMouseEvent、QKeyEvent 等)。 事件的传递顺序&…...
全面掌握Flutter开发:从核心原理到跨平台实战,构建高效应用
文章目录 引言 一、Flutter框架概述二、Flutter开发环境搭建三、Flutter核心技术解析1. **Widget树与状态管理**2. **路由与导航**3. **网络请求与数据解析**4. **本地存储与数据库**5. **包管理与依赖** 四、实战案例:开发跨平台新闻客户端五、Flutter开发工具与调…...

Flutter 添加 iOS widget 小组件
环境 macOS 15.1 Xcode16.1 Flutter 3.27.4 前言 本篇文章主要记录,在Flutter 项目中如何正确地添加iOS 小组件,iOS 小组件 相关的知识在另一篇文章有记录。 iOS 14 widget 添加小组件 WidgetExtension 打开Xcode New -> Target 选择 iOS -> 搜…...
2025年金三银四经典自动化测试面试题
概述 觉得自动化测试很难? 是的,它确实不简单。但是学会它,工资高啊! 担心面试的时候被问到自动化测试? 嗯,你担心的没错!确实会被经常问到! 现在应聘软件测试工程师的岗位&…...

C++17 中 std::lcm:从入门到精通
文章目录 一、引言二、std::lcm 的基本概念三、入门示例四、计算多个整数的最小公倍数五、std::lcm 的实现原理六、在实际项目中的应用七、注意事项八、总结 一、引言 在 C 编程中,处理数学运算时,计算最小公倍数(Least Common Multiple&…...

初阶c语言(循环语句习题,完结)
前言: c语言为b站鹏哥,嗯对应视频37集 昨天做的c语言,今天在来做一遍,发现做错了 今天改了平均值的计算, 就是说最大值加上最小值,如果说这个数值非常大的话,两个值加上会超过int类型的最大…...

本地Deepseek-r1:7b模型集成到Google网页中对话
本地Deepseek-r1:7b网页对话 基于上一篇本地部署的Deepseek-r1:7b,使用黑窗口对话不方便,现在将本地模型通过插件集成到Google浏览器中 安装Google插件 在Chrome应用商店中搜索page assis 直接添加至Chrome 修改一下语言 RAG设置本地运行的模型&#…...

SSM课设-学生选课系统
【课设者】SSM课设-学生选课系统 分为 管理员 和 老师 和 学生端 技术栈 前端: HtmlCssJavaScriptAjax 后端: Spring、Spring MVC、MyBatis、MySQL、JSP 学生端 --选课 选课 搜索 --查看选课结果 --退选 --查看已修课程 --管理个人信息 老师端 --添加教学课程 添加 …...

Windows中使用Docker安装Anythingllm,基于deepseek构建自己的本地知识库问答大模型,可局域网内多用户访问、离线运行
文章目录 Windows中使用Docker安装Anythingllm,基于deepseek构建自己的知识库问答大模型1. 安装 Docker Desktop2. 使用Docker拉取Anythingllm镜像2. 设置 STORAGE_LOCATION 路径3. 创建存储目录和 .env 文件.env 文件的作用关键配置项 4. 运行 Docker 命令docker r…...

学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...

python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...

回溯算法学习
一、电话号码的字母组合 import java.util.ArrayList; import java.util.List;import javax.management.loading.PrivateClassLoader;public class letterCombinations {private static final String[] KEYPAD {"", //0"", //1"abc", //2"…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...

Golang——9、反射和文件操作
反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一:使用Read()读取文件2.3、方式二:bufio读取文件2.4、方式三:os.ReadFile读取2.5、写…...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...
基于 HTTP 的单向流式通信协议SSE详解
SSE(Server-Sent Events)详解 🧠 什么是 SSE? SSE(Server-Sent Events) 是 HTML5 标准中定义的一种通信机制,它允许服务器主动将事件推送给客户端(浏览器)。与传统的 H…...
使用python进行图像处理—图像滤波(5)
图像滤波是图像处理中最基本和最重要的操作之一。它的目的是在空间域上修改图像的像素值,以达到平滑(去噪)、锐化、边缘检测等效果。滤波通常通过卷积操作实现。 5.1卷积(Convolution)原理 卷积是滤波的核心。它是一种数学运算,…...