范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

Fork之前创建了互斥锁,要警惕死锁问题

  下面的这段代码会导致子进程出现死锁问题,您看出来了吗? #include  #include  #include  #include  #include  using std::string; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  void* func(void* arg) {     pthread_mutex_lock(&mutex);     for(int i = 0;i < 10; ++i)     {         sleep(1);     }     pthread_mutex_unlock(&mutex);     return NULL; }  int main(void) {      pthread_t tid;     pthread_create(&tid, NULL, func, NULL);      sleep(5);     int ret = fork();     if (ret == 0) {         printf("before get lock ");         func(NULL);         printf("after get lock ");         return 0;     }     else if(ret > 0)     {         pthread_join(tid, 0);         wait(NULL);     }     else     {         printf("fork failed ");         exit(1);     }      return 0; }
  对上述代码进行编译, 并运行: [root@localhost test3]# g++ main.cpp -g [root@localhost test3]# ./a.out before get lock
  我们发现子进程始终没有打印出"after get lock"的日志。
  对fork熟悉的朋友们应该知道,在fork之后,由于copy-on-write机制,当子进程尝试修改数据时,会导致父子进程的内存分离,这个过程也将父进程中的互斥锁给拷贝了过来,也包括了互斥锁的状态(锁定,释放)。
  在父进程启动时,首先创建了一个线程去执行func函数,为了让该线程在fork之前可以被调度执行,使用了sleep函数让主进程中的主线程让出cpu,从而执行func函数,在func函数中对互斥锁进行了加锁。
  5s后,主进程的主线程sleep结束,从而执行fork函数,产生了子进程,子进程也继承了父进程中的互斥锁,也继承了该锁的锁定状态,因此尝试加锁时,就会出现死锁问题。
  下面通过GDB调试验证我们的分析。 使用GDB进行调试
  如果有同志对GDB还不熟悉,请参考 https://wizardforcel.gitbooks.io/100-gdb-tips/content/index.htmlopen in new window [root@localhost test3]# gdb a.out
  首先设置同时调试父子进程 (gdb) set detach-on-fork off
  接下来,在fork之前下一个断点,然后进行单步调试。 (gdb) b 26 Breakpoint 1 at 0x401217: file main.cpp, line 26. (gdb) r Starting program: /home/work/cpp_proj/test3/a.out [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib64/libthread_db.so.1". [New Thread 0x7ffff7a8c640 (LWP 167076)] Thread 1 "a.out" hit Breakpoint 1, main () at main.cpp:26 26          int ret = fork(); Missing separate debuginfos, use: dnf debuginfo-install glibc-2.34-40.el9.x86_64 libgcc-11.3.1-2.1.el9.x86_64 libstdc++-11.3.1-2.1.el9.x86_64 (gdb) n [New inferior 2 (process 167113)] Reading symbols from /home/work/cpp_proj/test3/a.out... Reading symbols from /lib64/ld-linux-x86-64.so.2... [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib64/libthread_db.so.1". 27          if (ret == 0) { Missing separate debuginfos, use: dnf debuginfo-install glibc-2.34-40.el9.x86_64 libgcc-11.3.1-2.1.el9.x86_64 libstdc++-11.3.1-2.1.el9.x86_64 (gdb) n 33          else if(ret > 0)
  单步到这里,子进程已经创建成功, 我们打开另一个窗口查看一下,确实目前父子进程都已经启动了 [root@localhost ~]# ps aux |grep -v grep|grep a.out root      166931  0.3  1.4 180844 55780 pts/0    Sl+  05:29   0:00 gdb a.out root      167072  0.0  0.0  14020  2220 pts/0    tl   05:29   0:00 /home/work/cpp_proj/test3/a.out root      167113  0.0  0.0  14020  1588 pts/0    t    05:30   0:00 /home/work/cpp_proj/test3/a.out
  这个时候,我们打印一下父进程中mutex的状态, 如下所示: (gdb) p mutex $1 = {__data = {__lock = 1, __count = 0, __owner = 167076, __nusers = 1, __kind = 0, __spins = 0, __elision = 0, __list = {__prev = 0x0,       __next = 0x0}}, __size = "0100000000000000244214020001", "00" , __align = 1}
  因为之前父进程中的线程已经执行了func函数, 因此锁的__lock值为1,即锁定状态,锁的__owner时167076, 说明该锁由父进程所加。
  接下来,切换到子进程查看:
  单步到执行func函数之前。 (gdb) info inferior   Num  Description       Connection           Executable * 1    process 167072    1 (native)           /home/work/cpp_proj/test3/a.out   2    process 167113    1 (native)           /home/work/cpp_proj/test3/a.out (gdb) inferior 2 [Switching to inferior 2 [process 167113] (/home/work/cpp_proj/test3/a.out)] [Switching to thread 2.1 (Thread 0x7ffff7a90380 (LWP 167113))] #0  0x00007ffff7ba98d7 in _Fork () from /lib64/libc.so.6 (gdb) n Single stepping until exit from function _Fork, which has no line number information. 0x00007ffff7ba96fa in fork () from /lib64/libc.so.6 (gdb) n Single stepping until exit from function fork, which has no line number information. main () at main.cpp:27 27          if (ret == 0) { (gdb) n 28              printf("before get lock "); (gdb) n before get lock 29              func(NULL);
  这个时候,我们查看一下子进程中mutex的状态, 可以发现__lock的值为1,说明目前该互斥锁已经被加锁。而且可以看到__owner也属于父进程。 (gdb) p mutex $2 = {__data = {__lock = 1, __count = 0, __owner = 167076, __nusers = 1, __kind = 0, __spins = 0, __elision = 0, __list = {__prev = 0x0,       __next = 0x0}}, __size = "0100000000000000244214020001", "00" , __align = 1} (gdb)
  到此,我们就验证了我们的分析, 确实时由于锁的状态的继承,导致了子进程的死锁。 如何解决该问题?
  使用pthread_atfork函数在fork子进程之前清理一下锁的状态。 #include   int pthread_atfork(void (*prepare)(void), void (*parent)(void),                     void (*child)(void));
  https://man7.org/linux/man-pages/man3/pthread_atfork.3.htmlopen in new window
  pthread_atfork()在fork()之前调用,当调用fork时,内部创建子进程前在父进程中会调用prepare,内部创建子进程成功后,父进程会调用parent ,子进程会调用child。
  修改之后,代码如下: #include  #include  #include  #include  #include  using std::string; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  void* func(void* arg) {     pthread_mutex_lock(&mutex);     for(int i = 0;i < 10; ++i)     {         sleep(1);     }     pthread_mutex_unlock(&mutex);     return NULL; }  void clean() {     if(pthread_mutex_trylock(&mutex) != 0)     {         pthread_mutex_unlock(&mutex);     } }  int main(void) {      pthread_t tid;     pthread_create(&tid, NULL, func, NULL);      sleep(5);     pthread_atfork(NULL, NULL, clean);     int ret = fork();     if (ret == 0) {         printf("before get lock ");         func(NULL);         printf("after get lock ");         return 0;     }     else if(ret > 0)     {         pthread_join(tid, 0);         wait(NULL);     }     else     {         printf("fork failed ");         exit(1);     }      return 0; }
  重新编译并运行,死锁问题解决了。 [root@localhost test3]# ./a.out before get lock after get lock是否还有别的问题?
  同样的代码,只是本此将锁增加了"可重入"的属性。我们再看看执行结果。 #include  #include  #include  #include  #include  using std::string; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutexattr_t   mta; void* func(void* arg) {     pthread_mutex_lock(&mutex);     for(int i = 0;i < 10; ++i)     {         sleep(1);     }     pthread_mutex_unlock(&mutex);     return NULL; }  void clean() {     if(pthread_mutex_trylock(&mutex) != 0)     {         int ret = pthread_mutex_unlock(&mutex);         printf("ret = %d ", ret);     } }  int main(void) {     //增加可重入的属性     pthread_mutexattr_init(&mta);     pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_RECURSIVE);     pthread_mutex_init(&mutex, &mta);      pthread_t tid;     pthread_create(&tid, NULL, func, NULL);      sleep(5);     pthread_atfork(NULL, NULL, clean);     int ret = fork();     if (ret == 0) {         printf("before get lock ");         func(NULL);         printf("after get lock ");         return 0;     }     else if(ret > 0)     {         pthread_join(tid, 0);         wait(NULL);     }     else     {         printf("fork failed ");         exit(1);     }      return 0; }
  执行结果如下: [root@localhost test3]# ./a.out ret = 1 before get lock
  此时发现再次发生了死锁。
  原因在于可重入锁解锁必须是相同的线程。子进程中的主线程并非加锁线程,因此无法解锁。
  查看glibc中的相关实现:
  https://github.com/lattera/glibc/blob/master/nptl/pthread_mutex_unlock.copen in new window
  glic-pthread-unlock
  可以看到可重入锁解锁时,确实会有owner的检查。并且会返回EPERM的errno, EPERM=1, 这与我们打印出来的ret=1是相一致的。 结论fork函数执行后,子进程会继承来自父进程中的锁和锁的状态 可重入锁解锁会检查owner, 非owner不能解锁。 在fork之前如果有创建互斥锁, 一定需要小心其状态。

中国国航无人陪伴机票预定,中国国航无人陪伴机票提前多久申请关注航旅在线了解更多省钱小妙招。相信很多人都有这种经历,孩子需要到外地或者回老家,自己因为工作的原因忙的实在抽不开时间来,所以航空公司推行了无人陪伴服务,可以不需要家属和监护人一同中国十大勾魂景色美到窒息罗平油菜花每年的春节过后,罗平坝子便是油菜花的海洋,荡漾着清香。蓝天映衬着峰林,峰林衬托着花田,千山万壑,花开成海,座座孤锋犹如花海中荡漾的岛屿。漫山遍野的金黄,如江水般轻舒漫卷荡湖北集旅游观光休闲度假于一体的4A级景区,是中国木兰八景之一一个城市的旅游业发展趋势的速度迅速,也是有许多城市针对旅游业的关注水平变的更高一些,并且许多风景区的发展前景非常大,根据与许多园林景观的连续管理方法下看,许多风景区的发展潜力都是会中国篮协名人堂15人出炉,姚明和易建联暂不在内众所周知,最近中国男篮正在墨尔本征战世预赛,而与此同时,中国篮协在近期在国内做出重磅决定,同样引起很多球迷的关注。就在最近中国篮协宣布,接下来将会在近期成立中国篮协名人堂,用来表彰中国男篮锋线有惊喜,广厦男篮小将首秀完美,搭档张镇麟让人期待中国男篮目前正在征战世预赛,在已经结束的两场比赛中,取得了一胜一负的战绩,目前在小组中排名第二,已经提前拿到了晋级下一轮的名额。通过这两场比赛来看,我们中国男篮的整体表现还是非常不国产操作系统大消息国家整合资源,发布开放麒麟,不内卷众所周知,在全球的PC操作系统领域,WindowsmacOS处于绝对主导地位,占了90以上的市场份额。国产操作系统,虽然这些年努力发展,但其所属的linux分类,占比不到5,差得非科贝巴萨有莱万等4大目标若激活第2个杠杆将有2亿欧引援资金直播吧7月2日讯科贝电台称,巴萨今夏引援有4大目标莱万孔德拉菲尼亚B席。巴萨相信,若他们能激活第2个经济杠杆,将有2亿欧的引援资金。此前在与第六街达成部分电视转播权的转让协议后,巴DNF110级BUFF换装价格有多离谱?旭旭宝宝直呼价格上头buff换装作为提升伤害最重要的细节之一,是玩家特别注重打造的对象!换装自86版本就已经有了,不过那时候由于对平民萌新并不公平,不是某些部位太贵,就是绝版了,因此很难拉满属性,例如等额本息VS等额本金,傻傻分不清楚?一分钟让你读懂说到买房,大多数人选择向银行贷款,然而贷款买房一般有两种选择方式等额本息和等额本金。两个词仅一字之差,但却有着很大的不同。那么,我们在买房的时候,到底要选择哪种还款方式,会更加适合怪不得玩家全都跑到韩服,DNF手游韩服这几项做得真得不错爱生活,爱游戏,大家好,我是你们的好朋友汤圆。关注汤圆,收获更多快乐哦!说起DNF手游,国内目前基本上已经凉凉,就连前几天鹅厂的27号游戏发布会,甚至对这个游戏只字未提,这不仅让苦这样的黄金首饰,才能算是艺术品点击关注不迷路,关注吉尔德,想必有所得古往今来,黄金饰品一直备受关注,不同于现代大众黄金首饰的简约时尚,高奢市场和古法工艺的黄金饰品似乎更能称之为艺术品。黄金有价,工艺无价,得益于
副处级公务员,40年工龄,2022年退休,能够领取多少养老金?视频加载中按照现行养老金的执行政策来看,机关事业单位人员如果在2022年办理退休,仍旧在养老金并轨的十年过渡期内,也就是我们常说的退休中人。因此该部分退休人员办理退休手续时,将会按你想别人的高利息,别人想你的本金你有被骗的经历吗?朋友们,大家好!我是蓝中秋。你有被骗的经历吗?我的一位朋友向我讲述了她亲身经历的被骗3万元的经过。现在将这个故事分享出来,希望对朋友们的投资理财有点帮助。为了记述方便,暂且使用第一徐工集团差点卖给美国徐工集团几乎便宜地卖给了美国。幸运的是,一个人明白了这个问题的严重性,这让z府意识到了这个问题的严重性。在交易过程中有一个黑幕,非常害怕仔细思考。这个混乱者是徐工的国内竞争对手。徐明清时代我国北方的国际运输线张库商道明清时代我国北方的国际运输线张库商道李桂仁塞北重镇张家口,群山环抱,峰峦叠障,地势隘峻,东望北京天津,北连内蒙古大草原和西北边疆,战略和交通地位都十分重要,素有神都北门之称。历史上被飞客推荐无数次的JW万豪,你还没住过吗?三张版图镇楼高空泳池三张版图镇楼窗外风景三张版图镇楼自助餐厅终于入住了心心念念的长沙JW酒店,最近在飞客论坛,长沙JW酒店也频繁出现在大家的视野里。地处南二环涂家冲的JW酒店位置相非比寻常秋冬旅行,金陵四十八景中的池寺山,空有遗迹人相依大风起兮云飞扬,家住六朝烟水间。这里是胖一和雪酱的探游日记,跟您分享诗词旅游中的所见所闻所感。这是一次非比寻常的秋冬旅行,池寺山,整个一次收集。这里,曾经是金陵四十八景之一,如诗如长期吃花生是降血压,还是升血压?一文讲清楚了花生素有长寿果的美誉。逢年过节很多家庭都会买一些花生,这也算是常见的坚果,营养丰富,不仅可以做下酒菜,更可做烹饪的辅助材料,制作成各种各样的美食。经常有人说每天吃一把花生能够养胃,胃不舒服,三分靠治,七分靠养三餐不规律,火锅配冷饮,饭后犯懒不想动,挨饿经常过了头。现代人生活越来越好,但肠胃却被自己作得越来越差。这肠胃问题就像那温水煮青蛙,一开始的放肆会慢慢导致不适。这加热的温水,是否已皮蛋,到底是人间美味还是健康杀手呢?现在知道不算晚皮蛋是一种独特的食物,但厌恶皮蛋的人不在少数,甚至有些人给皮蛋贴上了最恶心的食物的评价,这皮蛋真的就那么可怕吗?皮蛋本身是中国人餐桌上常吃的食物,如今看到皮蛋的评价不好,又了解了皮靠政策兜底,中国足球何以脱贫攻坚在众多的体育项目里,中国足球肯定是困难户中的困难户我们能消灭困扰几千年的贫困,却对中国足球束手无策我们不尊重规则与规律,我们却喜欢制定政策我们以为保护了中国足球的希望,却在不知不觉马斯克入主推特之后,已解雇公司CEO,接下来模仿中国微信?什么是生意人?就是不断生出主意的人!世人皆知,马斯克是全球首富,他虽然很有钱,但人家的钱也不是大风刮来的,所以马斯克也是非常懂得生意场上的成交之道!马斯克很早就钟情于推特了。今年4