Chinaunix

标题: 请高手解释下程序运行过程:多线程加锁阻塞问题 [打印本页]

作者: 埋头苦编    时间: 2011-11-23 10:31
标题: 请高手解释下程序运行过程:多线程加锁阻塞问题
本帖最后由 埋头苦编 于 2011-11-23 13:22 编辑

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define THRNR 6

static pthread_mutex_t mut[THRNR];

static int next(int n)
{
        if (n+1==THRNR) {
                return 0;
        }
        return n+1;
}

static void *thr_abcd(void *ptr)
{
        int c='a';

        c+=(int)ptr;
        while (1) {
                pthread_mutex_lock(mut+(int)ptr);
//sleep(1);       
write(1, &c, 1);
//sleep(1);
pthread_mutex_unlock(mut+next((int)ptr));
        }
}


int
main()
{
        int i;
        pthread_t tid[THRNR];

        for (i=0;i<THRNR;++i) {
                pthread_mutex_init(mut+i, NULL);
                pthread_create(tid+i, NULL, thr_abcd, (void*)i);
        }

        sleep(5);

        exit(0);
}
程序效果:abcd连续出来
红色sleep不会影响程序效果,但是黄色的会打乱输出顺序,为什么?
作者: 埋头苦编    时间: 2011-11-23 13:23
自己顶了,我认为在没解锁前其他线程都是阻塞的,在write前后加sleep因该没区别,可在write后加sleep输出顺序就变了
作者: 1514_louluo    时间: 2011-11-23 13:40
回复 2# 埋头苦编


    刚刚试了一下,如果去掉两个sleep,正常反复输出abcdef连续5秒,如果加上红色的sleep,只有第一次是正常abcdef 后面输出的顺序也是乱的;如果只加上黄色 的sleep,每次都是乱序。
作者: 狗蛋    时间: 2011-11-23 14:00
sleep会导致线程交出CPU控制权。

这个函数相当于明确告诉操作系统,在接下来的一段时间里,它什么都不干。
作者: 埋头苦编    时间: 2011-11-23 14:08
回复 3# 1514_louluo
我多是了几次,加红色的有一直abcdef的,也有第二次后乱序,后加黄色的第一次也是顺序的,后面的肯定是乱序,我认为在锁里面睡应该不会影响调度的,因为没解锁时其他线程都是阻塞的,两种情况都应该一样。结果不知道该怎么理解
作者: asuka2001    时间: 2011-11-23 14:10
1. 不清楚LZ使用的什么系统,我使用Ubuntu 10.10进行测试发现,两个sleep随便注释哪个,都是随机的输出
2. 两个sleep都注释掉,在经过一段随机输出后,才能得到连续输出,我将开头一段截取出来,红色开始为连续输出了。LZ可以自己思考下为什么!
dbccabcefadbecfabdcefdaebfcabcdefabcdefabcdefabcdefabcdefabcdefabcdef
作者: 埋头苦编    时间: 2011-11-23 14:20
回复 6# asuka2001

我用的红帽子,我想文的是文什么会有乱许,尤其是你说都注释后还有乱许,更难理解了,这里锁已经限定线程顺序了,应该不存在调度机制导致乱序的阿
作者: 1514_louluo    时间: 2011-11-23 14:49
回复 5# 埋头苦编


    兄弟,你认为的是for循环里面先用pthread_create创建的线程先执行,所以导致了你认为所有的流程都基于这个时序之上。其实用pthread_create创建的线程并不能保证哪个会先执行!
作者: digdeep126    时间: 2011-11-23 14:57
回复 1# 埋头苦编
你这个例子,只要第一个输出的字符是'a',后面的字符不管以什么顺序输出,都是合理的。因为,程序的实现是:6个线程分别不断的输出 'a', 'b', 'c', 'd', 'e', 'f'。那么逻辑上,肯定是第一个线程先获得锁,输出了第一个'a',其它后面的线程的执行顺序是不确定的。
作者: digdeep126    时间: 2011-11-23 14:58
回复 1# 埋头苦编
你这个例子,只要第一个输出的字符是'a',后面的字符不管以什么顺序输出,都是合理的。因为,程序的实现是:6个线程分别不断的输出 'a', 'b', 'c', 'd', 'e', 'f'。那么逻辑上,肯定是第一个线程先获得锁,输出了第一个'a',其它后面的线程的执行顺序是不确定的。
作者: digdeep126    时间: 2011-11-23 14:59
回复 1# 埋头苦编

你这个例子,只要第一个输出的字符是'a',后面的字符不管以什么顺序输出,都是合理的。因为,程序的实现是:6个线程分别不断的输出 'a', 'b', 'c', 'd', 'e', 'f'。那么逻辑上,肯定是第一个线程先获得锁,输出了第一个'a',其它后面的线程的执行顺序是不确定的。
作者: digdeep126    时间: 2011-11-23 14:59
回复 1# 埋头苦编
你这个例子,只要第一个输出的字符是'a',后面的字符不管以什么顺序输出,都是合理的。因为,程序的实现是:6个线程分别不断的输出 'a', 'b', 'c', 'd', 'e', 'f'。那么逻辑上,肯定是第一个线程先获得锁,输出了第一个'a',其它后面的线程的执行顺序是不确定的。
作者: digdeep126    时间: 2011-11-23 15:00
回复 1# 埋头苦编

你这个例子,只要第一个输出的字符是'a',后面的字符不管以什么顺序输出,都是合理的。因为,程序的实现是:6个线程分别不断的输出 'a', 'b', 'c', 'd', 'e', 'f'。那么逻辑上,肯定是第一个线程先获得锁,输出了第一个'a',其它后面的线程的执行顺序是不确定的。
作者: digdeep126    时间: 2011-11-23 15:02
回复 1# 埋头苦编


你这个例子,只要第一个输出的字符是'a',后面的字符不管以什么顺序输出,都是合理的。因为,程序的实现是:6个线程分别不断的输出 'a', 'b', 'c', 'd', 'e', 'f'。那么逻辑上,肯定是第一个线程先获得锁,输出了第一个'a',其它后面的线程的执行顺序是不确定的。
作者: digdeep126    时间: 2011-11-23 15:07
刚才CU的数据库好像 “卡住了”,只能读,不能写!难道在做数据库备份?
作者: 埋头苦编    时间: 2011-11-23 15:15
回复 8# 1514_louluo

解锁的时候限定了顺序,至少应该有序,不一定是abcdef
作者: 埋头苦编    时间: 2011-11-23 15:17
回复 13# digdeep126
第一个输出后,解锁时限定的线程顺序不管用???
作者: 1514_louluo    时间: 2011-11-23 15:28
回复 16# 埋头苦编


    兄弟,你还是认为abc,先要锁a放b,然后b才能够锁b放c,其实不是的,为什么不可以是锁a放b的这个时候,b已经锁上了,a放b的时候恰好解开了b的锁?你再自己想象一下~
作者: 1514_louluo    时间: 2011-11-23 15:30
回复 16# 埋头苦编
兄弟,你还是认为abc,先要锁a放b,然后b才能够锁b放c,其实不是的,为什么不可以是锁a放b的这个时候,b已经锁上了,a放b的时候恰好解开了b的锁?你再自己想象一下~
作者: 1514_louluo    时间: 2011-11-23 15:32
回复 16# 埋头苦编


    兄弟,你还是认为abc,先要锁a放b,然后b才能够锁b放c,其实不是的,为什么不可以是锁a放b的这个时候,b已经锁上了,a放b的时候恰好解开了b的锁?你再自己想象一下~
作者: 1514_louluo    时间: 2011-11-23 15:33
回复 20# 1514_louluo


    论坛怎么回事。。。
作者: file3    时间: 2011-11-23 15:38
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <pthread.h>

  4. #define THRNR 6

  5. static pthread_mutex_t mut[THRNR];

  6. static int next(int n)
  7. {
  8.         if (n+1==THRNR) {
  9.                 return 0;
  10.         }
  11.         return n+1;
  12. }

  13. static void *thr_abcd(void *ptr)
  14. {
  15.         int c='a';

  16.         c+=(int)ptr;
  17.         while (1) {
  18.                 pthread_mutex_lock(mut+(int)ptr);
  19. //sleep(1);      
  20. write(1, &c, 1);
  21. //sleep(1);
  22. pthread_mutex_unlock(mut+next((int)ptr));
  23.         }
  24. }


  25. int
  26. main()
  27. {
  28.         int i;
  29.         pthread_t tid[THRNR];

  30.         for (i=0;i<THRNR;++i) {
  31.                 pthread_mutex_init(mut+i, NULL);
  32.                 if(i > 0) {
  33.                         pthread_mutex_lock(mut+i);
  34.                 }
  35.                 pthread_create(tid+i, NULL, thr_abcd, (void*)i);
  36.         }

  37.         sleep(5);

  38.         exit(0);
  39. }
复制代码
试试。
作者: digdeep126    时间: 2011-11-23 15:43
回复  digdeep126
第一个输出后,解锁时限定的线程顺序不管用???
埋头苦编 发表于 2011-11-23 15:17

第一个输出a之后,解锁,然后,这时可能会有6个线程来竞争这个锁,但是哪一个线程获得锁,是不确定的。
作者: 1514_louluo    时间: 2011-11-23 15:53
22楼正解,关键是他没有为bcdef初始化锁,所以abcdef刚开始的时候,对应的解锁完全没有作用~!
作者: 埋头苦编    时间: 2011-11-23 15:54
回复 20# 1514_louluo
您能解释的在详细点马,我这里解锁的顺序一定,也就是线程上锁的顺序一定,而变量只有一个,被上一次锁加1,循环执行,第一次不一定是a,但是应该有序阿!
作者: 1514_louluo    时间: 2011-11-23 16:00
回复 25# 埋头苦编

你在初始化的时候,需要如下:

36         pthread_t tid[THRNR];
37
38         for (i=0;i<THRNR;++i) {
39                 pthread_mutex_init(mut+i, NULL);
40                if(i != 0)
41                     pthread_mutex_lock(mut+i);
42                 pthread_create(tid+i, NULL, thr_abcd, (void*)i);
43         }
44
因为你没有为输出bcdef的那几个线程加锁,所以对应你刚开始a释放的b的那个锁,没有用~~!所以就会出现我说那种情况!
作者: digdeep126    时间: 2011-11-23 16:02
没有看到pthread_mutex_unlock(mut+next((int)ptr)); 中带有next(ptr) 函数。22楼很强悍。
作者: asuka2001    时间: 2011-11-23 16:10
本帖最后由 asuka2001 于 2011-11-23 16:16 编辑

假设在LZ的系统上线程先创建,必定先执行,简单起见,只模拟3个线程A, B, C,以";"作为系统调度表示。

如果有黄色的sleep:

1. lock A, 输出 A, sleep; lock B, 输出 B, sleep; lock C, 输出C, sleep;
输出为ABC

2. unlock B, lock A阻塞; unlock C, lock B, 输出B, sleep; unlock A, lock C, 输出C, sleep;

3. lock A, 输出A, sleep;
输出为BCA

下次的话B先, 但是A还没给它解锁, 所以输出为CAB....

仅为大脑模拟运行, LZ自行验证下对不对吧...
作者: asuka2001    时间: 2011-11-23 16:13
顺带抱怨一句, LZ的锁使用的有些欠妥; 请拿锁来保护公共数据, 而不是代码.....
作者: 埋头苦编    时间: 2011-11-23 16:29
回复 24# 1514_louluo
这里6把锁在创建时就初始化了阿
作者: 埋头苦编    时间: 2011-11-23 16:31
回复 26# 1514_louluo
刚开始就创建了6个锁,难道不initializer就不存在马?
作者: 1514_louluo    时间: 2011-11-23 16:39
回复 30# 埋头苦编


   不好意思,我的意思是说锁住bcdef。你初始化的时候如果不锁住bcdef的话,刚开始你的a先锁上自己,然后释放b的锁,注意b这个时候是没有锁的,有可能它已经执行到write了,这时你的a把b的锁释放了,这个时候b又没有锁了,所以下一次cpu调度可能b又锁住自己,然后write。。。这样就都乱了。如果初始化的时候就锁住bcdef,每次b都要等到a释放b锁以后才会执行write,然后c又等着b释放c锁。。。这样才是你想要的顺序~~!请lz再仔细琢磨琢磨!!
作者: 埋头苦编    时间: 2011-11-23 16:40
回复 26# 1514_louluo

你这加锁锁住了线程,合理马?
作者: 1514_louluo    时间: 2011-11-23 16:44
回复 33# 埋头苦编

为了实现顺序输出嘛~~我想楼主应该已经理解为什么会出现乱序的原因了吧~
作者: 埋头苦编    时间: 2011-11-23 16:45
回复 29# asuka2001
乱序,呵呵,什么情况都有可能,你再想想,如果因为系统调度机制,那么头个轮回就不应该每一次都是顺序的阿!
作者: 埋头苦编    时间: 2011-11-23 16:50
回复 34# 1514_louluo

你这样有几个问题:1循环里会锁上加锁,导致未定义行为,
                                    2怎么解锁
                                    3 这成了单线程了
作者: 1514_louluo    时间: 2011-11-23 16:58
回复 36# 埋头苦编


    楼主,这只是就事论事而已嘛~出发点是为了高明白为什么会出现输出乱序的问题嘛,现在应该都分析的差不多了,至于实现的方法不一定非要用这个嘛~你说是吗?兄弟
作者: 埋头苦编    时间: 2011-11-23 17:01
回复 23# digdeep126 [/
我这里是多个互斥量的时候是怎么加锁的没搞明白,知道的话能指教下马
作者: 埋头苦编    时间: 2011-11-23 17:07
回复 37# 1514_louluo
我们是为了学知识,不是为了让这个程序达到想要的结果,直接printf abcd也行阿,就是学多线程阿,哥们,你来个单线程,而且你说的还是让人没怎么明白,如果向你说的,那应该乱到死锁,而不是乱许
作者: digdeep126    时间: 2011-11-23 17:24
忽然想到:
哪个线程“上的锁”,必须要该线程来“解锁”。否则其它的线程就不能获得该锁。其它的线程并不能来解锁该线程上的锁。(有点饶口,
比如说线程A对lock1加锁,那么只有线程A能够对lock1解锁,线程B并不能对“线程A加在lock1上的锁”进行解锁!

所以说,这个程序的代码,完全是错误的!
作者: 埋头苦编    时间: 2011-11-23 17:36
回复 40# digdeep126

晕,,,,,哥们,你这是从哪里想来的,你看看标准吧
作者: digdeep126    时间: 2011-11-23 17:38
本帖最后由 digdeep126 于 2011-11-23 18:00 编辑

回复 41# 埋头苦编
还是你自己想想清楚吧!兄弟。你想想文件锁是如何使用的。
作者: digdeep126    时间: 2011-11-23 18:02
本帖最后由 digdeep126 于 2011-11-23 18:04 编辑
回复  digdeep126

晕,,,,,哥们,你这是从哪里想来的,你看看标准吧
埋头苦编 发表于 2011-11-23 17:36


你看的什么标准?对锁的处理,必须是所有的线程以一致的方式来处理:先“加锁”,获得锁之后进行一些处理,然后“释放锁”;然后其它的线程的”加锁“操作才能成功。
如果所有涉及到的线程没有以一致的方式来操作,那么“锁”就是没有意义的。
作者: asuka2001    时间: 2011-11-23 18:06
本帖最后由 asuka2001 于 2011-11-23 18:07 编辑

这个LZ是为了研究多线程的调度,倒没必要这么要求,只是觉得这种用法很谋杀脑细胞啊 :)

假设在LZ的系统上线程先创建,必定先执行,简单起见,只模拟3个线程A, B, C,以";"作为系统调度表示。

如果有黄色的sleep:

1. lock A, 输出 A, sleep; lock B, 输出 B, sleep; lock C, 输出C, sleep;
输出为ABC

2. unlock B, lock A阻塞; unlock C, lock B, 输出B, sleep; unlock A, lock C, 输出C, sleep;

3. lock A, 输出A, sleep;
输出为BCA

下次的话B先, 但是A还没给它解锁, 所以输出为CAB....


上次提出的那个模拟的确没考虑红色sleep的情况,这个还得再琢磨,让我更头痛了。

不过那是按照顺序调度想的,第2次B之所以先运行是因为它先进sleep,C其次,A是最后进sleep的;所以调度B先执行应该是符合顺序调度的。

这个LZ提供点程序运行结果吧,我已经解释不通了,还是看实际结果得了。
作者: digdeep126    时间: 2011-11-23 18:08
这个LZ是为了研究多线程的调度,倒没必要这么要求,只是觉得这种用法很谋杀脑细胞啊 :)
asuka2001 发表于 2011-11-23 18:06

关键是线程锁,根本不是这样使用的。
作者: 埋头苦编    时间: 2011-11-24 09:24
回复 43# digdeep126

你刚开始说的哪个线程加锁必须得由自己释放自己的锁肯定是错的,你后面说的一致的方式应该指锁的类型,这里都是一个类型的锁,没问题。我想了下过程大致如下:首先循环后六个线程同时都用自己的锁锁上打印这个动作,打印出abcdef,这个顺序只跟调度有关,打印后由于都锁着,所以阻塞。然后其中有个程序首先释放了一个锁,导致下一个线程阻塞停止,能后打印了,由此往后,最终有序输出。就向之前那个ubuntu的哥们给出的结果那样,首先无序,最终有序,但不一定是abcdef的序,由“首先”是谁来决定。至于为什么不是第二次后就有序了,我想,应该是之前红色标记的“同时”那里出的问题,实际上很有可能a释放b的锁的时候b还没加锁,导致未定义行为,导致乱许几次,但毕竟这种概率小,所以几次后最终有序。我试验了,加一个sleep,前几次乱许,后面最终有序,很多次试验都是如此。
作者: digdeep126    时间: 2011-11-24 09:41
回复 46# 埋头苦编
你这个例子完全是你自己杜撰出来的。对吧?你给我举个例子,来证明我说的是错误的!
你想一想下面这个场景:两个线程A, B,一把锁mutex,实现两个线程互斥地访问共享变量 n;
首先线程A加锁lock(mutex),然后操作n(假设这个操作需要较长的时间);这个时候线程B可以将线程A加在mutex上的锁释放掉???你再想想吧!兄弟。
作者: digdeep126    时间: 2011-11-24 09:53
回复 46# 埋头苦编
你去查一查pthread_cleanup_push / pthread_cleanup_pop 这两个个函数最常用的功能是什么?
最常用的场景:
线程A,对mutex加锁 lock(mutex),然后主线程pthread_cancel(A),将线程A取消掉了。注意:这个时候mutex被线程A锁住了!!!那么pthread_cleanup_push的最常用的功能就是:在线程A中注册一个清理函数clean,该函数的功能就是:在线程A被取消(被pthread_cancel(A))的时候,将自己已经加在了mutex上的锁释放掉。以免其它的线程永远不能对mutex加锁成功,而造成死锁!!!
按照你的说法:其它的线程可以对“线程A加在mutex上的锁”进行释放锁的操作,那么pthread为什么还要提供pthread_cleanup_push / pthread_cleaup_pop,并且它们的最典型的作用就是在被pthread_cancel的时候对“自己加的锁进行释放(清理)”。
作者: 埋头苦编    时间: 2011-11-24 09:54
回复 47# digdeep126
这里是6把锁,没个线程一把锁
作者: keytounix    时间: 2011-11-24 10:00
回复 1# 埋头苦编


    经过测试
俩种状态输出的都是无序的

本人的 是 4核CPU
ubuntu 10.04
gcc 4.4.3
作者: digdeep126    时间: 2011-11-24 10:07
本帖最后由 digdeep126 于 2011-11-24 10:09 编辑

回复 49# 埋头苦编
关键是锁,不是这样使用的!
不能这样使用:pthread_mutex_unlock(mut+next((int)ptr));
作者: digdeep126    时间: 2011-11-24 10:13
http://biancheng.dnbcw.info/linux/259179.html
3.解锁
   解锁必须满足
      (1)互斥锁处于加锁状态
      (2)只能由加锁的线程解锁(谁加谁解);
   解锁后等待队列中的第一个线程获得互斥锁.
       int pthread_mutex_unlock(pthread_mutex_t *mutex);

作者: djsxut    时间: 2011-11-25 09:50
这个输出跟sleep应该没什么关系。线程之间竞争CPU,没有特定的执行顺序。每个线程若同时释放了它自己的下一个锁,那么下一次的输出也将由竞争到cpu的那个线程执行。
作者: 埋头苦编    时间: 2011-11-25 10:22
回复 52# digdeep126

不能说谁加谁解,那篇文章也不严谨,一般用法是谁加谁解,但:
如果指定的互斥对象在以下情况下由调用的互斥对象拥有,那么 pthread_mutex_unlock 子例程将指定的互斥对象重新设置为已解锁状态:

    * 如果互斥对象已经被解锁,子例程返回错误。
    * 如果互斥对象为调用线程所拥有,子例程解锁互斥对象。
    * 如果互斥对象为另一个线程所拥有,子例程可能返回错误或者解锁互斥对象,这取决于互斥的类型。建议不要解锁互斥对象,因为互斥对象通常被同一个 pthread 锁定和解锁。
    来自http://publib.boulder.ibm.com/in ... progc%2Fmutexes.htm
作者: digdeep126    时间: 2011-11-25 13:37
本帖最后由 digdeep126 于 2011-11-25 13:53 编辑

回复 54# 埋头苦编
你的翻译,我实在看不明白,还是看原文吧:
http://publib.boulder.ibm.com/in ... progc%2Fmutexes.htm

The thread that locked a mutex is often called the owner of the mutex.

The pthread_mutex_unlock subroutine resets the specified mutex to the unlocked state if it is owned by the calling mutex under the following conditions:

If the mutex was already unlocked, the subroutine returns an error.
If the mutex was owned by the calling thread, the subroutine unlocks the mutex.
If the mutex was owned by another thread, the subroutine might return an error or unlock the mutex depending on the type of mutex. Unlocking the mutex is not recommended because mutexes are usually locked and unlocked by the same pthread.

既然:Unlocking the mutex is not recommended
那么推荐的方式就是: 对pthread_mutex_unlock函数的调用,不应该解锁“非自己线程拥有的锁”。
那我们在往前推一步:既然pthread_mutex_unlock函数不应该解锁“非自己线程拥有的锁”,那么“自己线程拥有的锁”就只能是拥有锁的线程自己调用pthread_mutex_unock来解锁了,也就是说“推荐的方式是:谁加锁,谁解锁。”
另:“The pthread_mutex_unlock subroutine resets the specified mutex to the unlocked state if it is owned by the calling mutex under the following conditions:”这一句英文中的:calling mutex似乎应该为 calling thread (仅仅是自己的猜测!)
作者: zzyuri    时间: 2011-11-25 14:41
前排前排
作者: 埋头苦编    时间: 2011-11-28 15:01
回复 55# digdeep126
那段话我当时看也有点别扭,有句话太长了(翻译的太英文化了),不过仔细看还是懂了,两端话其实意思是差不多的了,呵呵。现在可以确定的是:默认类型的锁可以由别的线程打开(否则我的程序是跑不动的),但真正写代码时是不这么用的。有劳你了,哈哈,同僚




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2