Fork之前创建了互斥锁,要警惕死锁问题
下面的这段代码会导致子进程出现死锁问题,您看出来了吗?includestdio。hincludepthread。hincludeunistd。hincludesyswait。hincludestringusingstd::string;pthreadmutextmutexPTHREADMUTEXINITIALIZER;voidfunc(voidarg){pthreadmutexlock(mutex);for(inti0;i10;i){sleep(1);}pthreadmutexunlock(mutex);returnNULL;}intmain(void){pthreadttid;pthreadcreate(tid,NULL,func,NULL);sleep(5);intretfork();if(ret0){printf(beforegetlock);func(NULL);printf(aftergetlock);return0;}elseif(ret0){pthreadjoin(tid,0);wait(NULL);}else{printf(forkfailed);exit(1);}return0;}
对上述代码进行编译,并运行:〔rootlocalhosttest3〕gmain。cppg〔rootlocalhosttest3〕。a。outbeforegetlock
我们发现子进程始终没有打印出aftergetlock的日志。
对fork熟悉的朋友们应该知道,在fork之后,由于copyonwrite机制,当子进程尝试修改数据时,会导致父子进程的内存分离,这个过程也将父进程中的互斥锁给拷贝了过来,也包括了互斥锁的状态(锁定,释放)。
在父进程启动时,首先创建了一个线程去执行func函数,为了让该线程在fork之前可以被调度执行,使用了sleep函数让主进程中的主线程让出cpu,从而执行func函数,在func函数中对互斥锁进行了加锁。
5s后,主进程的主线程sleep结束,从而执行fork函数,产生了子进程,子进程也继承了父进程中的互斥锁,也继承了该锁的锁定状态,因此尝试加锁时,就会出现死锁问题。
下面通过GDB调试验证我们的分析。使用GDB进行调试
如果有同志对GDB还不熟悉,请参考https:wizardforcel。gitbooks。io100gdbtipscontentindex。htmlopeninnewwindow〔rootlocalhosttest3〕gdba。out
首先设置同时调试父子进程(gdb)setdetachonforkoff
接下来,在fork之前下一个断点,然后进行单步调试。(gdb)b26Breakpoint1at0x401217:filemain。cpp,line26。(gdb)rStartingprogram:homeworkcppprojtest3a。out〔Threaddebuggingusinglibthreaddbenabled〕Usinghostlibthreaddblibrarylib64libthreaddb。so。1。〔NewThread0x7ffff7a8c640(LWP167076)〕Thread1a。outhitBreakpoint1,main()atmain。cpp:2626intretfork();Missingseparatedebuginfos,use:dnfdebuginfoinstallglibc2。3440。el9。x8664libgcc11。3。12。1。el9。x8664libstdc11。3。12。1。el9。x8664(gdb)n〔Newinferior2(process167113)〕Readingsymbolsfromhomeworkcppprojtest3a。out。。。Readingsymbolsfromlib64ldlinuxx8664。so。2。。。〔Threaddebuggingusinglibthreaddbenabled〕Usinghostlibthreaddblibrarylib64libthreaddb。so。1。27if(ret0){Missingseparatedebuginfos,use:dnfdebuginfoinstallglibc2。3440。el9。x8664libgcc11。3。12。1。el9。x8664libstdc11。3。12。1。el9。x8664(gdb)n33elseif(ret0)
单步到这里,子进程已经创建成功,我们打开另一个窗口查看一下,确实目前父子进程都已经启动了〔rootlocalhost〕psauxgrepvgrepgrepa。outroot1669310。31。418084455780pts0Sl05:290:00gdba。outroot1670720。00。0140202220pts0tl05:290:00homeworkcppprojtest3a。outroot1671130。00。0140201588pts0t05:300:00homeworkcppprojtest3a。out
这个时候,我们打印一下父进程中mutex的状态,如下所示:(gdb)pmutex1{data{lock1,count0,owner167076,nusers1,kind0,spins0,elision0,list{prev0x0,next0x0}},size0100000000000000244214020001,00repeats26times,align1}
因为之前父进程中的线程已经执行了func函数,因此锁的lock值为1,即锁定状态,锁的owner时167076,说明该锁由父进程所加。
接下来,切换到子进程查看:
单步到执行func函数之前。(gdb)infoinferiorNumDescriptionConnectionExecutable1process1670721(native)homeworkcppprojtest3a。out2process1671131(native)homeworkcppprojtest3a。out(gdb)inferior2〔Switchingtoinferior2〔process167113〕(homeworkcppprojtest3a。out)〕〔Switchingtothread2。1(Thread0x7ffff7a90380(LWP167113))〕00x00007ffff7ba98d7inFork()fromlib64libc。so。6(gdb)nSinglesteppinguntilexitfromfunctionFork,whichhasnolinenumberinformation。0x00007ffff7ba96fainfork()fromlib64libc。so。6(gdb)nSinglesteppinguntilexitfromfunctionfork,whichhasnolinenumberinformation。main()atmain。cpp:2727if(ret0){(gdb)n28printf(beforegetlock);(gdb)nbeforegetlock29func(NULL);
这个时候,我们查看一下子进程中mutex的状态,可以发现lock的值为1,说明目前该互斥锁已经被加锁。而且可以看到owner也属于父进程。(gdb)pmutex2{data{lock1,count0,owner167076,nusers1,kind0,spins0,elision0,list{prev0x0,next0x0}},size0100000000000000244214020001,00repeats26times,align1}(gdb)
到此,我们就验证了我们的分析,确实时由于锁的状态的继承,导致了子进程的死锁。如何解决该问题?
使用pthreadatfork函数在fork子进程之前清理一下锁的状态。includepthread。hintpthreadatfork(void(prepare)(void),void(parent)(void),void(child)(void));
https:man7。orglinuxmanpagesman3pthreadatfork。3。htmlopeninnewwindow
pthreadatfork()在fork()之前调用,当调用fork时,内部创建子进程前在父进程中会调用prepare,内部创建子进程成功后,父进程会调用parent,子进程会调用child。
修改之后,代码如下:includestdio。hincludepthread。hincludeunistd。hincludesyswait。hincludestringusingstd::string;pthreadmutextmutexPTHREADMUTEXINITIALIZER;voidfunc(voidarg){pthreadmutexlock(mutex);for(inti0;i10;i){sleep(1);}pthreadmutexunlock(mutex);returnNULL;}voidclean(){if(pthreadmutextrylock(mutex)!0){pthreadmutexunlock(mutex);}}intmain(void){pthreadttid;pthreadcreate(tid,NULL,func,NULL);sleep(5);pthreadatfork(NULL,NULL,clean);intretfork();if(ret0){printf(beforegetlock);func(NULL);printf(aftergetlock);return0;}elseif(ret0){pthreadjoin(tid,0);wait(NULL);}else{printf(forkfailed);exit(1);}return0;}
重新编译并运行,死锁问题解决了。〔rootlocalhosttest3〕。a。outbeforegetlockaftergetlock是否还有别的问题?
同样的代码,只是本此将锁增加了可重入的属性。我们再看看执行结果。includestdio。hincludepthread。hincludeunistd。hincludesyswait。hincludestringusingstd::string;pthreadmutextmutexPTHREADMUTEXINITIALIZER;pthreadmutexattrtmta;voidfunc(voidarg){pthreadmutexlock(mutex);for(inti0;i10;i){sleep(1);}pthreadmutexunlock(mutex);returnNULL;}voidclean(){if(pthreadmutextrylock(mutex)!0){intretpthreadmutexunlock(mutex);printf(retd,ret);}}intmain(void){增加可重入的属性pthreadmutexattrinit(mta);pthreadmutexattrsettype(mta,PTHREADMUTEXRECURSIVE);pthreadmutexinit(mutex,mta);pthreadttid;pthreadcreate(tid,NULL,func,NULL);sleep(5);pthreadatfork(NULL,NULL,clean);intretfork();if(ret0){printf(beforegetlock);func(NULL);printf(aftergetlock);return0;}elseif(ret0){pthreadjoin(tid,0);wait(NULL);}else{printf(forkfailed);exit(1);}return0;}
执行结果如下:〔rootlocalhosttest3〕。a。outret1beforegetlock
此时发现再次发生了死锁。
原因在于可重入锁解锁必须是相同的线程。子进程中的主线程并非加锁线程,因此无法解锁。
查看glibc中的相关实现:
https:github。comlatteraglibcblobmasternptlpthreadmutexunlock。copeninnewwindow
glicpthreadunlock
可以看到可重入锁解锁时,确实会有owner的检查。并且会返回EPERM的errno,EPERM1,这与我们打印出来的ret1是相一致的。结论fork函数执行后,子进程会继承来自父进程中的锁和锁的状态可重入锁解锁会检查owner,非owner不能解锁。在fork之前如果有创建互斥锁,一定需要小心其状态。
县级中国人民银行工作人员去向无非三种昨天国务院机构改革方案一经公布,瞬间冲上各大平台热搜榜。公众聚焦在不再保留中国人民银行县(市)支行之后,县级人民银行工作人员何去何从问题上。根据以往国家机构改革经验和各地县市机构合
3月9日凌晨,中国又传来25个新消息,认为很有必要告诉大家25专家建议退休人员征收个人所得税,凡是养老金高于五千的,一律征税!对此,你们大家怎么看?网友1提这个馊主意的人非妖即怪,可能吃了唐僧肉,会永远长生不老,永远不领退休金,家里更没有
秦刚反击美国后,不到24小时,马克龙联系拜登,联手应对中国挑战秦刚反击美国后,不到24小时,马克龙联系拜登称,联手应对中国挑战就在秦刚外长在记者会上呵斥美国人无下限做法后,美国赶紧跳出来解释称,没有压制中国的意思,并说尊重一个中国原则。然而,
代表委员议国是郑春阳委员进一步改革化妆品原料许可机制为推动我国化妆品行业高质量发展,今年全国两会期间,全国政协委员天津强微特生物科技有限公司董事长郑春阳呼吁,进一步改革化妆品原料许可机制,加强化妆品原料负面清单管理,增加注册类新原料
亚洲杯8强诞生6席!韩国00爆冷闷平8进4或遭遇中国男足日本或翻车2023年U20亚洲杯决赛阶段C组小组赛最后一轮,韩国男足与塔吉克斯坦男足00战平,约旦男足00战平阿曼男足,最终,韩国队与约旦队分别排在小组第12位晋级8强。至此,本届亚洲杯8强
95后文学每次去外公家,外公都会坐在门口的板凳上等我,我总是让他失望,但他却从来不埋怨我。有一年国庆节前,我便盘算着日子,打算给他意外的惊喜。去的那天,帮母亲忙完农活已是傍晚时分。四季分明的
观音山上观山水等你,今世途中相见(山水红尘三篇)观音山上观山水红尘是你,人间是你山海是你,彼岸是你玛吉阿米。圆通法中圆法性幡动是我,风动是我心动是我,不动是我仓央嘉措。那一日闭目在经殿香雾中蓦然听见,是你颂经中的真言。那一夜摇动
100个有些伤感的小句子精选1。人在变心的时候根本不会刹车2。幸福始终充满着缺陷。3。任何关系走到最后,不过相识一场,心者有所累,无心者无所谓,情出自愿,事过无悔,不负遇见,不谈亏欠。4。以后先了解我再喜欢我
世界上最好的养生静心文一藻俗语说心乱则百病生,心静则万病息。越是环境复杂,越要保持心态的平和。处变不惊,得失随缘,人生是一场修心之旅。心静则身安,万物静观皆自得。一个人最好的养生,是静心。保持情绪稳定
纠结点赞了很多感同身受的抖音。我却依然还无法释怀。这些年我错了错在优柔寡断错在没主见,一直被別人利用牵着鼻子走。我不知该怎么办无论是在感情还是在孩子身上我都无能为力了。以前那个那么高傲
旺自己的10个小妙招1不要纠结,不要内耗,要学会爱自己,包容自己,接纳自己的缺点,只有会爱自己,你才会获得更多的爱。2遇见问题不要逃避,要想着去解决,去正视问题,直面问题。3要用积极的心态去思考人和事