二、多线程、锁
2.1 C语言线程库pthread
(POSIX threads)
2.2.1 线程创建 pthread_create
#include <pthread.h>
pthread_t thread;
ThreadData args = {1, "Hello from parameterized thread"};
int result = pthread_create(&thread, attr, function, args); // 线程创建即启动。
arttr
:定制各种不同的线程属性,通常直接设为NULL;function
:线程要执行的函数;args
:函数执行需要输入的参数,无参数是输入NULL,有参数时需要输入ThreadData
结构体对象。- 返回值:成返回0,失败返回非0值。
2.2.2 线程同步
-
互斥锁:
pthread_mutex_t
// 静态初始化互斥锁 pthread_mutex_t m_mutex = PTHREAD_MUTEX_INITIALIZER; // 动态初始化互斥锁 pthread_mutex_t m_mutex; pthread_mutex_init(&m_mutex, nullptr); // 加锁 pthread_mutex_lock(&m_mutex); // 解锁 pthread_mutex_unlock(&m_mutex); // 销毁 pthread_mutex_destroy(&m_mutex);
-
条件变量:
pthread_cond_t
// 静态初始化条件变量 pthread_cond_t m_cond = PTHREAD_COND_INITIALIZER; // 动态初始化条件变量 pthread_cond_t m_cond; pthread_cond_init(&m_cond, nullptr); // 阻塞 pthread_cond_Wait(&m_cond, &m_mutex); // 要同时输入一个互斥锁对象,代表是等待申请该互斥锁 // 唤醒一个其他等待线程 pthread_cond_signal(&m_mcond); // 唤醒全部其他等待线程 pthread_cond_broadcast(&m_mcond);
2.2.3 等待线程结束 pthread_join
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
thread
:要等待结束的线程;retval
:若不为NULL,则用于复制出线程的退出状态;- 返回值:成功返回0,失败返回非0。
2.2.4 线程分离 pthread_detach
#include <pthread.h>
int pthread_detach(pthread_t thread);
thread
:要分离的线程;- 返回值:成功返回0,失败返回非0。
2.2.5 线程退出 pthread_exit
#include <pthread.h>
void pthread_exit(void *retval);
retval
:用于保存线程的退出状态;
2.2 C+11标准库的线程 std::thread
2.2.1 线程创建 std::thread
#include <thread>
// 使用函数
std::thread t(func, args1, std::ref(args2), std::cref(args3)); // 线程创建即启动。
// 使用lambda表达式
std::thread t([]() { std::cout << "hellow, world!" << std::endl;}); // 线程创建即启动。
// 使用类成员函数
std::thread t(&MyClass::func, &obj, args1, args2, ...); // obj是实际依托的对象
// 使用类静态成员函数不需要传递对象
std::thread t(&MyClass::func, args1, args2, ...);
std::ref()
:指传递引用参数;std::cref()
:指传递常量引用参数。
2.2.2 线程同步
-
**互斥锁:
std::mutex
**#include <mutex> // 初始化 std::mutex m_mtx; // 加锁 m_mtx.lock(); // 解锁 m_mtx.unlock();
-
**RAII(资源获取即初始化)锁:
std::lock_guard
**用于管理某个锁(Lock)对象,因此与 Mutex RAII 相关,方便线程对互斥量上锁,即在某个 lock_guard 对象的声明周期内,它所管理的锁对象会一直保持上锁状态;而 lock_guard 的生命周期结束之后,它所管理的锁对象会自动解锁 (注:类似 shared_ptr 等智能指针管理动态分配的内存资源 )。
lock_guard 对象不负责管理 Mutex 对象的生命周期,只是简化了 Mutex 对象的上锁和解锁操作
简单理解就是自动unlock
#include<mutex> // 初始化 std::mutex m_mtx; void func() { std::lock_guard<std::mutex> lc(m_mtx); // 加锁 ... } // 函数执行完毕lc资源释放后会自动解锁,无需显式的unlock
-
**RAII锁:
std::unique_lock
**更灵活的锁,它允许手动锁定和解锁互斥量,以及与条件变量一起使用(是lock_guard的进阶版)。与 lock_guard 类似,unique_lock 也是一个 RAII 风格的锁,当对象离开作用域时,它会自动解锁互斥量。unique_lock 还支持延迟锁定、尝试锁定和可转移的锁所有权。
#include<mutex> // 初始化 std::mutex m_mtx; void func() { std::unique_lock<std::mutex> lc(m_mtx); // 加锁 ... } // 函数执行完毕lc资源释放后会自动解锁,无需显式的unlock
-
条件变量:
std::condition_variable
#include <condition_variable> #include <mutex> std::mutex m_mtx; std::unique_lock<std::mutex> lc(m_mtx); // 初始化 std::condition_variable cv; // 阻塞 cv.wait(lc); // 唤醒一个阻塞线程 cv.notify_one(); // 唤醒全部阻塞线程 cv.notify_all();
2.2.3 等待线程结束 thread.join()
#include <thread>
std::thread t(func);
t.join(); // 等待线程结束
2.2.4 线程分离 thread.detach()
#include <thread>
std::thread t(func);
t.detach(); // 等待线程结束
2.2.5 线程退出
C++标准库std::thread并没有提供类似pthread_exit
函数相关显式退出线程的函数,原因是 std::thread
的设计是面向 RAII(Resource Acquisition Is Initialization)原则的,即资源管理应当通过对象的生命周期来控制。
尽管没有 pthread_exit
,你仍然可以通过控制线程的执行逻辑来实现类似的功能。如,通过在线程函数中检查某个退出条件或标志位,在满足条件时退出函数。
#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>
std::atomic<bool> stop_thread(false);
void worker() {
while (!stop_thread) {
std::cout << "Working..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
std::cout << "Worker thread is exiting..." << std::endl;
}
int main() {
std::thread t(worker);
std::this_thread::sleep_for(std::chrono::seconds(2));
stop_thread = true; // Signal the thread to exit
t.join();
std::cout << "Main thread is done." << std::endl;
return 0;
}