C++多线程
1.创建线程
void printHellWorld(std::string msg){std::cout << msg << std::endl; } int main(){std::thread thread1(printHellWorld,"Hello Thread"); //创建线程 从后面传递参数thread1.join(); //让主线程等待子线程执行完毕return 0; }
thread1.detach(); //分离主线程和子线程的,主线程可以结束,子线程在后台可以运行
//判断是否可以调用join 返回bool值 bool isJoin = thread1.joinable(); if(isJoin){thread1.join(); }
2.线程函数中的数据未定义错误
1.传递临时变量
#include <iostream> #include <thread> void foo(int& x) {x += 1; } int main() {std::thread t(foo, 1); // 传递临时变量 传不过去t.join();return 0; }
在这个例子中,定义了一个名为foo
的函数,它接受一个整数引用作为参数,并将该引用加1。然后,我们创建了一个名为t
的线程,将foo
函数以及一个临时变量1
作为参数传递给它。这样会导致在线程函数执行时,临时变量1
被销毁,从而导致未定义行为。
解决方案是将变量复制到一个持久的对象中,然后将该对象传递给线程。例如,我们可以将1
复制到一个int
类型的变量中,然后将该变量的引用传递给线程。
int a =1; std::thread t(foo, std::ref(a));
2.传递指针或引用指向局部变量的问题
#include <iostream> #include <thread> void foo(int& x) {x += 1; } void test(){int a =1; //局部变量 std::thread t(foo, std::ref(a)); } int main() {test(); //调用完之后就释放了a了t.join();return 0; }
-
类成员函数作为入口函数,类对象被提前释放
#include <iostream> #include <thread> class MyClass { public:void func() {std::cout << "Thread " << std::this_thread::get_id() << " started" << std::endl;// do some workstd::cout << "Thread " << std::this_thread::get_id() << " finished" << std::endl;} }; int main() {MyClass obj;std::thread t(&MyClass::func, &obj);// obj 被提前销毁了,会导致未定义的行为return 0; }
上面的代码中,在创建线程之后,obj 对象立即被销毁了,这会导致在线程执行时无法访问 obj 对象,可能会导致程序崩溃或者产生未定义的行为。
为了避免这个问题,可以使用 std::shared_ptr 来管理类对象的生命周期,确保在线程执行期间对象不会被销毁。具体来说,可以在创建线程之前,将类对象的指针封装在一个 std::shared_ptr 对象中,并将其作为参数传递给线程。这样,在线程执行期间,即使类对象的所有者释放了其所有权,std::shared_ptr 仍然会保持对象的生命周期,直到线程结束。
以下是使用 std::shared_ptr 修复上面错误的示例:
#include <iostream> #include <thread> #include <memory> class MyClass { public:void func() {std::cout << "Thread " << std::this_thread::get_id() << " started" << std::endl;// do some workstd::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);t.join();return 0; }
上面的代码中,使用 std::make_shared 创建了一个 MyClass 类对象,并将其封装在一个 std::shared_ptr 对象中。然后,将 std::shared_ptr 对象作为参数传递给线程。这样,在线程执行期间,即使 obj 对象的所有者释放了其所有权,std::shared_ptr 仍然会保持对象的生命周期,直到线程结束。
3.互斥量解决多线程数据共享问题
线程安全:如果多线程程序每一次的运行结果和单线程运行的结果始终是一样的,那么多线程就是安全的。
为了防止多个线程去访问同一个变量导致数据竞争的问题
加互斥锁mutex 加锁和解锁操作。
#include <iostream> #include <thread> #include <mutex> int shared_data = 0; std::mutex mtx; void func(int n) {for (int i = 0; i < 100000; ++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; }
4.lock_guard、unique_lock
1.lock_guard:自动会给互斥量加锁
在构造函数中调用加锁操作:lock
在析构函数中调用解锁操作:unlock
std::mutex mtx; std::lock_guard<std::mutex> lg(mtx);
std::lock_guard
禁用了移动和拷贝,对象不能复制或移动,因此它只能在局部作用域中使用
2.unique_lock
比lock_gurad的功能更多:延迟加锁、条件变量、超时等;
1)加锁加锁
std::mutex mtx; std::unque_lock<std::mutex> lg(mtx);
2)try_lock
try_lock()
:尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则函数立即返回 false
,否则返回 true
。
3)try_lock_for
尝试对互斥量进行加锁操作,如果当前互斥量已经被其他线程持有,则当前线程会被阻塞,直到互斥量被成功加锁,或者超过了指定的时间。
std::mutex mtx; std::unque_lock<std::time_mutex> lg(mtx,std::defer_lock); //取消自动加锁,手动加锁 lg.try_lock_for(std::chrono::seconds(1)); //1秒后自动释放锁
unique_lock支持move()右值引用操作,但是不支持拷贝构造和=
因为unique_lock是独占的,如果支持了拷贝构造,那么多个实例就会拥有互斥锁的所有权。移动不会复制所有权
5.call_once
单例模式:确保某个类只能创建一个实例,由于单例实例是全局唯一的,因此在多线程环境中使用单例模式时,需要考虑线程安全的问题。
class Log{ public:Log(){};Log(const Log& log) = delete; //禁用拷贝构造Log& operaztor = (const Log& log) = delete; //禁用=static Log& GetInstance(){ //创建一个对象static Log *log = nullper;if(!log) log = new Log();return *log;}void printLog(std::string msg){std::cout << msg << std::endl;} }; int main(){Log::GetInstence().printLog("error"); }
call_once:保证在多个线程中只使用一次
new Log();如果多线程同时执行,可能会创建多个对象
call_once只能在多线程中使用
static Log *log = nullper; static std::once_flag once; class Log{ public:Log(){};Log(const Log& log) = delete; //禁用拷贝构造Log& operaztor = (const Log& log) = delete; //禁用=static Log& GetInstance(){ //创建一个对象std::call_once(once,init);return *log;}void printLog(std::string msg){std::cout << msg << std::endl;}static void init(){if(!log) log = new Log();} }; void print_error(){Log::GetInstence().printLog("error"); } int main(){std::thread t1(print_error);std::thread t2(print_error);t1.join();t2.join();return 0; }
6.condition_variable条件变量
条件变量是根据条件控制线程执行或等待的变量
使用步骤:
-
创建一个 std::condition_variable 对象。
-
创建一个互斥锁 std::mutex 对象,用来保护共享资源的访问。
-
在需要等待条件变量的地方
-
使用 std::unique_lockstd::mutex 对象锁定互斥锁
-
并调用 std::condition_variable::wait()、std::condition_variable::wait_for() 或 std::condition_variable::wait_until() 函数等待条件变量。
-
在其他线程中需要通知等待的线程时,调用 std::condition_variable::notify_one() 或 std::condition_variable::notify_all() 函数通知等待的线程
生产者与消费者模型
#include <iostream> #include <thread> #include <mutex> #include <condition_variable> #include <queue> std::mutex g_mutex; std::condition_variable g_cv; std::queue<int> g_queue; void Producer() {for (int i = 0; i < 10; i++) {{ std::unique_lock<std::mutex> lock(g_mutex);g_queue.push(i); std::cout << "Producer: produced " << i << std::endl;}g_cv.notify_one(); std::this_thread::sleep_for(std::chrono::milliseconds(100));} } void Consumer() { while (true) { std::unique_lock<std::mutex> lock(g_mutex); g_cv.wait(lock, []() { return !g_queue.empty(); }); //为空阻塞,等待唤醒 不为空时,往下进行 int value = g_queue.front();g_queue.pop(); std::cout << "Consumer: consumed " << value << std::endl;} } int main() { std::thread producer_thread(Producer); std::thread consumer_thread(Consumer);producer_thread.join();consumer_thread.join(); return 0; }
7.实现跨平台线程池
线程池:维护 线程数组、任务队列、
线程数组去任务队列取任务
生产者往任务队列加任务
#include<iostream> #include<thread> #include<vector> #include<queue> #include<functional> #include<mutex> class threadPool { public://构造函数threadPool(int numThread) : stop(false){//开辟多少线程for (int i = 0; i < numThread; i++) {//添加线程threads.emplace_back([this] {//线程将不断等待新任务的到来while (1) {std::unique_lock<std::mutex> lock(mutex);condition.wait(lock, [this] {return !tasks.empty() || stop; //当任务不为空时,肯定是不阻塞的 线程终止了也不阻塞});if (stop && tasks.empty()) { //线程终止了return;}//线程没有终止 并且我任务队列中是有任务的//取任务std::function<void()> task(std::move(tasks.front()));//任务队列减一tasks.pop();//解锁lock.unlock();//执行任务task();}});}}//析构函数~threadPool() {{//加锁std::unique_lock<std::mutex> lock(mutex);//线程终止stop = true;}//通知线程,把任务队列中的任务全部取完condition.notify_all();//让线程把任务队列里的任务全部完成for (auto& t : threads) {//等待所有线程结束t.join();} }//向任务队列中添加新任务template<class F,class... Args>void enqueue(F &&f,Args&&...args) {//任务std::function<void()>task =std::bind(std::forward<F>(f), std::forward<Args>(args)...); //将传入的函数f和参数args绑定成一个无参的函数对象,并将其放在任务队列中//放到任务队列中{std::unique_lock<std::mutex> lock(mutex);tasks.emplace(std::move(task));}//唤醒一个正在等待的线程去执行任务condition.notify_one(); } private://线程池中的所有线程std::vector<std::thread> threads;//任务队列:有好多任务,每个任务都是一个无参的函数对象std::queue<std::function<void()>> tasks;//互斥锁std::mutex mutex;//条件变量std::condition_variable condition;//线程什么时候终止bool stop; //true:线程池停止接受新任务,false:线程池正在运行 }; void printTask(int i ) {//每个任务打印当前任务的编号和线程IDstd::cout << "Task " << i << "is runing in thread" << std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout << "Task " << i << "is done" << std::endl; } int main() {threadPool pool(4);//8个任务for (int i = 0; i < 8; i++) {pool.enqueue(printTask,i);}return 0; }
8.异步并发
1.async、future
函数模板,用于异步执行一个函数,并返回一个std::future对象,表示异步操作的结果。使用std::async可以方便得进行异步编程,避免手动创建线程和管理线程的麻烦。
int func(){int i =0;for(i = 0; i<1000;i++){i++;}return i; } int main(){std::future<int> future_result = std::async(std::launch::async,func);cout << func() <<endl;cout << future_result.get() << endl; }
当把函数传给async时候,理解成是一个线程,在后台已经开始运行了,运行完之后,最后返回的结果会存在future_result这里。这里是不阻塞的。
2.packaged_task
packaged_task是一个类模板,用于将一个可调用对象(如函数、函数对象或者lambda表达式)封装成一个异步操作,并返回一个std::future对象,表示异步操作的结果
int func(){int i =0;for(i = 0; i<1000;i++){i++;}return i; } int main(){std::packaged_task<int()> task(func); //把函数封装成一个packaged_task 后台运行,将结果放到future对象里面auto future_result = task.get_future(); //获取future对象std::thread t1(std::move(task));cout << func() <<endl;t1.join();cout << future_result.get() << endl; }
3.promise
promise是一个类模板,用于在一个线程中产生一个值,并在另一个线程中获取这个值。promise通常与future和saync一起使用,用于实现异步编程。
void func(std::promise<int> &f){f.set_value(1000); } int main(){std::promise<int> f;auto future_result = f.get_future();astd::thread t1(func,std::ref(f));t1.join();cout << future_result.get() <<endl; //主线程获取子线程中的值return 0; }
9.原子操作
#include<atomic> std::atomic<int> shared_data=0; void func(){for(int i =0;i<1000;i++){shared_data++;} }