《C++ 108好库》之2 多线程库thread,mutex,condition_variable,this_thread
《C++ 108好库》之之2 多线程库thread,mutex,condition_variable,this_thread
- 《C++ 108好库》之2 多线程库thread,mutex,condition_variable,this_thread
- std::thread类
- 互斥量(Mutex)类
- std::mutex类
- std::lock_guard类
- std::unique_lock类
- 条件变量(Condition Variable)类
- 获取线程ID和 延时
- 使用例子
- 创建线程
- 使用mutex和Condition Variable的生产者-消费者模型
《C++ 108好库》之2 多线程库thread,mutex,condition_variable,this_thread
C++11 中,多线程编程通过标准库 正式引入,提供了跨平台的线程管理能力。
多线程一般由这些类合作构成:std::thread类、std::mutex类、std::lock_guard类、 std::unique_lock类、condition_variable类
std::thread类
1 创建线程
explicit thread(_Callable&& __f, _Args&&… __args)
使用 std::thread类创建线程,构造函数接受函数对象及参数:
向线程函数传递参数时,参数会被复制到线程的独立内存中;如果希望传递引用,需要使用std::ref。
2线程管理:
join():等待线程完成。
detach():将线程与主线程分离,使其独立运行。一旦分离,就不能再与之通信。
注意:在销毁std::thread对象之前,必须调用join()或detach(),否则程序会终止(调用std::terminate)。
互斥量(Mutex)类
C++11提供了std::mutex用于保护共享数据,防止数据竞争。
std::mutex类
std::mutex()
std::mutex::lock() :调用此函数会尝试锁定互斥体。
行为:如果互斥体当前未被任何线程锁定,则调用线程获得锁,函数立即返回。如果互斥体已被其他线程锁定,则调用线程会被阻塞(挂起),直到持有锁的线程解锁该互斥体。一旦解锁,等待的线程之一(取决于调度)会获得锁并继续执行。
特点: 阻塞调用。如果忘记解锁会导致死锁。
用法: 当你需要确保临界区被独占访问,并且可以接受线程阻塞等待时使用。
std::mutex::try_lock(): 调用此函数会尝试锁定互斥体,但不会阻塞调用线程。
行为:如果互斥体当前未被锁定,则调用线程获得锁,函数返回 true。如果互斥体已被锁定(无论是当前线程还是其他线程),则函数立即返回 false,线程继续执行而不会被阻塞。
特点: 非阻塞调用。需要检查返回值来判断是否成功获得锁。
用法: 当你需要在锁不可用时做其他事情,避免阻塞线程时使用。常用于轮询、避免死锁策略(如按固定顺序获取多个锁失败时释放已持有的锁)或非关键路径的优化。
std::mutex::unlock():解锁
std::lock_guard类
它在构造时自动锁定给定的互斥体,并在析构时(通常是离开作用域时)自动解锁它。
构造时: 调用传入的互斥体(如 std::mutex)的 lock()方法。这会阻塞直到获得锁。
析构时: 调用互斥体的 unlock()方法。
用法:std::lock_guardstd::mutex lock(mtx); // 构造时自动调用 mtx.lock()
std::unique_lock类
功能比 lock_guard更强大、更灵活。它也管理互斥体的锁定和解锁,但提供了更多的控制选项。
行为/特点:
灵活的构造:默认构造:创建一个不关联任何互斥体的 unique_lock。
关联互斥体并立即锁定(阻塞):unique_lock lock(mtx);(等价于 lock_guard)
关联互斥体但延迟锁定:unique_lock lock(mtx, std::defer_lock);构造时不锁定,稍后手动调用 lock(), try_lock()或 try_lock_for()/try_lock_until()(针对 std::timed_mutex)。
关联互斥体并尝试锁定:unique_lock lock(mtx, std::try_to_lock);构造时调用 mtx.try_lock()。
关联互斥体并假设已锁定(接管所有权):unique_lock lock(mtx, std::adopt_lock);假设调用线程已经持有 mtx的锁,unique_lock负责后续解锁。
手动控制:
lock(): 阻塞锁定关联的互斥体(如果尚未锁定)。
try_lock(): 尝试非阻塞锁定关联的互斥体。
unlock(): 手动解锁关联的互斥体,在 unique_lock析构前可以临时释放锁(这在 lock_guard中是不可能的)。析构时如果互斥体仍被该 unique_lock持有,会自动解锁。
release(): 释放对互斥体的所有权管理(返回互斥体指针),不再负责解锁。调用者需手动管理。
所有权:
可移动 (std::move),但不可复制。锁的所有权可以在 unique_lock对象之间转移。
条件变量: std::condition_variable::wait等函数必须使用 std::unique_lockstd::mutex作为参数,因为它需要能在等待时解锁和重新锁定互斥体(这是 lock_guard做不到的)。
条件变量(Condition Variable)类
<condition_variable>
用于线程间的同步,允许线程在某些条件不满足时休眠,直到被其他线程通知。通常与互斥量一起使用。
void notify_one() noexcept;
void notify_all() noexcept;
wait(unique_lock& __lock) noexcept;
wait_until(unique_lock& __lock,const chrono::time_point<__clock_t, _Duration>& __atime)
wait_for(unique_lock& __lock, const chrono::duration<_Rep, _Period>& __rtime)
获取线程ID和 延时
std::this_thread里的函数
inline thread::id get_id()
获取当前执行线程的唯一标识符。
std::thread::id main_id = std::this_thread::get_id(); // 输出示例:0x70000a15c000
inline void sleep_for(const chrono::duration<_Rep, _Period>& __rtime)
使当前线程阻塞一段指定的相对时间。
std::this_thread::sleep_for(std::chrono::seconds(1));
inline void sleep_until(const chrono::time_point<_Clock, _Duration>& __atime)
使当前线程阻塞直到绝对时间点。
使用例子
创建线程
std::thread thread_hello(hello,"LiHua");
thread_hello.join();
传入函数给线程
void hello(const std::string str) {
std::cout << "hello world! "<<str<<std::endl;
}
使用mutex和Condition Variable的生产者-消费者模型
std::thread thread_producer(producer);
thread_producer.detach();std::thread thread_consumer(consumer);
thread_consumer.join();
其中的线程函数
std::queue<int> dataQueue;
std::mutex mtx_test_thread;
std::condition_variable cv_test_thread;
void producer() {for (int i = 0; i < 50; ++i) {{std::lock_guard<std::mutex> lock(mtx_test_thread);dataQueue.push(i); std::cout << "push:" << i << std::endl;}std::this_thread::sleep_for(std::chrono::seconds(1));cv_test_thread.notify_one(); // 通知消费者}
}void consumer() {while (true) {std::unique_lock<std::mutex> lock(mtx_test_thread);cv_test_thread.wait(lock, [] { return !dataQueue.empty(); });int value = dataQueue.front();dataQueue.pop();lock.unlock();if (value == -1) break; // 终止条件std::cout << "Consumed: " << value << std::endl;}
}