网站添加 备案,深圳官方网站建设,北京做网站推广,h5企业模板网站一、std::thread
std::thread 是 C11 引入的标准库中的线程类#xff0c;用于创建和管理线程
1. 带参数的构造函数
template class F, class... Args
std::thread::thread(F f, Args... args);F f#xff1a;线程要执行的函数…一、std::thread
std::thread 是 C11 引入的标准库中的线程类用于创建和管理线程
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 threadvoid 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 functionalclass 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 i0; i 10000; i){shared_data;}
}int main()
{std::thread t1(myThread);std::thread t2(myThread);t1.join();t2.join();std::coutshared_data shared_datastd::endl;return 0;
} 互斥量是一种同步原语用于保护共享资源防止多个线程同时访问。互斥量确保同一时间只有一个线程可以持有互斥量从而保证对共享资源的互斥访问。确保线程安全。
C11 引入了 mutex 头文件其中定义了多种类型的互斥量。
4.1 std::mutex std::mutex 是最基本且最常用的互斥量类型它不支持递归加锁。
int shared_data 0;
std::mutex mtx; // 定义互斥量void myThread()
{mtx.lock();for (int i0; i 10000; i){shared_data;}mtx.unlock();
}int main()
{std::thread t1(myThread);std::thread t2(myThread);t1.join();t2.join();std::coutshared_data shared_datastd::endl;return 0;
}此时无论运行多少次输出都是相同的。 4.2 std::lock_guard 为了简化互斥量的使用 C 提供了 std::lock_guard 它是一个 RAII 风格的工具用于自动管理互斥量的加锁和解锁。lock_guard 对象不能复制或移动因此他只能在局部作用域中。
#include iostream
#include thread
#include mutexint shared_data 0;
std::mutex mtx; // 定义互斥量void myThread()
{std::lock_guardstd::mutex lock(mtx); // 自动加锁(析构中自动解锁)for (int i0; i 10000; i){shared_data;}
}int main()
{std::thread t1(myThread);std::thread t2(myThread);t1.join();t2.join();std::coutshared_data shared_datastd::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::durationRep, Period rel_time);
尝试在指定的相对时间 rel_time 内获取互斥量。template class Clock, class Duration
bool try_lock_until(const std::chrono::time_pointClock, 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_lockstd::mutex u_lock(mtx);for (int i0; i 10000; i){shared_data;}
}int main()
{std::thread t1(myThread);std::thread t2(myThread);t1.join();t2.join();std::coutshared_data shared_datastd::endl;return 0;
}
② 尝试立即加锁
std::mutex mtx;
void foo() {std::unique_lockstd::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_lockstd::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 i0; i 10000; i){shared_data;}}
}int main()
{std::thread t1(myThread);std::thread t2(myThread);t1.join();t2.join();std::coutshared_data shared_datastd::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 mutexstd::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 是 C11 引入的一个非常有用的工具用于确保某个函数或操作只被调用一次即使在多线程环境中也能保证线程安全。它常用于实现单例模式、初始化全局资源等场景。
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::queueint data_queue; // 数据队列
std::mutex mtx; // 互斥锁
std::condition_variable cv; // 条件变量
std::atomicbool done(false); // 标志位表示生产者是否完成// 生产者函数
void producer() {for (int i 0; i 10; i) {std::unique_lockstd::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_lockstd::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 futureclass ThreadPool {
public:ThreadPool(size_t num_threads);~ThreadPool();template class F, class... Argsauto enqueue(F f, Args... args) - std::futuretypename std::result_ofF(Args...)::type;private:std::vectorstd::thread workers;std::queuestd::functionvoid() 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::functionvoid() task;{std::unique_lockstd::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_lockstd::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::futuretypename std::result_ofF(Args...)::type {using return_type typename std::result_ofF(Args...)::type;auto task std::make_sharedstd::packaged_taskreturn_type()(std::bind(std::forwardF(f), std::forwardArgs(args)...));std::futurereturn_type res task-get_future();{std::unique_lockstd::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 functionalclass TheradPool
{
public:TheradPool(int thread_num) : stop_(false){for (int i0; i thread_num; i){threads_.emplace_back([this](){while(1){std::unique_lockstd::mutex uptr_lock(mtx_);condition_.wait(uptr_lock, [this](){return !tasks_.empty() || stop_;});if (stop_ tasks_.empty()){return;}std::functionvoid() task(std::move(tasks_.front()));tasks_.pop();uptr_lock.unlock();task();}});}}// F 是函数参数类型表示可调用对象的类型如函数指针、lambda 表达式、函数对象等
// Args... 是可变参数模板表示可调用对象的参数类型列表。... 表示参数包可以接受任意数量和类型的参数template typename F, typename... Argsvoid enqueue(F f, Args... args){// F f 是一个右值引用表示可调用对象。使用右值引用是为了支持完美转发//即可以将 f 作为左值或右值传递给后续的调用std::functionvoid() task std::bind(std::forwardF (f), std::forwardArgs(args)...);{std::unique_lockstd::mutex uptr_lock(mtx_);tasks_.emplace(std::move(task));}condition_.notify_one();}~TheradPool(){// 控制锁的作用域设置完stop_立即释放锁确保其他线程能拿到锁避免死锁{std::unique_lockstd::mutex uptr_lock(mtx_);stop_ true;}condition_.notify_all();for (auto thread : threads_){thread.join();}}private:std::vectorstd::thread threads_;std::queuestd::functionvoid() tasks_;std::mutex mtx_;std::condition_variable condition_;bool stop_;};8. 异步并发 (async , future)
8.1 async std::async 是 C11 引入的一个用于异步任务执行的函数模板它提供了一种简单的方式来启动一个任务并通过 std::future 获取任务的返回值。 std::async用于启动一个异步任务返回一个 std::future 对象通过该对象可以获取任务的返回值或处理任务中的异常std::future表示异步操作的结果可以用来获取异步任务的返回值或检查任务是否完成。
std::futureR std::async(std::launch policy, F f, Args... args);
返回值std::async 返回一个 std::futureR 对象其中 R 是函数 f 的返回类型。std::future 是一个用于获取异步操作结果的对象。
参数
std::launch policy启动策略用于指定任务的执行方式。
F f可调用对象如函数指针、lambda 表达式、函数对象等。
Args... args可调用对象的参数列表支持可变参数。
#include iostream
#include future
#include thread
#include chronoint add(int a, int b) {std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作return a b;
}int main() {std::futureint 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 是 C11 引入的一个模板类用于包装可调用对象如函数、lambda 表达式、函数对象等使其能够异步执行并通过 std::future 获取任务的执行结果。
#include iostream
#include future
#include thread// 一个简单的函数计算整数的平方
int square(int x) {return x * x;
}int main() {// 创建一个 std::packaged_task包装 square 函数std::packaged_taskint(int) task(square);// 获取与 packaged_task 关联的 std::future 对象std::futureint 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 是 C11 引入的一个模板类用于存储异步操作的结果并通过 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 chronovoid worker(std::promiseint promise) {std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作promise.set_value(42); // 设置异步操作的结果
}int main() {std::promiseint promise; // std::promise 对象用于存储异步操作的结果std::futureint 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 是 C11 标准库中引入的一个模板类用于实现原子操作。它确保对变量的读取、修改和存储操作是不可分割的即原子的即使在多线程环境下也不会出现中间状态被其他线程干扰的情况.
常用类型
C 标准库提供了多种原子类型包括
std::atomicbool原子布尔类型
std::atomicint原子整数类型
std::atomicT*原子指针类型
std::atomicstd::shared_ptrT原子共享指针类型
#include iostream
#include atomic
#include threadstd::atomicint 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::futureC 提供了多种线程间通信机制每种机制都有其适用场景和特点。以下是一些常见的替代方案
1. 共享内存 同步原语
共享内存是最直接的线程间通信方式通过全局变量、静态变量或堆上分配的对象实现。为了防止数据竞争需要使用同步原语如互斥锁 std::mutex来保护对共享变量的访问。
2. 消息队列
消息队列允许线程通过发送和接收消息来交换信息而不是直接操作共享内存。可以使用 std::queue 和条件变量 std::condition_variable 实现一个简单的线程安全消息队列。
3. 条件变量
条件变量是一种同步原语允许线程等待某个条件成立然后被其他线程唤醒。它通常与互斥锁 std::mutex 配合使用适用于复杂的同步逻辑。
4. 信号量
信号量是一种计数器用于控制对共享资源的访问。线程可以通过 acquire() 请求资源通过 release() 释放资源。C20 引入了 std::counting_semaphore可以方便地实现线程间的同步。
5. 读写锁
读写锁std::shared_mutex允许多个线程同时读取共享资源但写操作需要独占访问。它适用于读多写少的场景。
6. 事件
事件是一种简单的同步机制线程可以等待某个事件的发生而其他线程可以触发事件。可以通过条件变量实现。