如何使用线程库
std::thread 创建线程
thread1.join(); 阻塞主线程
thread1.detach(); 线程分离
cpp
#include<iostream>
#include<thread>
void helloworld(std::string msg) {
for (int i = 0; i < 10000; i++)
{
std::cout << i << std::endl;
}
//std::cout << msg << std::endl;
return;
}
int main() {
//1. 创建线程
std::thread thread1(helloworld, "hello Tread");
//thread1.join();
//thread1.detach();
bool isJoin = thread1.joinable();
if (isJoin)
{
thread1.join();
};
std::cout << "over" << std::endl;
}
线程中常见的数据传递错误
临时变量
cpp
#include <iostream>
#include <thread>
void foo(int& x) {
x += 1;
}
int main() {
int x = 1; // 将变量复制到一个持久的对象中
std::thread t(foo, std::ref(x)); // 将变量的引用传递给线程,不可以传递临时变量
t.join();
return 0;
}
传递指针,需要将指针或引用指向堆上的变量,或使用std::shared_ptr
等智能指针来管理对象的生命周期。
cpp
#include <iostream>
#include <thread>
void foo(int* ptr) {
std::cout << *ptr << std::endl;
delete ptr; // 在使用完指针后,需要手动释放内存
}
int main() {
int* ptr = new int(1); // 在堆上分配一个整数变量
std::thread t(foo, ptr); // 将指针传递给线程
t.join();
return 0;
}
cpp
#include <iostream>
#include <thread>
#include <memory> // 引入shared_ptr的头文件
void foo(std::shared_ptr<int> ptr) {
std::cout << *ptr << std::endl;
}
int main() {
std::shared_ptr<int> ptr = std::make_shared<int>(1); // 使用make_shared分配内存
std::thread t(foo, ptr); // 传递shared_ptr到线程
t.join();
return 0;
}
传递指针或引用指向已释放的内存的问题
cpp
#include <iostream>
#include <thread>
void foo(int& x) {
std::cout << x << std::endl;
}
int main() {
int x = 1;
std::thread t(foo, std::ref(x));
// 将变量的引用传递给线程,在线程函数执行期间,变量`x`的生命周期是有效的。
t.join();
return 0;
}
类对象被提前释放
cpp
#include <iostream>
#include <thread>
#include <memory>
class MyClass {
public:
void func() {
std::cout << "Thread " << std::this_thread::get_id()
<< " started" << std::endl;
std::cout << "Thread " << std::this_thread::get_id()
<< " finished" << std::endl;
}
};
int main() {
std::shared_ptr<MyClass> obj = std::make_shared<MyClass>();
std::thread t(&MyClass::func, obj);
//避免obj被提前销毁,导致未定义的行为
t.join();
return 0;
}
入口函数为类的私有成员函数
cpp
#include <iostream>
#include <thread>
class MyClass {
private:
friend void myThreadFunc(MyClass* obj);
void privateFunc(){
std::cout << "Thread "
<< std::this_thread::get_id() << " privateFunc" << std::endl;
}
};
void myThreadFunc(MyClass* obj) {
obj->privateFunc();
}
int main() {
MyClass obj;
std::thread thread_1(myThreadFunc, &obj);
thread_1.join();
return 0;
}
将 myThreadFunc 定义为 MyClass 类的友元函数,并在函数中调用 privateFunc 函数。在创建线程时,需要将类对象的指针作为参数传递给线程。
多线程数据共享
互斥锁 (mutex)
cpp
#include <iostream>
#include <thread>
#include <mutex>
int shared_data = 0;
std::mutex mtx;
void func(int n) {
for (int i = 0; i < 1000; ++i) {
mtx.lock();
shared_data++;
std::cout << "Thread " << n << " increment shared_data to " << shared_data << std::endl;
mtx.unlock();
}
}
int main() {
std::thread t1(func, 1);
std::thread t2(func, 2);
t1.join();
t2.join();
std::cout << "Final shared_data = " << shared_data << std::endl;
return 0;
}
互斥量死锁
如果 T1 获取了 mtx1 的所有权,但是无法获取 mtx2 的所有权,而 T2 获取了 mtx2 的所有权,但是无法获取 mtx1 的所有权,两个线程互相等待对方释放互斥量,就会导致死锁。解决办法,需要资源顺序化,或者通过一次性尝试锁定所有指定的互斥锁,确保要么成功获取所有锁,要么释放已持有的锁并重新尝试,从而避免了死锁。
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx1, mtx2;
void func1() {
// 锁定 mtx2,然后再锁定 mtx1
mtx2.lock();
std::cout << "Thread 1 locked mutex 2" << std::endl;
mtx1.lock();
std::cout << "Thread 1 locked mutex 1" << std::endl;
// 解锁 mtx1 和 mtx2
mtx1.unlock();
std::cout << "Thread 1 unlocked mutex 1" << std::endl;
mtx2.unlock();
std::cout << "Thread 1 unlocked mutex 2" << std::endl;
}
void func2() {
// 锁定 mtx2,然后再锁定 mtx1
mtx2.lock();
std::cout << "Thread 2 locked mutex 2" << std::endl;
mtx1.lock();
std::cout << "Thread 2 locked mutex 1" << std::endl;
// 解锁 mtx1 和 mtx2
mtx1.unlock();
std::cout << "Thread 2 unlocked mutex 1" << std::endl;
mtx2.unlock();
std::cout << "Thread 2 unlocked mutex 2" << std::endl;
}
int main() {
// 启动两个线程,分别执行 func1 和 func2
std::thread t1(func1);
std::thread t2(func2);
// 等待两个线程结束
t1.join();
t2.join();
return 0;
}
lock_guard 与 std::unique_lock
使用 std::lock 和 std::lock_guard 或 std::unique_lock,它们能确保同时尝试锁定多个互斥量,并且不会死锁。
cpp
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx1, mtx2;
void func1() {
std::lock(mtx1, mtx2); // 同时锁定两个互斥量,避免死锁
std::lock_guard<std::mutex> lk1(mtx1, std::adopt_lock); // adopt_lock表示互斥量已经被锁定
std::lock_guard<std::mutex> lk2(mtx2, std::adopt_lock);
std::cout << "Thread 1 locked mutexes 1 and 2" << std::endl;
// 自动解锁
}
void func2() {
std::lock(mtx1, mtx2); // 同时锁定两个互斥量
std::lock_guard<std::mutex> lk1(mtx1, std::adopt_lock);
std::lock_guard<std::mutex> lk2(mtx2, std::adopt_lock);
std::cout << "Thread 2 locked mutexes 1 and 2" << std::endl;
// 自动解锁
}
int main() {
std::thread t1(func1);
std::thread t2(func2);
t1.join();
t2.join();
return 0;
}
std::unique_lock 提供了以下几个成员函数:
- lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁。
- try_lock():尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则函数立即返回 false,否则返回 true。
- try_lock_for(const std::chrono::duration<Rep, Period>& rel_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间。
- try_lock_until(const std::chrono::time_point<Clock, Duration>& abs_time):尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间点。
- unlock():对互斥量进行解锁操作。
call_once
单例设计模式是一种常见的设计模式,用于确保某个类只能创建一个实例。由于单例实例是全局唯一的,因此在多线程环境中使用单例模式时,需要考虑线程安全的问题。
全局只需要一个该类的对象,
下面是一个简单的单例模式的实现:
cpp
#include<iostream>
#include<thread>
#include<mutex>
#include<string>
class Log {
Log() {};
public:
Log(const Log& log) = delete;
Log& operator = (const Log &log) = delete;
static Log& GetInstance() {
//static Log log;
//return log;
static Log *log = nullptr;
if (!log) log = new Log;
return *log;
}
void PrintLog(std::string msg) {
std::cout<<__TIME__<< msg << std::endl;
};
};
int main() {
Log::GetInstance().PrintLog("error");
}
使用 std::call_once 可以确保单例实例只会被创建一次,从而避免了多个对象被创建的问题。此外,使用 std::unique_ptr 可以确保单例实例被正确地释放,避免了内存泄漏的问题。
cpp
#include<iostream>
#include<string>
#include <thread>
#include <mutex>
class Log
{
public:
static Log& GetInstance()
{
//static Log log; //饿汉模式
//return log;
std::call_once(once, initfunc);
return *log;
}
static void initfunc()
{
if (!log)
{
log = new Log;
}
}
void PrintLog(std::string msg)
{
std::cout << __TIME__ << " " << msg << std::endl;
}
private:
Log() {}
~Log() {}
Log(const Log&) = delete;
Log& operator=(const Log&) = delete;
//静态成员必须类外初始化
static Log* log;
static std::once_flag once;
};
Log* Log::log = nullptr;
std::once_flag Log::once; void print_error()
{
Log::GetInstance().PrintLog("error");
}
int main()
{
std::thread t1(print_error);
std::thread t2(print_error);
t1.join();
t2.join();
return 0;
}
condition_variable
生产者与消费者模型