《C++ 多线程编程基础总结(thread / mutex / condition_variable / future / promise/atomic)》
x = x + 1;//std::ref 传递引用类型 传递的临时变量是给引用的时候要注意类型要统一t.join();return 0;传递临时变量的问题int main()//std::ref 传递引用类型 传递的临时变量是给引用的时候要注意类型要统一delete ptr;t.join();return 0;传递指针或引用指向已释放的内存的问题class Apublic:void foo()in
引言
一、线程库的基本使用
进程是运行中的程序
线程是进程中的进程
线程的多少取决于CPU的核数 几核几线程
std::thread thread1(printHelloWorld,"Hello Thread"); //创建线程
thread1.join(); //会堵塞,主程序等待线程执行完毕
thread1.detach(); //主程序结束,子线程在后台持续运行
bool isJion = thread1.joinable(); //判断能否调用join和detach的函数
二、线程函数中的数据未定义错误
#include<iostream>
#include<thread>
void foo(int & x)
{
x = x + 1;
}
int main()
{
int a = 1;
std::thread t(foo,std::ref(a)); //std::ref 传递引用类型 传递的临时变量是给引用的时候要注意类型要统一
t.join();
std::cout<<a<<std::endl;
return 0;
}
传递临时变量的问题
#include<iostream>
#include<thread>
std::thread t;
void foo(int *x)
{
std::cout<< *x << std::endl;
}
int main()
{
int* ptr = new int(1);
std::thread t(foo,ptr); //std::ref 传递引用类型 传递的临时变量是给引用的时候要注意类型要统一
delete ptr;
t.join();
return 0;
}
传递指针或引用指向已释放的内存的问题
#include<iostream>
#include<thread>
#include<memory>
class A
{
public:
void foo()
{
std::cout<<"Hello"<<std::endl;
}
};
int main()
{
std::shared_ptr<A> a =std::make_shared<A>();
std::thread t(&A::foo,a);
t.join();
}
使用智能指针解决问题!
#include<iostream>
#include<thread>
#include<memory>
class A
{
private:
friend void thread_foo();
void foo()
{
std::cout<<"Hello"<<std::endl;
}
};
void thread_foo()
{
std::shared_ptr<A> a =std::make_shared<A>();
std::thread t(&A::foo,a);
t.join();
}
int main()
{
thread_foo();
}
入口函数为类的私有成员函数的问题:用友元
三、互斥量解决多线程数据共享问题
数据共享问题:在多个线程中共享数据时,需要注意线程安全问题。如果多个线程同时访问同一个变量,并且其中至少有一个对该变量进行了写操作,那么就会出现数据竞争问题。数据竞争可能会导致程序崩溃、产生未定义的结果,或者得到错误的结果
解决:加锁(mutex)
#include<iostream>
#include<thread>
#include<mutex>
int a = 0;
std::mutex mtx;
void func()
{
for (int i = 0 ; i<10000 ; i++)
{
mtx.lock(); //获取所有权
a = a + 1 ;
mtx.unlock(); //释放所有权
}
}
int main()
{
std::thread t1(func);
std::thread t2(func);
t1.join();
t2.join();
std::cout<<a<<std::endl;
return 0;
}
线程安全:如果多线程程序每一次的运行结果和单线程运行的结果始终是一样的,那么你的线程就是安全的
四、互斥量死锁
#include<iostream>
#include<thread>
#include<mutex>
std::mutex m1,m2;
void func_1()
{
for(int i = 0 ; i < 50 ; i++)
{
m1.lock();
m2.lock();
m1.unlock();
m2.unlock();
}
}
void func_2()
{
for(int i = 0 ; i < 50 ; i++)
{
m2.lock();
m1.lock();
m1.unlock();
m2.unlock();
}
}
int main()
{
std::thread t1(func_1);
std::thread t2(func_2);
t1.join();
t2.join();
std::cout<<"over"<<std::endl;
return 0;
}
代码会产生死锁,如果让两个函数都m1开始获取,就不会死锁
五、lock_guard与unique_lock
1.lock_guard:
当函数构造时被调用,该互斥量会被自动锁定
当析构函数时被调用,该互斥量会被自动解锁
#include<iostream>
#include<thread>
#include<mutex>
int share_data = 0;
std::mutex mtx;
void func()
{
for (int i = 0 ; i<10000 ; i++)
{
std::lock_guard<std::mutex> lg(mtx);
share_data++;
}
}
int main()
{
std::thread t1(func);
std::thread t2(func);
t1.join();
t2.join();
std::cout<<share_data<<std::endl;
return 0;
}
2.unique_lock
lock_guard能做的它都能做,并且还能做到延迟加锁、条件变量、超时等
#include<iostream>
#include<thread>
#include<mutex>
int share_data = 0;
std::timed_mutex mtx;
void func()
{
for (int i = 0 ; 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(1)); //抢到了睡眠1秒
share_data++;
}
}
}
int main()
{
std::thread t1(func);
std::thread t2(func);
t1.join();
t2.join();
std::cout<<share_data<<std::endl;
return 0;
}
六、call_once与其使用场景(单例模式场景)
单例模式场景:单例设计模式是一种常见的设计模式,用于确保某个类只能创建一个实例。由于单例实例是全局唯一的,因此在多线程环境中使用单例模式时,需要考虑例程安全的问题。
(只能有一个实例,不能随便new,通过统一接口拿到这个实例)
#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;
}
};
int main()
{
Log::GetInstance().PrintLog( "error");
}
call_once只能在线程函数中使用,在main函数中调用call_once 会报错
#include<iostream>
#include<thread>
#include<mutex>
#include<string>
static Log* log = nullptr;
static std::once_flag once;
class Log
{
public:
Log() {};
Log (const Log& log) =delete;
Log& operator = (const Log& log) = delete;
static Log& GetInstance()
{
std::call_once(once,init);
return *log;
}
static void init()
{
if(!log) log =new Log;
}
void PrintLog(std::string msg)
{
std::cout<<__TIME__<<' '<<msg<<std::endl;
}
};
void print_error()
{
Log::GetInstance().PrintLog("error");
}
int main()
{
std::thread t1(print_error);
std::thread t2(print_error);
t1.join();
t2.join();
}
七、condition_variable与其使用场景(生产者消费者模型)
#include<iostream>
#include<thread>
#include<mutex>
#include<string>
#include<condition_variable>
#include<queue>
std::queue<int> g_queue;
std::condition_variable g_cv;
std::mutex mtx;
void Producer()
{
for (int i = 0 ; i < 10 ; i++){
{
std::unique_lock<std::mutex> lock(mtx);
g_queue.push(i);
g_cv.notify_one(); //通知消费者有任务
std::cout<<"Producer : " <<i<<std::endl;
}
std::this_thread::sleep_for(std::chrono::microseconds(1000));
}
}
void Consumer()
{
while(1)
{
std::unique_lock<std::mutex> lock(mtx);
g_cv.wait(lock,[]() { return !g_queue.empty(); //lambda
});
int value = g_queue.front();
g_queue.pop();
std::cout<<"Consumer : " <<value<<std::endl;
}
}
int main()
{
std::thread t1(Producer);
std::thread t2(Consumer);
t1.join();
t2.join();
return 0;
}
八、C++跨平台线程池
#include<iostream>
#include<thread>
#include<mutex>
#include<string>
#include<condition_variable>
#include<queue>
#include<vector>
#include<functional>
class ThreadPool
{
private:
std::vector<std::thread> threads;
std::queue<std::function<void()>> tasks;
std::mutex mtx;
std::condition_variable condition;
bool stop;
public:
ThreadPool(int numThreads):stop(false)
{
for (int i = 0 ; i < numThreads; i++)
{
threads.emplace_back([this]
{
while(1)
{
std::unique_lock<std::mutex> lock(mtx);
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(mtx);
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)...);
{
std::unique_lock<std::mutex> lock(mtx);
tasks.emplace(std::move(task));
}
condition.notify_one();
}
};
int main()
{
ThreadPool pool(4);
for (int i = 0 ; i < 10 ; i ++)
{
pool.enqueue( [i] {
std::cout<<"task : " <<i<< " is running" <<std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout<<"task : " <<i<<" is done " <<std::endl;
});
}
}
1.右值引用
int x = 0; //x是左值 左值为可以取地址的值
int y = x; //x还能再用
int z = 10; //10是右值 (临时的)
int&& r = 10 //√
int a = 5 ;
int&& r2 = a //×
2.bind 把函数和参数绑定
3.forward 完美转发
4.lambda
5.move 移动语义
6.std::this_thread::sleep_for(std::chrono::seconds(1));
7.auto 自动类型推导
8.可变列表
九、异步并发——async future packaged_task promise
async是C++11 引入的一个函数模板,用于异步执行一个函数,并返回一个std::future对象,表示异步操作的结果。使用std::async可以方便地进行异步编程,避免了手动创建线程和管理线程的麻烦
#include<iostream>
#include<future>
using namespace std;
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;
return 0 ;
}
//该代码的核心作用是:异步调用函数 func,并将其返回值存储在 std::future<int> 对象中。
//提高程序效率:将耗时操作放入异步线程执行,从而避免阻塞主线程。提供机制来获取异步任务的结果。
packaged_task的意义在于你可以手动的去启动线程, 而不是像async直接启动线程
#include<iostream>
#include<future>
using namespace std;
int func()
{
int i = 0;
for (i = 0 ; i< 1000; i++)
{
i++;
}
return i;
}
int main()
{
std::packaged_task<int()> task(func);
auto future_result = task.get_future();
std::thread t1(std::move(task));
cout<<func() <<endl;
t1.join();
cout<< future_result.get()<<endl;
return 0 ;
}
promise是一个类模板,用于在一个线程中产生一个值,并在另一个线程中获取这个值。promise通常与future和async一起使用,用于实现异步编程
#include<iostream>
#include<future>
using namespace std;
int func(std::promise<int> &f)
{
f.set_value(1000);
}
int main()
{
std::promise<int> f;
auto future_result = f.get_future();
std::thread t1(func,std::ref(f));
t1.join();
cout<<future_result.get()<<endl;
return 0 ;
}
十、原子操作 atomic
平常加锁再解锁的时间大约在57000左右
#include <iostream>
#include <thread>
#include <mutex>
int shared_data = 0;
std::mutex mtx;
void func()
{
for (int i = 0 ; i < 1000000 ; ++i)
{
mtx.lock();
shared_data++;
mtx.unlock();
}
}
int main()
{
auto last = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
std::thread t1(func);
std::thread t2(func);
t1.join();
t2.join();
std::cout<<"shared data = " <<shared_data << std::endl;
auto cur = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
std::cout <<cur - last<<std::endl;
return 0 ;
}
shared data = 2000000
57838
#include <iostream>
#include <thread>
#include <mutex>
#include<atomic>
std::atomic<int> shared_data = 0;
void func()
{
for (int i = 0 ; i < 1000000 ; ++i)
{
shared_data++;
}
}
int main()
{
auto last = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
std::thread t1(func);
std::thread t2(func);
t1.join();
t2.join();
std::cout<<"shared data = " <<shared_data << std::endl;
auto cur = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
std::cout <<cur - last<<std::endl;
return 0 ;
}
shared data = 2000000
39893
时间缩短了近一半, 原子操作相对于加锁解锁效率是高很多的
结尾语
更多推荐


所有评论(0)