cpp11--多线程
创建线程
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include<iostream> #include<thread>
void printHelloWorld() { std::cout<<"Hello world"<<std::endl; } int main() { std::thread thread1(printHelloWorld); return 0; }
|
但这个时候会报错,因为主线程先于子线程结束
join()函数
强制主线程等待子线程结束后再结束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include<iostream> #include<thread>
void printHelloWorld() { std::cout<<"Hello world"<<std::endl; } int main() { std::thread thread1(printHelloWorld); thread1.join(); return 0; }
|
detach()函数
分离线程,即使主线程先于子线程结束也不会报错,此时子线程在后台运行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #include<iostream> #include<thread>
void printHelloWorld() { std::cout<<"Hello world"<<std::endl; } int main() { std::thread thread1(printHelloWorld); thread1.detach(); return 0; }
|
joinable()函数
判断是否可以调用join()函数或者detach()函数,返回布尔值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include<iostream> #include<thread>
void printHelloWorld() { std::cout<<"Hello world"<<std::endl; } int main() { std::thread thread1(printHelloWorld); bool isJoin = thread1.joinable(); if(isJoin) { thread1.join(); } return 0; }
|
互斥量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <iostream> #include <thread> #include <mutex> int a=0; std::mutex mtx; void func() { for(int i=0;i<1000;i++) { mtx.lock(); a++; mtx.unlock(); } }
int main() { std::thread t1(func); std::thread t2(func); t1.join(); t2.join(); std::cout<<a<<std::endl; return 0; }
|
线程安全:多线程的程序每一次运行的结构和单线程的结构始终是一样的,那么线程就是安全的
互斥量死锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #include <iostream> #include <thread> #include <mutex> #include <windows.h> std::mutex m1,m2; void func_1() { for(int i=1;i<=50;i++) { m1.lock(); m2.lock(); m1.unlock(); m2.unlock(); } } void func_2() { for(int i=1;i<=50;i++) { m2.lock(); m1.lock(); m1.unlock(); m2.unlock(); sleep(1); } }
int main() { std::thread t1(func_1); std::thread t2(func_2); t1.join(); t2.join(); std::cout<<"over"<<std::endl; return 0; }
|
lock_guard与unique_lock
Rall原则:
- 构造时:自动获取互斥锁。
- 析构时:自动释放互斥锁。 这意味着即使代码块中发生异常,锁也能被安全释放,避免了资源泄漏和死锁。
C++11 中的 std::lock_guard 和 std::unique_lock。它们是实现 RAII(资源获取即初始化)来管理互斥锁的两种重要工具,能有效防止死锁并简化代码。
std::lock_guard
std::lock_guard 是一个轻量级的、不可拷贝的 RAII 包装器,功能简单直接。
特点:
- 简单且高效:开销小,只提供最基本的 RAII 风格锁管理。
- 不可手动操作:一旦创建,就不能主动
lock 或 unlock,其生命周期完全由作用域控制。
- 不支持延迟锁定:构造时必须立即获得锁。
- 不可复制和移动:通过禁用拷贝构造和等于号实现
适用于在某个明确的作用域(局部作用域)内需要持有锁,且在此作用域内不需要手动释放或重新获取锁的简单情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #include <iostream> #include <thread> #include <mutex> #include <windows.h> std::mutex mtx; int shared_data=0; void func() { for(int i=1;i<=10000;i++) { std::lock_guard<std::mutex> lg(mtx); shared_data++; } } int main() { std::thread t1(func); std::thread t2(func); t1.join(); t2.join(); std::cout<<shared_data<<std::endl; return 0; }
|
std::unique_lock
std::unique_lock 是一个功能更全面、更灵活的 RAII 包装器。它包含了 lock_guard 的所有功能,并提供了额外的控制能力。
特点:
- 灵活性高:支持延迟锁定、手动解锁、条件变量等复杂场景。
- 可手动操作:可以在生命周期内主动调用
lock() 和 unlock()。
- 支持所有权转移:可以通过
std::move 转移锁的所有权(但不能复制)。
- 支持多种锁定策略:在构造时可以通过参数指定锁定行为。
锁定策略:
std::defer_lock:不立即加锁,稍后手动加锁。
std::try_to_lock:尝试获取锁,但不阻塞。
std::try_lock_for: 阻塞并等待一定的时间
std::adopt_lock:假设调用线程已经持有该互斥锁。
适用于所有需要锁的场景,尤其是在以下复杂情况:
- 需要与
std::condition_variable 配合使用。
- 需要手动释放锁(例如,在长时间操作的中间阶段)。
- 需要尝试获取锁或延迟获取锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <iostream> #include <thread> #include <mutex> #include <windows.h> std::timed_mutex mtx; int shared_data=0; void func() { for(int i=1;i<=10000;i++) { std::unique_lock<std::timed_mutex>lg(mtx,std::defer_lock); lg.try_lock_for(std::chrono::seconds(5)); shared_data++; } } int main() { std::thread t1(func); std::thread t2(func); t1.join(); t2.join(); std::cout<<shared_data<<std::endl; return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <iostream> #include <thread> #include <mutex> #include <windows.h> std::timed_mutex mtx; int shared_data=0; void func() { for(int i=1;i<=2;i++) { std::unique_lock<std::timed_mutex>lg(mtx,std::defer_lock); if(lg.try_lock_for(std::chrono::seconds(2))) { std::this_thread::sleep_for(std::chrono::seconds(4)); shared_data++; } } } int main() { std::thread t1(func); std::thread t2(func); t1.join(); t2.join(); std::cout<<shared_data<<std::endl; return 0; }
|
std::call_once与其使用场景
单例设计模式:用于确保某个类只能创建一个实例,如日志类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Log{ public: Log(){} Log(const Log& log) = delete; Log& operator=(const Log& log) = delete; static Log& GetInstance(){ static Log log; return log; } void PrintLog(std::string msg) { std::cout<<msg<<std::endl; } };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class Log{ public: Log(){} Log(const Log& log) = delete; Log& operator=(const Log& log) = delete; static Log& GetInstance(){ static Log* log=nullptr; if(!log) log = new Log; return *log; } void PrintLog(std::string msg) { std::cout<<msg<<std::endl; } };
|
多线程情况下会出现一下情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <iostream> #include <thread> #include <mutex> #include <string> class Log{ public: Log(){} Log(const Log& log) = delete; Log& operator=(const Log& log) = delete; static Log& GetInstance(){ static Log* log=nullptr; if(!log) log = new Log; return *log; } void PrintLog(std::string msg) { std::cout<<__TIME__<<' '<<msg<<std::endl; } }; void func() { Log::GetInstance().PrintLog("error"); } int main() { std::thread t1(func); std::thread t2(func); t1.join(); t2.join(); return 0; }
|
为了保证单例,使用std::call_once
1 2
| template<class Callable, class... Args> void call_once(std::once_flag& flag, Callable&& func, Args&&... args);
|
- 线程安全:保证在多线程环境下,函数只被执行一次
- 高效:内部使用原子操作和锁,性能较好
- 异常安全:如果函数抛出异常,不算执行完成,其他线程会重试
- 只能在线程中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <iostream> #include <thread> #include <mutex> #include <string> class Log{ private:
static Log* log; static std::once_flag once;
public: Log(){} Log(const Log& log) = delete; Log& operator=(const Log& log) = delete; static Log& GetInstance(){ std::call_once(once, init); return *log; } void PrintLog(std::string msg) { std::cout<<__TIME__<<' '<<msg<<std::endl; } static void init() { log = new Log; } };
Log* Log::log = nullptr; std::once_flag Log::once; void func(){ Log::GetInstance().PrintLog("error"); }
int main(){ std::thread t1(func); std::thread t2(func); t1.join(); t2.join(); return 0; }
|