最新要闻

广告

手机

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案

家电

每日观点:c++11多线程入门<学习记录>

来源:博客园

最近学习了c++多线程相关知识,也算是对这方面内容的入门视频链接c++11并发与多线程视频课程

看了大概两周,简单进行总结参考文章C++11并发与多线程


(相关资料图)

PS:c++11提供了标准的可跨平台的线程库,本次多线程开发以此库为核心

一.并发,进程,线程理解

1.并发:两个或者更多的任务(独立的活动)同时发生(进行):一个程序同时执行多个独立的任务

2.进程:运行起来的可执行程序

3.线程:每个进程有唯一的主进程,可以通过写代码创建其他子线程<一个新线程,代表一条新的代码执行路径>

二.子线程创建与结束

1.线程创建:thread类提供了创建线程的接口 #include thread myThread(可调用对象); //创建myThread子线程,线程执行参数内代码(或者函数)c++中的可调用对象可以是函数、函数指针、lambda表达式、bind创建的对象或者重载了函数调用运算符的类对象<称为线程函数>

2.线程结束线程一旦创建,就会与主线程并发进行,此时如果主线程执行完毕,会强制退出程序,因此需要保证子线程在主线程之前执行完毕。库提供两种方式:myThread.join(); //表示此时阻塞主线程,等待子线程执行完毕与主线程汇合,一起结束整个进程myThread.detach(); //主线程不再与子线程汇合,不再等待子线程//detach后,子线程和主线程失去关联,主调线程无法再取得该被调线程的控制权,驻留在后台,由C++运行时库接管<由于detach使线程脱离主线程控制,若该线程使用了主线程或者其他线程中的内容,要注意内容是否有效合法,坑!>

三.线程ID和线程参数

1.线程ID:每一个线程都有自己独一无二的线程ID,可以用std::this_thread::get_id()来获取目前代码执行位置处于的线程ID

2.线程参数thread myThread(print(), ...);其中print为函数指针,后续参数就是传入print函数的参数,注意传参格式

四.多个线程创建,数据共享问题

1.多个线程创建可以用STL容器来便于多个线程的创建,如std::verctor,不过要注意创建一个线程要给对应的join或者detach函数,让线程可控

2.数据共享多线程程序要注意对共享数据的管理,由于线程执行顺序随机性,若对共享数据同时进行读和写,会导致读数据的一方得到脏数据,而写的一方修改混乱<若线程都只读共享数据,可无需管理>

五.互斥量和死锁

1.互斥量 #include 对共享数据的管理,最简便也是最佳方案就是用mutex锁进行管理mutex类似一把锁,锁住共享数据部分,如果需要访问共享数据时,先要查看目前锁是否打开,若锁打开则进入访问数据,并且锁上防止其他线程进入,访问结束后解锁;若锁未打开则在外循环等待直到锁打开

std::mutex myMutex;myMutex.lock();//...共享数据myMutex.unlock();

互斥量锁上了一定要记得打开,不然后续无法读取数据且线程会卡死类似于指针,要释放内存。因此也有lock_guard类模板,和智能指针一样,创建时自动调用lock,作用域外自动调用unlockstd::lock_guard myGuard(myMutex);

std::mutex myMutex;{  std::lock_guard myGuard(myMutex); //lock  //...共享数据} //unlock

2.死锁两个或两个以上的互斥量,由于在进程中锁的顺序不一样,导致两个或多个进程相互等待对方锁住的锁时,就产生了死锁。

<两个互斥量mutex1,mutex2。>a.线程A执行时,这个线程先锁mutex1,并且锁成功了,然后去锁mutex2的时候,出现了上下文切换。b.线程B执行,这个线程先锁mutex2,因为mutex2没有被锁,即mutex2可以被锁成功,然后线程B要去锁mutex1.c.此时,死锁产生了,A锁着mutex1,需要锁mutex2,B锁着mutex2,需要锁mutex1,两个线程没办法继续运行下去。。。

只要保证多个互斥量上锁的顺序一样就不会造成死锁。

六.unique_lock类模板

<很类似unique_ptr>unique_lock可以取代lock_guard,理解为更加灵活的lock_guard,但效率相对较低

1.第二参数:std::adopt_lock:表示这个互斥量已经被lock(),即不需要在构造函数中lock这个互斥量了

std::try_to_lock:尝试用mutex的lock()去锁定这个mutex,但如果没有锁定成功,会立即返回,不会阻塞在那里

std::defer_lock:如果没有第二个参数就对mutex进行加锁,加上defer_lock是始化了一个没有加锁的mutex

2.常用成员函数:lock():加锁

unlock():解锁

try_lock():尝试给互斥量加锁如果拿不到锁,返回false,否则返回true。

release():解除与锁的绑定,返回它所管理的mutex对象的指针,并释放所有权

七.条件变量

1.condition_variable:为一个类,为互斥量解锁设定条件

std::mutex mymutex1;std::unique_lock sbguard1(mymutex1);std::condition_variable condition;condition.wait(sbguard1, [this] {if (!msgRecvQueue.empty())                                    return true;                                return false;                                });  //锁 + 解锁条件 condition.wait(sbguard1); //不建议这么写

2.notify_one、notify_allwait阻塞时,如果接收到其他地方的notify指令,则会尝试解锁.a)如果lambda表达式为true,则wait返回,流程可以继续执行(此时互斥量已被锁住)b)如果表达式为false,那wait又对互斥量解锁,然后又休眠,等待再次被notify_one()唤醒<无第二参数默认为true>PS:由于多线程执行随机性,可能会出现虚假notify,notify的时候wait线程不处于wait

notify_one():通知一个线程的wait()notify_all():通知所有线程的wait()

八.async、future、packaged_task、promise

1.async、future std::async是一个函数模板,用来启动一个异步任务,启动起来一个异步任务之后,它返回一个std::future对象

std::future对象,为类模板。“future”将来的意思,也有人称呼std::future提供了一种访问异步操作结果的机制,就是说这个结果你可能没办法马上拿到,但是在不久的将来,这个线程执行完毕的时候,你就能够拿到结果了,所以,大家这么理解:future中保存着一个值,这个值是在将来的某个时刻能够拿到

std::future对象的get()成员函数会等待线程执行结束并返回结果,拿不到结果它就会一直等待,感觉有点像join()。但是,它是可以获取结果的。std::future对象的wait()成员函数,用于等待线程返回,本身并不返回结果,这个效果和std::thread 的join()更像。

2.std::packaged_task:打包任务,把任务包装起来为类模板,它的模板参数是各种可调用对象,通过packaged_task把各种可调用对象包装起来,方便将来作为线程入口函数来调用。

int mythread(int mypar){...}std::packaged_task mypt(mythread);//用法一std::thread t1(std::ref(mypt), 1);t1.join();std::future result = mypt.get_future(); cout << result.get() << endl;//用法二,直接调用mypt(1);std::future result = mypt.get_future();cout << result.get() << endl;

3.std::promise类模板我们能够在某个线程中给它赋值,然后我们可以在其他线程中,把这个值取出来

九.future其他成员函数、shared_future、atomic

1.future其他成员函数std::future_status status = result.wait_for(std::chrono::seconds(5s));卡住当前流程,等待std::async()的异步任务运行一段时间,然后返回其状态std::future_status。std::future_status是枚举类型,表示异步任务的执行状态。类型的取值有std::future_status::timeout //时间耗尽,还未运行结束std::future_status::ready //运行结束std::future_status::deferred //async为deferred状态

2.std::shared_future:也是个类模板std::future的 get() 成员函数是转移数据std::shared_future 的 get()成员函数是复制数据

3.std::atomic原子操作<“不可分割的操作”>原子操作,指的是执行该操作时,CPU不会强制切换时间片,必须等该操作完全执行完成,才会切换时间片,因此可以保护数据合法性。和互斥量类似,但从效率上来说,原子操作要比互斥量的方式效率要高。互斥量的加锁一般是针对一个代码段,而原子操作针对的一般都是一个变量。

#include std::atomic g_count = 0; //封装了一个类型为int的 对象(值)void mythread1() {for (int i = 0; i < 1000000; i++) {g_count++;}}nt main() {std::thread t1(mythread1);std::thread t2(mythread1);t1.join();t2.join();cout << "正常情况下结果应该是次,实际是" << g_count << endl; //2000000}

十.std::atomic续谈、std::async深入谈

1.一般atomic原子操作,针对++,--,+=,-=,&=,|=,^=是支持的,其他操作不一定支持。

2.std::async深入理解第二参数:std::launch::deferred【延迟调用】std::launch::async【强制创建一个线程】如果同时用 std::launch::async | std::launch::deferred这里这个 | 意味着async的行为可能是 std::launch::async 创建新线程立即执行, 也可能是 std::launch::deferred 没有创建新线程并且延迟到调用get()执行,由系统根据实际情况来决定采取哪种方案<若无第二参数,这种为默认值>

async不确定性问题的解决不加额外参数的async调用时让系统自行决定,是否创建新线程。std::futureresult = std::async(mythread);问题焦点在于这个写法,任务到底有没有被推迟执行。通过wait_for返回状态来判断:

std::future_status status = result.wait_for(std::chrono::seconds(6));//std::future_status status = result.wait_for(6s);if (status == std::future_status::timeout) {//超时:表示线程还没有执行完cout << "超时了,线程还没有执行完" << endl;}else if (status == std::future_status::ready) {//表示线程成功放回cout << "线程执行成功,返回" << endl;cout << result.get() << endl;}else if (status == std::future_status::deferred) {cout << "线程延迟执行" << endl;cout << result.get() << endl;}

十一. windows临界区、其他各种mutex互斥量

1.windows临界区Windows临界区,同一个线程是可以重复进入的,但是进入的次数与离开的次数必须相等。C++互斥量则不允许同一个线程重复加锁。

#include CRITICAL_SECTION my_winsec;//windows中的临界区,非常类似C++11中的mutexInitializeCriticalSection(&my_winsec);//用临界区之前要初始化EnterCriticalSection(&my_winsec);//进入临界区// ....LeaveCriticalSection(&my_winsec);//离开临界区

2.其他各种mutex互斥量递归独占互斥量std::recursive_mutex:允许在同一个线程中同一个互斥量多次被 lock() ,(但是递归加锁的次数是有限制的,太多可能会报异常),效率要比mutex低。

带超时的互斥量std::timed_mutex 和 std::recursive_timed_mutex:判断是否拿到锁,若未拿到可以继续执行其他代码

十二.线程池浅谈、线程数量谈

1.线程池把一堆线程弄到一起,统一管理。这种统一管理调度,循环利用的方式,就叫做线程池。实现方式:程序启动时,一次性创建好一定数量的线程。这种方式让人更放心,觉得程序代码更稳定。

2.数量谈A、线程创建的数量极限的问题一般来讲,2000个线程基本就是极限;再创建就会崩溃。

B、线程创建数量建议a、采用某些计数开发程序提供的建议,遵照建议和指示来确保程序高效执行。b、创建多线程完成业务;考虑可能被阻塞的线程数量,创建多余最大被阻塞线程数量的线程,如100个线程被阻塞再充值业务,开110个线程就是很合适的c、线程创建数量尽量不要超过500个,尽量控制在200个之内;

END<信号量呢??>

关键词: