多线程编程
在C++11中,线程编程得到了极大的简化和标准化,引入了一系列与线程相关的类和方法。这些功能主要集中在 <thread>
和 <mutex>
头文件中,提供了线程创建、同步和管理的工具。
1. std::thread
std::thread
是C++中用于表示线程的类,用于创建和管理线程。
主要方法:
构造函数
std::thread::thread(); // 默认构造函数 std::thread::thread(std::thread&& other); // 移动构造函数 template <class F, class... Args> std::thread::thread(F&& f, Args&&... args); // 带参数的构造函数用于启动线程并执行指定的函数
std::thread::join()
void join(); // 等待线程完成。如果线程未启动或已结束,则行为未定义。
joinable
函数主要用于判断一个线程对象所代表的线程是否处于可执行相关操作的状态。如果一个线程对象是通过默认构造函数创建的,或者已经调用过join
函数、detach
函数,那么该线程对象是不可连接的,joinable
函数将返回false
。线程对象在启动线程后到线程执行完毕期间,joinable
函数返回true
。一旦线程执行完毕,即使没有调用join
或detach
,该线程对象也会变为不可连接状态,joinable
返回false
。std::thread::detach()
void detach(); // 将线程分离,使其独立运行。即使线程对象被销毁,线程仍然会继续执行。
std::thread::swap()
void swap(std::thread& other); // 交换两个线程对象的所有权。
std::thread::get_id()
std::thread::id get_id() const; // 获取线程的唯一标识符。
std::thread::hardware_concurrency()
static unsigned hardware_concurrency(); // 返回系统支持的线程数(逻辑核心数)。
2. 线程同步
C++11提供了多种同步机制,包括互斥锁(Mutex)和条件变量(Condition Variable)。
互斥锁(std::mutex):
std::muxtex
:- 独占的互斥量,不能递归使用。
- 提供了保护共享资源,防止多个线程同时访问。
- 提供了
lock()
和unlock()
方法。一般用法是通过lock()
方法来阻塞线程,直到获得互斥量的所有权为止。在线程获得互斥量并完成任务之后,就必须使用unlock()
来解除对互斥量的占用,lock()
和unlock()
必须成对出现。try_lock()
尝试锁定互斥量,如果成功则返回true,如果失败则返回false
,它是非阻塞的。使用lock_guard
可以简化lock/unlock
的写法,同时也更安全,因为lock_guard
在构造时会自动锁定互斥量,而在退出作用域后进行析构时就会自动解锁,从而保证了互斥量的正确操作,避免忘记unlock
操作,因此,应尽量用lock_guard
。 - 不可复制,但可移动。
std::recursive_mutex
:递归互斥量,不带超时功能。
允许同一线程多次锁定同一互斥锁。
需要注意的是尽量不要使用递归锁好,主要原因如下:
1)需要用到递归锁定的多线程互斥处理往往本身就是可以简化的,允许递归互斥很容易放纵复杂逻辑的产生,从而导致一些多线程同步引起的晦涩问题。
2)递归锁比起非递归锁,效率会低一些。
3)递归锁虽然允许同一个线程多次获得同一个互斥量,可重复获得的最大次数并未具体说明,一旦超过一定次数,再对
lock
进行调用就会抛出std::system
错误。
std::timed_mutex
:- 带超时的独占互斥量,不能递归使用。
- 支持尝试锁定,并在指定时间内未成功时返回。
std::recursive_timed_mutex
:- 结合了递归锁和超时锁的功能。
条件变量(std::condition_variable):
std::condition_variable
:- 用于线程间的同步,允许线程在某个条件不满足时挂起,并在条件满足时被唤醒。
- 常与互斥锁配合使用。
- 提供了
wait()
、notify_one()
和notify_all()
方法。
std::condition_variable_any
:- 与
std::condition_variable
类似,但可以与任何互斥锁类型配合使用。
- 与
lock_guard
和unique_lock
的关系与区别:
lock_guard
提供了一种简单的 RAII(Resource Acquisition Is Initialization)机制,用于管理互斥锁。当lock_guard
对象被创建时,它会自动锁定与之关联的互斥锁;当lock_guard
对象被销毁时,它会自动解锁互斥锁。这种自动锁定和解锁的机制有助于防止因忘记解锁而导致的死锁问题,使得代码更简洁、更安全。
unique_lock
同样提供了互斥锁的自动管理,但具有更多的灵活性。它可以在构造时不立即锁定互斥锁,允许在后续代码中手动锁定、解锁或尝试锁定。还支持所有权转移,即可以将unique_lock
的所有权从一个对象转移到另一个对象。
- 可以使用不同的构造方式:
std::unique_lock<std::mutex> lock(mtx);
:构造时立即锁定。std::unique_lock<std::mutex> lock(mtx, std::defer_lock);
:构造时不锁定,后续需要手动锁定。std::unique_lock<std::mutex> lock(mtx, std::try_to_lock);
:尝试锁定,根据结果决定是否进入临界区。- 提供成员函数,如
lock()
、try_lock()
、unlock()
,可以手动操作锁定和解锁。- 可以使用
owns_lock()
成员函数来检查是否持有锁。- 支持移动语义,可在不同的
unique_lock
对象之间转移锁的所有权。
3. 线程局部存储(Thread Local Storage)
C++11引入了线程局部存储(TLS),允许为每个线程分配独立的变量副本。
thread_local
关键字:
thread_local int var = 0;
定义一个线程局部变量,每个线程都有自己的独立副本。
4. 线程相关函数
std::this_thread::yield()
: 提示调度器让出当前线程的执行权,允许其他线程运行。
std::this_thread::sleep_for()
: 使当前线程暂停指定的时间。
std::this_thread::sleep_until()
: 使当前线程暂停,直到指定的时间点.