南宁网站建设公司哪里,国外产品设计网,优秀校园网站建设汇报,试用期工作总结进程#xff1a;运行中的程序 线程#xff1a;进程中的进程 线程的最大数量取决于CPU的核心数
一、将两个函数添加到不同线程中
demo#xff1a;两个函数test01()和test02()#xff0c;实现将用户输入的参数进行打印输出1000次 将这两个函数均放到独立的线程t1和t2中运行中的程序 线程进程中的进程 线程的最大数量取决于CPU的核心数
一、将两个函数添加到不同线程中
demo两个函数test01()和test02()实现将用户输入的参数进行打印输出1000次 将这两个函数均放到独立的线程t1和t2中可实现独立的运作与主函数独立执行
#include thread线程头文件 std::thread t1(test01, 10);创建线程t1第一个参数为函数名第二个参数为对应函数的参数若函数没有参数则不需要填入 if (t1.joinable())判断线程t1是否可以加入到主线程中若可以加入t1.join();则加入到主线程中 子线程加入到主线程可以保证主线程会等待子线程运行完毕之后再结束
t1.join();是将子线程t1则加入到主线程中 t1.detach();是将主线程main和子线程t1分离确保主线程结束后子线程仍可以再后台执行
t1.join();较为常用
#include iostream
#include thread
#include stringvoid test01(int num)
{for (int i 0; i 1000; i) {std::cout test01 num i std::endl;// test01 10 0// test01 10 1// test01 10 2//...}
}void test02(std::string str)
{for (int i 0; i 1000; i){std::cout test02 str i std::endl;// test02 hello beyond 0// test02 hello beyond 1// test02 hello beyond 2// ...}
}int main(int argc, char* argv[])
{//创建线程把函数名和参数传递给线程std::thread t1(test01, 10);//第一个参数是函数名第二个参数是该函数所需要的参数std::thread t2(test02, hello beyond);//第一个参数是函数名第二个参数是该函数所需要的参数if (t1.joinable()) //判断线程是否还在运行{//若没有将t1线程加入到主线程则主线程结束时t1线程也会结束导致程序崩溃t1.join(); //等待线程结束若不把线程t1加入到主进程中则主进程不会等待t1进程结束之后再结束若主进程结束了项目结束//t1.detach(); //分离线程主线程结束时t1线程也会结束不会导致程序崩溃//join()和detach()的区别在于join()等待线程结束detach()分离线程主线程结束时t1线程不会结束可以继续运行}if (t2.joinable()) {t2.join(); //等待线程结束若不把线程t2加入到主进程中则主进程不会等待t2进程结束之后再结束若主进程结束了项目结束}//主线程for (int i 0; i 1000; i) {std::cout main i std::endl;}//就此开了三个线程main()、t1、t2三个线程并发执行return 0;
}运行效果 可以看到线程t1和t2是并发执行的执行完之后主线程main才进行执行 这说明在主线程main中程序是按顺序执行的而相互独立的线程是并行执行的
二、引用类型传入线程中
若要操作同一个变量在函数中需要传入该变量的引用 若要加入到线程中则需要通过std::ref(a)将变量a的引用加入到线程中操作的都是该变量a共享同一个变量a
#include iostream
#include thread
#include stringvoid test01(int num1) //引用传递
{for (int i 0; i 10; i){num1;}
}void test02(int num2) //值传递
{for (int i 0; i 10; i){num2;}
}int main(int argc, char* argv[])
{int num1 0;int num2 0;std::thread t1(test01, std::ref(num1));//引用传递std::thread t2(test02, num2);//值传递if (t1.joinable()) {t1.join();} if (t2.joinable()) {t2.join();} std::cout num1 num1 std::endl;// 输出结果为10std::cout num2 num2 std::endl;// 输出结果为0return 0;
}三、锁和死锁
1现象分析 俩线程都对num进行自加10000次期间会出现t1和t2线程同时拿到num并自加导致结果会重复多次出现无效的自加效果
#include iostream
#include thread
#include stringvoid test01(int num) //引用传递
{for (int i 0; i 100000; i){num;}
}void test02(int num) //值传递
{for (int i 0; i 100000; i){num;}
}int main(int argc, char* argv[])
{int num 0;std::thread t1(test01, std::ref(num));//引用传递std::thread t2(test02, std::ref(num));//引用传递if (t1.joinable()) {t1.join();} if (t2.joinable()) {t2.join();} //按理说num应该是200000但是由于线程并发执行导致结果可能是100000也可能是200000std::cout num num std::endl;return 0;
}运行结果 实际效果并不是20000
2解决方法加锁
#include mutex互斥锁头文件 std::mutex mtx;定义互斥锁 mtx.lock();加锁 mtx.unlock();解锁
在加锁和解锁之间的数据是不允许多个线程进行操作 当线程t1拿到num之后立马加锁线程t2就无法获取num直到线程t1解锁后线程t2才可以拿到num
加锁操作保证多线程安全
#include iostream
#include thread
#include string
#include mutexstd::mutex mtx;//定义互斥锁void test01(int num) //引用传递
{for (int i 0; i 1000000; i){mtx.lock();//加锁num;mtx.unlock();//解锁}
}void test02(int num) //值传递
{for (int i 0; i 1000000; i){mtx.lock();//加锁num;mtx.unlock();//解锁}
}int main(int argc, char* argv[])
{int num 0;std::thread t1(test01, std::ref(num));//引用传递std::thread t2(test02, std::ref(num));//引用传递if (t1.joinable()) {t1.join();} if (t2.joinable()) {t2.join();} //按理说num应该是200000但是由于线程并发执行导致结果可能是100000也可能是200000std::cout num num std::endl;return 0;
}运行效果
3死锁现象 #include iostream
#include thread
#include string
#include mutexstd::mutex mtx1, mtx2;//定义互斥锁void test01(int num1,int num2) //引用传递
{for (int i 0; i 10000; i){mtx1.lock();//加锁num1;mtx2.lock();//加锁num2;mtx2.unlock();//解锁mtx1.unlock();//解锁}
}void test02(int num1,int num2) //值传递
{for (int i 0; i 10000; i){mtx2.lock();//加锁num2;mtx1.lock();//加锁num1;mtx1.unlock();//解锁mtx2.unlock();//解锁}
}int main(int argc, char* argv[])
{int num1 0;int num2 0;std::thread t1(test01, std::ref(num1), std::ref(num2));//引用传递std::thread t2(test02, std::ref(num1), std::ref(num2));//引用传递if (t1.joinable()) {t1.join();} if (t2.joinable()) {t2.join();} //按理说num应该是200000但是由于线程并发执行导致结果可能是100000也可能是200000std::cout run is over std::endl;//死锁永远不会执行std::cout num1 num1 std::endl;std::cout num2 num2 std::endl;return 0;
}运行效果 陷入死锁
4死锁解决方法
方法一要锁谁都锁谁
也就是互斥锁的先后顺序要一致要都先锁num1就都先锁num1
#include iostream
#include thread
#include string
#include mutexstd::mutex mtx1, mtx2;//定义互斥锁void test01(int num1,int num2) //引用传递
{for (int i 0; i 10000; i){mtx1.lock();//加锁num1;mtx2.lock();//加锁num2;mtx2.unlock();//解锁mtx1.unlock();//解锁}
}void test02(int num1,int num2) //值传递
{for (int i 0; i 10000; i){mtx1.lock();//加锁num1;mtx2.lock();//加锁num2;mtx2.unlock();//解锁mtx1.unlock();//解锁}
}int main(int argc, char* argv[])
{int num1 0;int num2 0;std::thread t1(test01, std::ref(num1), std::ref(num2));//引用传递std::thread t2(test02, std::ref(num1), std::ref(num2));//引用传递if (t1.joinable()) {t1.join();} if (t2.joinable()) {t2.join();} std::cout run is over std::endl;std::cout num1 num1 std::endl;std::cout num2 num2 std::endl;return 0;
}运行效果
方法二使用智能互斥锁
四、智能互斥锁
1lock_guard
lock_guard会在其构造函数中自动加锁在其析构函数中自动解锁 无需人为的去加锁解锁操作
std::mutex mtx1;定义互斥锁 std::lock_guardstd::mutex lock1(mtx1);将互斥锁升级为智能互斥锁是一个函数模板
#include iostream
#include thread
#include string
#include mutexstd::mutex mtx1, mtx2;//定义互斥锁void test01(int num1,int num2) //引用传递
{for (int i 0; i 10000; i){std::lock_guardstd::mutex lock1(mtx1);num1;num2;}
}void test02(int num1,int num2) //值传递
{for (int i 0; i 10000; i){std::lock_guardstd::mutex lock1(mtx1);num1;num2;}
}int main(int argc, char* argv[])
{int num1 0;int num2 0;std::thread t1(test01, std::ref(num1), std::ref(num2));//引用传递std::thread t2(test02, std::ref(num1), std::ref(num2));//引用传递if (t1.joinable()) {t1.join();} if (t2.joinable()) {t2.join();} std::cout run is over std::endl;std::cout num1 num1 std::endl;std::cout num2 num2 std::endl;return 0;
}运行效果
2unique_lock推荐使用
lock_guard只支持自动加锁解锁操作 unique_lock不仅仅支持自动加锁解锁操作还支持超时处理、延迟加锁、达到某些条件后进行加锁等操作
①将unique_lock直接替换lock_guard效果是一样的
自动加锁自动解锁
#include iostream
#include thread
#include string
#include mutexstd::mutex mtx1, mtx2;//定义互斥锁void test01(int num1,int num2) //引用传递
{for (int i 0; i 10000; i){std::unique_lockstd::mutex lock1(mtx1);num1;num2;}
}void test02(int num1,int num2) //值传递
{for (int i 0; i 10000; i){std::unique_lockstd::mutex lock1(mtx1);num1;num2;}
}int main(int argc, char* argv[])
{int num1 0;int num2 0;std::thread t1(test01, std::ref(num1), std::ref(num2));//引用传递std::thread t2(test02, std::ref(num1), std::ref(num2));//引用传递if (t1.joinable()) {t1.join();} if (t2.joinable()) {t2.join();} std::cout run is over std::endl;std::cout num1 num1 std::endl;std::cout num2 num2 std::endl;return 0;
}运行效果
②手动上锁
std::unique_lockstd::mutex lock1(mtx1,std::defer_lock);需要传入参数std::defer_lock 用户需要手动上锁自动解锁 手动上锁是为了可以使用一些延迟上锁等互斥锁 之前的是常规互斥锁std::mutex mtx1; 例如手动上锁的时候可以使用时间互斥锁std::timed_mutex mtx1;
std::timed_mutex mtx1;定义时间互斥锁 std::unique_lockstd::timed_mutex lock1(mtx1, std::defer_lock);定义一个局部锁使用defer_lock参数表示不加锁手动加锁自动解锁 bool res lock1.try_lock_for(std::chrono::milliseconds(100));尝试加锁超时时间为100ms如果加锁成功则返回true否则返回false
五、单例模式下的多线程使用
单例模式全局只有一个实例初始化操作只能执行一次 若在多线程中就有可能被执行多次此时需要使用call_once 常用的单例模式为日志类只需要一次实例化对象
Singleton类实现了单例模式。通过std::call_once结合std::once_flag来保证Singleton类的构造函数只被调用一次从而保证在多线程环境下只有一个Singleton实例被创建。在main函数中创建了两个线程这两个线程都调用Singleton::getInstance方法来获取单例实例并调用showMessage方法。
#include iostream
#include mutex
#include threadclass Singleton {
private:// 私有构造函数Singleton() {std::cout Singleton created. std::endl;}static Singleton* instance;static std::once_flag flag;
public:// 获取单例实例的方法static Singleton* getInstance() {// 确保初始化只进行一次std::call_once(flag, []() {instance new Singleton();});return instance;}void showMessage() {std::cout Singleton is working. std::endl;}
};// 静态成员变量初始化
Singleton* Singleton::instance nullptr;
std::once_flag Singleton::flag;// 线程函数
void threadFunction() {Singleton* single Singleton::getInstance();single-showMessage();
}int main() {std::thread t1(threadFunction);std::thread t2(threadFunction);t1.join();t2.join();return 0;
}六、条件变量的使用
生产者与消费者模型 生产者导师消费者导师的学生 一个导师一般情况下会招多个学生导师申请到国家基金或横向课题将课题任务分成多个小块依次分配给学生A、学生B、学生C等进行完成 导师将任务放到任务队列中学生从任务队列中取出任务并完成若任务队列中没有任务了学生就可以等待一会儿摸会儿鱼。等导师又有新任务了放入到任务队列中并通知学生学生收到通知就不需要再等待了开始去任务队列中取任务。 导师往任务队列中加入一个任务因为是一个任务所以只通知一次notify_once至于哪个学生去完成导师不关心但需要一个学生来进行完成 导师往任务队列中加入了很多个任务因为任务比较多需要所有的学生进行完成notify_all把所有的学生都通知一遍。
极端例子任务队列为空导师放任务到任务队列的同时学生来任务队列中取任务学生发现任务队列中没任务开始摸鱼其实导师已经放入了一个任务
此时就需要加锁并引入条件变量导师往任务队列中放任务的时候加锁放完之后发通知 学生从任务队列中取任务之前先判断任务队列中是否为空若为空等待直到收到导师发的通知之后再开始从任务队列中取任务
g_cv.wait(lock, bool); 参数一互斥锁 参数二布尔值若为true则执行下一行若为false则等待 一般这个bool值用lambda表达式设置
#include iostream
#include mutex
#include thread
#include condition_variable//引入条件变量头文件
#include queue//任务对列头文件std::queueint g_task_queue; //任务队列 导师将任务放到任务队列中队列先进先出导师先放入的任务学生先完成
std::condition_variable g_cv; //创建一个条件变量
std::mutex g_mutex; //创建一个互斥锁void Teacher()//导师生产者负责产生任务并通知学生
{for (int i 0; i 10; i) //导师将10个任务放入任务队列中{std::unique_lockstd::mutex lock(g_mutex); //获取互斥锁g_task_queue.push(i); //将任务放入队列g_cv.notify_one();//通知学生来一个学生完成这个任务//g_cv.notify_all();//通知所有学生完成这个任务std::cout Teacher produce task i std::endl; //打印提示信息}//导师放任务到任务队列的速度别太快睡个100msstd::this_thread::sleep_for(std::chrono::milliseconds(100)); //等待100ms模拟学生完成任务
}void Students()//学生消费者负责消费任务并等待通知
{while (true) //学生一直等待通知{std::unique_lockstd::mutex lock(g_mutex); //获取互斥锁//等待通知若队列为空则阻塞等待bool is_empty g_task_queue.empty();//判断队列是否为空//lambda表达式为ture执行下一行false阻塞等待直到条件变量调用notify_one来唤醒线程该等待结束g_cv.wait(lock, []() {return !g_task_queue.empty(); //队列不为空则返回true通知学生}); //等待通知若队列为空则阻塞等待int task; //定义一个任务变量task g_task_queue.front(); //取出队列头部的任务g_task_queue.pop(); //从队列中删除任务std::cout Student consume task task std::endl; //打印提示信息}
}int main(int argc, char* argv[])
{std::thread t1(Teacher); //创建导师线程std::thread t2(Students); //创建学生线程if (t1.joinable()){t1.join(); //等待导师线程结束}if (t2.joinable()){t2.join(); //等待学生线程结束}return 0;
}七、跨平台线程池 现准备好线程池包括线程数组和任务队列等待用户放任务到任务队列中现场数组分配线程去任务队列中取任务完成
#include iostream
#include mutex
#include thread
#include condition_variable//引入条件变量头文件
#include queue//任务对列头文件
#include vector
#include functional//引入函数对象头文件class ThreadPool
{
public:ThreadPool(int num_threads) :m_stop(false) {//往线程数组中添加num_threads个线程for (int i 0; i num_threads; i){// push_back()函数向线程数组中添加一个线程的时候会进行一个拷贝因此这里使用emplace_back()函数直接调用成员构造函数避免拷贝// 比push_back()函数效率高更加节省资源避免了构造函数的调用m_threads.emplace_back([this](){while (true) {std::unique_lockstd::mutex lock(m_mutex);//加锁//条件变量等待直到有任务需要处理m_cv.wait(lock, [this]() {//如果线程池需要停止或者任务对列为空则返回false通知线程等待//否则返回true通知线程继续执行下一行代码return m_stop ||!m_tasks.empty();});//如果线程池需要停止并且任务对列为空则退出线程if (m_stop m_tasks.empty()){return;}//线程池没有停止任务对列不为空则取出一个任务std::functionvoid() task m_tasks.front();//取出任务列表中的第一个任务m_tasks.pop();//取出任务列表中的第一个任务//取到任务之后解锁让其他线程有机会去取任务lock.unlock();//解锁task();//执行任务}});}}~ThreadPool(){//指定锁的作用域{std::unique_lockstd::mutex lock(m_mutex);//加锁//因为m_stop是多个线程共享的所以在析构函数中需要加锁确保线程安全m_stop true;//设置线程池停止标志}m_cv.notify_all();//通知所有线程线程池需要停止for (std::thread t: m_threads){if (t.joinable())//如果线程还没有结束则等待线程结束{t.join();}}}//向任务对列中添加任务templatetypename F, typename... Args//函数模板可变参数模板void enqueue(F f, Args... args) {// 右值引用可以避免拷贝提高效率// 左值引用可以传引用避免拷贝提高效率//在函数模板里面 表示万能引用在函数模板中 右值引用就是万能引用//std::forwardF(f) 转发函数对象避免拷贝提高效率std::functionvoid() task std::bind(std::forwardF(f), std::forwardArgs(args)...);//使用bind函数将任务封装成函数对象//对共享变量操作需要加锁确保线程安全指定锁的作用域{std::unique_lockstd::mutex lock(m_mutex);m_tasks.emplace(std::move(task));//向任务对列中添加任务}//通知线程数组中的一个线程去取任务m_cv.notify_one();}
private:std::vectorstd::thread m_threads;//线程数组std::queuestd::functionvoid() m_tasks;//任务对列std::mutex m_mutex;//互斥锁//生产者生产任务去通知线程数组线程数组去指派线程去完成任务std::condition_variable m_cv;//条件变量 线程池符合生产者消费者模型故需要条件变量bool m_stop false;//线程池是否停止
};int main(int argc, char* argv[])
{//10个任务0-9由3个线程去完成ThreadPool pool(3);//创建线程池线程数为3//向任务对列中添加任务for (int i 0; i 10; i){pool.enqueue([i]() {std::cout Task i running on thread std::this_thread::get_id() std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));//休眠1秒模拟任务执行时间std::cout Task i finished on thread std::this_thread::get_id() std::endl;}); }return 0;
}运行效果
八、异步并发
1async
无需手动去创建线程当运行到std::futureint f1 std::async(std::launch::async, func);时自动会有个异步线程去调用func函数同时开启一个线程立马执行通过f1.get()取到函数返回的结果
#include iostream
#include futureint func()
{int num 0;for (int i 0; i 10000; i){num;}return num;
}int main(int argc, char* argv[])
{//主线程和子线程同步只不过主线程执行完func函数后会直接输出结果//子线程执行完func函数后会将结果存入f中主线程通过f.get()获取结果//子线程执行func函数std::futureint f1 std::async(std::launch::async, func);//执行到这行代码时func函数已经开始执行但是并不等待func函数的结果而是返回一个future对象通过这个future对象可以获取func函数的结果std::cout main thread result: func() std::endl;//主线程执行func函数并打印结果//以上两者是同时执行的主线程和子线程同步std::cout async result: f1.get() std::endl;//会将return的结果存放到f1中通过f1.get()获取结果return 0;
}运行效果
2future
async会立马自动调用线程执行而future需要手动创建线程执行
std::packaged_taskint() task(func);创建packaged_task对象task包装func函数 std::futureint f task.get_future();创建future对象f用于获取func函数的结果 std::thread t(std::move(task));将task的控制权移交给t线程t线程负责执行task函数并将结果存入f中
#include iostream
#include futureint func()
{int num 0;for (int i 0; i 10000; i){num;}return num;
}int main(int argc, char* argv[])
{std::packaged_taskint() task(func);//创建task对象包装func函数std::futureint f task.get_future();//创建future对象f用于获取func函数的结果std::thread t(std::move(task));//将task的控制权移交给t线程t线程负责执行task函数并将结果存入f中子线程t执行func函数与主线程同步std::cout main thread result: func() std::endl;//主线程执行func函数并打印结果if (t.joinable()) {t.join();//等待子线程t线程结束}std::cout packaged_task result: f.get() std::endl;//获取f中存放的func函数的结果return 0;
}运行效果
3promise
场景在主线程中获取子线程中通过promise所设置的值
#include iostream
#include futurevoid func(std::promiseint prom)
{prom.set_value(42);//设置promise的值
}int main(int argc, char* argv[])
{std::promiseint prom;//声明一个promise对象std::futureint fut prom.get_future();//promise对象会返回一个future对象用于获取promise的值std::thread t(func, std::ref(prom));启动一个线程并传入promise对象作为参数if (t.joinable()) {t.join();}std::cout fut.get() std::endl;//获取得到promise所设置的值return 0;
}运行效果
九、原子操作
atomic原子操作是一个模板类用于在多线程中访问和修改共享变量避免多线程中所出现的数据竞争现象的发生 原子操作和锁所处理的都是同一类问题 把变量设置为atomc类型该变量自身自带线程安全操作会自动加锁和解锁且要比手动加锁解锁的速度更快
#include iostream
#include thread
#include atomicstd::atomicint g_num_atomic 0;//原子变量
int g_num 0;//非原子变量void func()
{for (int i 0; i 1000000; i) {g_num_atomic;g_num;}
}int main(int argc, char* argv[])
{std::thread t1(func);std::thread t2(func);if (t1.joinable()) t1.join();if (t2.joinable()) t2.join();std::cout g_num g_num std::endl;//输出非原子变量的值因为g_num是非原子变量所以可能出现数据竞争导致结果不准确std::cout g_num_atomic g_num_atomic std::endl;//输出原子变量的值return 0;
}运行效果 全局变量g_num是一个int由于是多线程操作故出现了数据竞争问题导致数据不对 全局变量g_num_atomic是一个int型的原子变量该变量会自动加锁和解锁不会发生数据竞争问题
学习笔记参考来源陈子青——C11 多线程编程-小白零基础到手撕线程池 若有侵权联系立删