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:线程要执行的函数&…...
探索 Text-to-SQL 技术:从自然语言到数据库查询的桥梁
亲爱的小伙伴们😘,在求知的漫漫旅途中,若你对深度学习的奥秘、Java 与 Python 的奇妙世界,亦或是读研论文的撰写攻略有所探寻🧐,那不妨给我一个小小的关注吧🥰。我会精心筹备,在未来…...
Java NIO ByteBuffer 详解
什么是 ByteBuffer ByteBuffer 是 Buffer 的一个具体实现,专门用于存储和操作字节数据。它提供了高效的、基于内存的 I/O 数据处理方式。 Buffer 类是构建 Java NIO 的基础,其中 ByteBuffer 类是 Buffer 子类中最受欢迎的。这是因为字节类型是最通用的…...
【机器学习】简单线性回归算法及代码实现
线性回归算法 一、摘要二、线性回归算法概述三、损失函数的定义和衡量标准四、简单线性回归的求解和应用五、机器学习算法一般求解思路 一、摘要 本文讲解了线性回归算法的基础知识和应用,强调线性回归主要用于解决回归问题。通过分析房产价格与房屋面积的关系&…...
【前端开发】query参数和params参数的区别
在Web开发中,query参数(URL查询参数)和params参数(路由参数)是两种不同的URL传参方式,它们的核心区别如下: 一、 位置不同 query参数params参数位置URL中?之后,用&连接多个参数…...
人工智能数学基础学习PPT
学习视频:人工智能 -数学基础 文章目录 1.简介1.函数2.极限3.无穷小与无穷大4.连续性与导数5.偏导数6.方向导数7.梯度 2.微积分1.微积分基本想法2.微积分的解释3.定积分4.定积分性质5.牛顿-莱布尼茨公式 3.泰勒公式与拉格朗日1.泰勒公式2.一点一世界3.阶数的作用4.…...
企业文件防泄密软件哪个好?
在企业文件防泄密软件领域,天锐绿盾和中科数安都是备受认可的品牌,它们各自具有独特的特点和优势。 以下是对这两款软件的详细比较: 天锐绿盾 功能特点 集成性强:集成了文件加密、数据泄露防护DLP、终端安全管理、行为审计等数据安…...
美丽 百褶裙提示词 + MD
MD 参考教程:Marvelous Designer零基础教学,MD布料制作-百褶裙建模制作,次世代教学_哔哩哔哩_bilibili 【MD新手教程】30分钟教会你制作百褶裙,Marvelous Designer超简单入门案例教程_哔哩哔哩_bilibili 【c4d技术解析】MD百褶裙…...
解释和对比“application/octet-stream“与“application/x-protobuf“
介绍 在现代 Web 和分布式系统的开发中,数据的传输和交换格式扮演着关键角色。为了确保数据在不同系统之间的传输过程中保持一致性,MIME 类型(Multipurpose Internet Mail Extensions)被广泛应用于描述数据的格式和内容类型。在 …...
基于YALMIP和cplex工具箱的微电网最优调度算法matlab仿真
目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 系统建模 4.2 YALMIP工具箱 4.3 CPLEX工具箱 5.完整工程文件 1.课题概述 基于YALMIP和cplex工具箱的微电网最优调度算法matlab仿真。通过YALMIP和cplex这两个工具箱,完成微电网的最优调…...
AI前端开发技能提升与ScriptEcho:拥抱AI时代的前端开发新范式
随着人工智能技术的飞速发展,AI前端开发岗位对技能的要求也水涨船高。越来越多的企业需要具备AI相关知识和高级前端开发能力的工程师,这使得传统的前端开发模式面临着巨大的挑战。如何提升开发效率,降低人力成本,成为了摆在所有前…...
LeetCode题解:2690. 无穷方法对象,Proxy
Problem: 2690. 无穷方法对象 思路 这个问题的核心在于创建一个对象,该对象能够响应对其任何方法的调用,并返回调用的方法名称。为了实现这一点,我们可以利用 JavaScript 中的 Proxy 对象。Proxy 对象允许我们自定义对象的基本操作ÿ…...
Python中的HTTP客户端库:httpx与request | python小知识
Python中的HTTP客户端库:httpx与request | python小知识 在Python中,发送HTTP请求和处理响应是网络编程的基础。requests和httpx是两个常用的HTTP库,它们都提供了简洁易用的API来发送HTTP请求。然而,httpx作为新一代的HTTP客户端…...
RabbitMQ使用guest登录提示:User can only log in via localhost
guest用户默认是无法使用远程访问的,生产环境建议直接在对应服务器登录使用。 1、通过创建新增用户并赋予权限实现远程登录 添加新用户 rabbitmqctl add_user zjp zjp 设置管理员 rabbitmqctl set_user_tags zjp administrator 设置新用户的权限 rabbitmqctl…...
#渗透测试#批量漏洞挖掘#Crocus系统—Download 文件读取
免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停…...
基于Matlab实现六自由度机械臂正逆运动仿真(源码)
在机器人技术领域,六自由度机械臂是一种广泛应用的设备,它可以实现空间中的位置和姿态控制。本项目聚焦于六自由度机械臂的正逆运动学仿真,利用MATLAB2016b作为开发工具,旨在深入理解并掌握机械臂的工作原理和运动控制。 正运动学…...
亚信安全正式接入DeepSeek
亚信安全致力于“数据驱动、AI原生”战略,早在2024年5月,推出了“信立方”安全大模型、安全MaaS平台和一系列安全智能体,为网络安全运营、网络安全检测提供AI技术能力。自2024年12月DeepSeek-V3发布以来,亚信安全人工智能实验室利…...
Django 创建第一个项目
Django 创建第一个项目 引言 Django 是一个高级的 Python Web 框架,它鼓励快速开发和干净、实用的设计。本指南将带您从头开始创建一个简单的 Django 项目,以便您能够熟悉 Django 的基本结构和概念。 准备工作 在开始之前,请确保您已经安装了 Python 和 Django。以下是安…...
RL基础概念
RL意味着模型从真实世界中获取反馈,根据策略调整参数,以达到最大化反馈的优化算法; 强化学习的核心在于:1.试错(因为仅凭reward无法得到明确的优化方向,所以需要不断多尝试);2.延迟奖…...
像取快递一样取文件?
看到一个很有意思的项目,像我们做软件分享的感觉会有用,就是现在服务器费用太贵了,如果自建的话感觉不是很值得。 FileCodeBox FileCodeBox 是一个轻量级的文件分享系统,它基于匿名口令分享文本和文件,无需注册登录&…...
Jenkins 新建配置Pipeline任务 三
Jenkins 新建配置Pipeline任务 三 一. 登录 Jenkins 网页输入 http://localhost:8080 输入账号、密码登录 一个没有创建任务的空 Jenkins 二. 创建 任务 图 NewItem 界面左上角 New Item 图NewItemSelect 1.Enter an item name:输入任务名 2.Select an ite…...
React 前端框架搭建与解析
React 前端框架搭建与解析 一、 概述 React 是 Facebook 开源的用于构建用户界面的 JavaScript 库,以其组件化、声明式编程范式以及高效的虚拟 DOM 渲染机制,成为当今最流行的前端框架之一。本文将带领你从零开始搭建一个 React 开发环境,并深入解析其核心概念。 二、 环…...
像指针操作、像函数操作的类
像指针一样的类。把一个类设计成像一个指针。什么操作符运用到指针上? 使用标准库的时候,里面有个很重要的东西叫容器。容器本身一定带着迭代器。迭代器作为另外一种智能指针。迭代器指向容器里的一个元素。迭代器用来遍历容器。 _list_iterator是链表迭…...
15.Python网络编程:进程池、进程间通信、多线程、进程和线程区别、网络通信、端口、IP地址、socket、UDP、TCP、http
1. 进程池(Process Pool) 进程池是通过将多个进程放入池中管理来避免频繁地创建和销毁进程,提高效率。Python 提供了 multiprocessing.Pool 类来实现进程池,它可以用于并行计算任务。 示例:使用进程池 from multipr…...
Ubuntu启动geteck/jetlinks实战:Docker启动
参考: JetLinks 物联网基础平台 安装Docker Ubuntu下载安装Docker-Desktop-CSDN博客 sudo apt install -y docker-compose 下载源码 git clone https://github.com/jetlinks/jetlinks-community.git cd jetlinks-community 启动 cd docker/…...
Newton 差商插值多项式
Newton 差商插值多项式 根据差商定义,把 x x x 看成[ a , b ] a,b] a,b]上一点,可得 f ( x ) f ( x 0 ) f [ x , x 0 ] ( x − x 0 ) f(x)f(x_0)f[x,x_0](x-x_0) f(x)f(x0)f[x,x0](x−x0) f [ x , x 0 ] f [ x 0 , x 1 ] f [ x , x 0 , x 1…...
ubuntu下ollama/vllm两种方式在本地部署Deepseek-R1
1.前言 今年过年,deepseek一下子爆火,导致我前段时间好好用的官网直接挤爆了,一直转圈圈到没心思过了,天天挂热搜,直接导致我的科研工作受阻(dog),好吧,话不多说,看看怎么在本地部署deepseek丝滑享受以及白嫖一下api体验>_<! 部署环境: 系统:ubuntu22.04 显…...
如何使用ps批量去除固定位置水印
使用 Photoshop 批量去除固定位置的水印,有几种方法可以实现自动化,具体取决于水印的复杂程度和你对 Photoshop 的熟悉程度: 1. 动作(Actions) 批处理(Batch): 这是最常用的方法&…...
基于 STM32 的病房监控系统
标题:基于 STM32 的病房监控系统 内容:1.摘要 基于 STM32 的病房监控系统摘要:本系统采用 STM32 微控制器作为核心,通过传感器实时监测病房内的环境参数,如温度、湿度、光照等,并将数据上传至云端服务器。医护人员可以通过手机或…...
Windows部署deepseek-r1
安装Ollama 访问Ollama下载页,选择对应的操作系统进行下载 运行OllamaSetup,进行安装 任务栏出现Ollama图标,运行正常 cmd查看Ollama版本 C:\Users\PC>ollama -v ollama version is 0.5.7 部署模型 deepseek 可访问deepseek模型页,切…...
CCFCSP第34次认证第一题——矩阵重塑(其一)
第34次认证第一题——矩阵重塑(其一) 官网链接 时间限制: 1.0 秒 空间限制: 512 MiB 相关文件: 题目目录(样例文件) 题目背景 矩阵(二维)的重塑(reshap…...
webpack打包优化策略
1. 减少打包体积 减少打包文件的大小是为了提高加载速度,降低网络带宽消耗,提升用户体验。常见的减少打包体积的优化策略包括: 代码分割(Code Splitting):将代码拆分成多个小文件,让浏览器按需…...
八股文-2025-02-12
BFC BFC属于普通流。BFC全称是Block Formatting Context,意思就是块级格式化上下文。你可以把BFC看做元素的一个属性,当元素拥有BFC属性,这个元素就可以看作是隔离了的独立容器,容器里边的元素不会影响到容器外部的元素.https://b…...
部门管理(体验,最原始方法来做,Django+mysql)
本人初学,写完代码在此记录和复盘 在创建和注册完APP之后(我的命名是employees),编写models.py文件创建表 手动插入了几条数据 1.部门查询 urls.py和views.py在编写之前,都要注意导入对应的库 urls.py:…...
联想拯救者Y9000银河麒麟系统安装Nvidia 4060显卡驱动
查了好多资料, 发现银河麒麟的资料较少, 只能自己试验了, 如有帮助, 点个赞吧~~ 前提: 本人笔记本是联想拯救者Y9000, 独立显卡 Nvidia4060, 主机系统是银河麒麟V10 (2403), 笔记本处于联网状态ÿ…...
深入解析 STM32 GPIO:结构、配置与应用实践
理解 GPIO 的工作原理和配置方法是掌握 STM32 开发的基础,后续的外设(如定时器、ADC、通信接口)都依赖于 GPIO 的正确配置。 目录 一、GPIO 的基本概念 二、GPIO 的主要功能 三、GPIO 的内部结构 四、GPIO 的工作模式 1. 输入模式 2. 输…...
突破数据壁垒,动态住宅代理IP在数据采集中的高效应用
在当今数字化时代,数据已经成为企业和个人决策的重要依据。无论是市场调研、竞争分析,还是价格监控、SEO优化,数据采集都扮演着至关重要的角色。然而,随着技术发展与网络安全措施的日益严格,传统的数据采集方式面临着前…...
Spring 项目接入 DeepSeek,分享两种超简单的方式!
⭐自荐一个非常不错的开源 Java 面试指南:JavaGuide (Github 收获148k Star)。这是我在大三开始准备秋招面试的时候创建的,目前已经持续维护 6 年多了,累计提交了 5600 commit ,共有 550 多位贡献者共同参与…...
SSE与Websocket详解,SSE实现对话框流式输出
SSE详解 SSE(Server-Sent Events)是一种在Web应用中实现单向实时通信的技术,它允许服务器主动向客户端发送更新,而无需客户端不断发起请求。SSE基于HTTP协议,利用HTTP的长连接特性,通过浏览器向服务器发送一个HTTP请求,建立一条持久化的连接,然后服务器可以通过这条连…...
Shell脚本笔记
Linux其他命令 Shell脚本笔记...
Hydra主配置文件和模块化配置管理
在 Hydra 中,我们可以使用 defaults 关键字 在主配置文件 config.yaml 中加载多个子配置文件,从而实现 模块化配置管理。这在深度学习、超参数优化、数据预处理等场景下非常有用。 1️⃣ 配置文件目录结构 假设我们有一个深度学习训练项目,…...
oracle dbms_sqltune 使用
创建测试表 CREATE TABLE test_table (id NUMBER PRIMARY KEY,event_date DATE,value NUMBER );插入测试数据 DECLAREi NUMBER; BEGINFOR i IN 1..1000000 LOOPINSERT INTO test_table (id, event_date, value)VALUES (i, SYSDATE - MOD(i, 365), DBMS_RANDOM.VALUE(1, 1000)…...
Mediamtx+Python读取webrtc流
一、功能思路: 1、我采用ffmpeg -re -stream_loop -1 -i xcc.mp4 -c:v libx264 -profile:v baseline -x264opts "bframes0:repeat_headers1" -b:v 1500k -preset fast -f flv rtmp://127.0.0.1:1835/stream/111推流到mediamtx的rtmp上 2、通过mediamtx自…...
Makefile和算法(20250213)
1. Makefile 1.1 功能 管理工程代码的编译和链接,可以一键化实现代码工程的编译和管理。 时间戳:根据时间戳,可以只编译发生修改后的文件 1.2 Makefile 基本语法和相关操作 1.2.1 创建一个Makefile文件 Makefile/makefile(首…...
postgresql源码学习(59)—— 磁盘管理器 SMGR
一、 定义及作用 PostgreSQL 的磁盘管理器(Storage Manager,简称 SMGR)是数据库系统中负责管理底层存储的核心模块。磁盘管理器并非直接操作磁盘上的文件,而是通过VFD(虚拟文件描述符,将在后续学习…...
亚冬会绽放“云端”,联通云如何点亮冰城“科技之光”?
科技云报到原创。 35年前,中国第一次承办亚运会,宣传曲《亚洲雄风》红遍大江南北,其中有一句“我们亚洲,云也手握手”。如今回看,这句话仿佛有了更深的寓意:一朵朵科技铸就的“云”,把人和人连…...
寻找最优的路测路线
寻找最优的路测路线 真题目录: 点击去查看 E 卷 200分题型 题目描述 评估一个网络的信号质量,其中一个做法是将网络划分为栅格,然后对每个栅格的信号质量计算。 路测的时候,希望选择一条信号最好的路线(彼此相连的栅格集合)进行演示。 现给出 R 行 C 列的整数数组 Cov…...
某虚拟页式存储管理系统中有一个程序占8个页面,运行时访问页面的顺序是1,2,3,4,5,3,4,1,6,7,8,7,8,5。假设刚开始内存没有预装入任何页面。
某虚拟页式存储管理系统中有一个程序占8个页面,运行时访问页面的顺序是1,2,3,4,5,3,4,1,6,7,8,7,8,5。假设刚开始内存没有预装入任何页面。 (1) 如果采用LRU调度算法,该程序在得到4块内存空间时,会产生多少次缺页中断?请给出详细…...
介绍下SpringBoot如何处理大数据量业务
Spring Boot 处理大数据量业务时,通常会面临性能、内存、数据库负载等挑战。为了高效处理大数据量,Spring Boot 提供了多种解决方案和优化策略。以下是一些常见的处理方式: 1. 分页查询 问题:一次性查询大量数据会导致内存溢出和…...
推荐系统召回算法
推荐系统召回算法 召回算法UserCFItemCFSwing矩阵分解 召回算法 基于协同过滤的召回算法主要是应用在推荐环节的早期阶段,大致可以分为基于用户、基于物品的。两者各有优劣,优点是具有较好的可解释性,缺点是对于稀疏的交互矩阵,效…...