2012-01-06 49 views
1

这里的事情:有一个int数组float bucket[5]和2个线程,说线程1和线程。何时使用互斥

线程1负责收集bucket,为bucket中的每个元素分配一个随机数。当桶装满时,thread2将访问bucket并读取其元素。

这里是我做的工作:

float bucket[5]; 
pthread_mutex_t mu = PTHREAD_MUTEX_INITIALIZER; 
pthread_t thread1, thread2; 

void* thread_1_proc(void*); //thread1's startup routine, tank up the bucket 
void* thread_2_proc(void*); //thread2's startup routine, read the bucket 

int main() 
{ 
    pthread_create(&thread1, NULL, thread_1_proc, NULL); 
    pthread_create(&thread2, NULL, thread_2_proc, NULL); 
    pthread_join(thread1); 
    pthread_join(thread2); 
} 

下面是我对thread_x_proc实现:

void* thread_1_proc(void*) 
{ 
    while(1) { //make it work forever 
     pthread_mutex_lock(&mu); //lock the mutex, right? 

     cout << "tanking\n"; 
     for(int i=0; i<5; i++) 
      bucket[i] = rand(); //actually, rand() returns int, doesn't matter 

     pthread_mutex_unlock(&mu); //bucket tanked, unlock the mutex, right? 

     //sleep(1); /* this line is commented */ 
    } 
} 

void* thread_2_proc(void*) 
{ 
    while(1) { 
     pthread_mutex_lock(&mu); 

     cout << "reading\n"; 
     for(int i=0; i<5; i++) 
      cout << bucket[i] << " "; //read each element in the bucket 

     pthread_mutex_unlock(&mu); //reading done, unlock the mutex, right? 

     //sleep(1); /* this line is commented */ 
    } 
} 

问题

是我实现吗?因为输出不是我所期望的。

... 
reading 
5.09434e+08 6.58441e+08 1.2288e+08 8.16198e+07 4.66482e+07 7.08736e+08 1.33455e+09 
reading 
5.09434e+08 6.58441e+08 1.2288e+08 8.16198e+07 4.66482e+07 7.08736e+08 1.33455e+09 
reading 
5.09434e+08 6.58441e+08 1.2288e+08 8.16198e+07 4.66482e+07 7.08736e+08 1.33455e+09 
reading 
tanking 
tanking 
tanking 
tanking 
... 

,如果我去掉每个thread_x_proc功能sleep(1);,输出是正确的,tankingreading跟随对方,就像这样:

... 
tanking 
reading 
1.80429e+09 8.46931e+08 1.68169e+09 1.71464e+09 1.95775e+09 4.24238e+08 7.19885e+08 
tanking 
reading 
1.64976e+09 5.96517e+08 1.18964e+09 1.0252e+09 1.35049e+09 7.83369e+08 1.10252e+09 
tanking 
reading 
2.0449e+09 1.96751e+09 1.36518e+09 1.54038e+09 3.04089e+08 1.30346e+09 3.50052e+07 
... 

为什么?我应该在使用mutex时使用sleep()吗?

回答

4

你的代码是在技术上是正确的,但它没有多大意义,它不会做你的假设。

你的代码确实是是,它自动更新一段数据,并以原子方式从该段读取数据。但是,您不知道发生这种情况的顺序,也不知道在读取数据之前数据被写入的频率如何(或者根本就没有!)。

你可能想要想要每次只在一个线程中产生一个数字序列,并且每次在另一个线程中读取一个新序列。为此,您可以使用必须使用额外的信号量或更好的单生产者单消费者队列。

一般来说,“我应该何时使用互斥锁”的答案是“永远不会,如果可以帮助的话”。线程应该发送消息,而不是共享状态。这使得大部分时间内互斥量都是不必要的,并且提供了并行性(这是首先使用线程的主要动机)。

互斥锁让你的线程运行锁步,所以你也可以在单线程中运行。

+0

这在技术上并不正确,因为它的行为是未定义的(它可能连续两次,例如它“不应该”)。 – Valmond 2012-01-06 12:37:07

+0

你是对的,谢谢。 – Alcott 2012-01-06 12:39:21

+0

我会回答“何时使用互斥体?”是“只有当你需要保护优先倒置时”。我认为在所有其他情况下,有更好的方法来评估问题。 – elmo 2012-01-06 12:45:26

1

如果有应该按顺序执行两个功能,即F1要完成F2开始前,那么你不应该使用两个线程。 F1返回后,在与F1相同的线程上运行F2。

不使用线程,你将不再需要互斥无论是。

+0

很好,但我想F1工作永远,此外,F1和F2数据共享,对不对? – Alcott 2012-01-06 12:29:49

+0

“永远”和“共享数据”都没有什么区别:'for(;;){f1(data); F2(数据); }'。或者以OO风格:'for(;;){object.f1(); object.f2(); }'。仍然没有互斥。 – MSalters 2012-01-06 12:36:37

+0

是的,没错。 – Alcott 2012-01-06 12:42:46

0

这是不是真的在这里的问题。

睡眠只允许'其他'线程访问互斥锁(偶然的机会,它正在等待锁可能会有互斥锁),所以你不可能确定第一个线程不会尽管重新锁定了互斥锁,并让其他线程访问它。

互斥是保护数据,以便两个线程并不: 一)写的同时 B)一个是当另一个正在读

写这不是让线程按照一定的顺序工作(如果你想该功能,抛弃线程方法或使用标志来告诉“坦克”已满)。

+0

没有使用线程的任何其他方式? – Alcott 2012-01-06 12:32:00

+1

没有线程你只是:while(true){Fill_Bucket(); Empty_Bucket();} – Valmond 2012-01-06 12:35:31

2

没有隐含顺序线程将获得运行。这意味着你不应该期望任何订单。更重要的是,可以让线程反复运行而不让另一个运行。这是具体实现,应该假定是随机的。

您呈现瀑布更愿意为semaphor它与每个元素添加“贴”的情况。

但是,如果它一直是这样的:

  • 写5个元素
  • 读5元

你应该有两个互斥:

  • 一个块制作直到消费者完成
  • 一个b锁定消费者直到生产者完成

因此,代码看起来应该是这样的:

Producer: 
    while(true){ 
     lock(&write_mutex) 
     [insert data] 
     unlock(&read_mutex) 
    } 

Consumer: 
    while(true){ 
     lock(&read_mutex) 
     [insert data] 
     unlock(&write_mutex) 
    } 

最初write_mutex应该被解锁和read_mutex锁定。

正如我所说你的代码似乎是信号或情况变量的更好的情况。 互斥体并不适用于这种情况(这并不意味着你不能使用它,它只是意味着有更多方便的工具来解决这个问题)。

2

你没有权利假设只是因为你想让你的线程按照特定的顺序运行,这个实现将会找出你想要的并且按照这个顺序实际运行它们。

为什么不应该在thread1之前运行thread2?为什么每个线程都不应该在其他线程有机会运行到获取互斥锁的线路之前多次完成其循环?

如果您希望执行以可预测的方式在两个线程之间切换,那么您需要使用信号量,条件变量或其他机制来实现两个线程之间的消息传递。 sleep似乎会导致您在这个场合下的顺序,但即使您的睡眠不足以保证它们会交替进行。而且我不知道为什么sleep对于哪个线程首先运行会产生影响 - 是否在几次运行中保持一致?

+0

经过几次测试,这是一致的,但你的解释显然是正确的。 Upvoted。 – Alcott 2012-01-06 12:46:10

+0

@Alcott:也许你最初的误解是认为'pthread_mutex_unlock'的意思是“给另一个线程赋予互斥体”。它不会,它只是释放互斥体,以便此线程或任何其他人可以在将来获得它。在另一个线程得到运行的机会之前,它将会是这个,所以'sleep'的区别就是让别的运行。随着睡眠,线程会交替地得到互斥,只要它们交替唤醒,这很可能但不能保证,因为“sleep(1)”每次不一定是1秒。 – 2012-01-06 12:51:55

+0

是的,谢谢你,:) – Alcott 2012-01-07 02:01:07

0

现在,从其他答案中应该清楚,原始代码中的错误是什么。所以,我们试着改进它:

/* A flag that indicates whose turn it is. */ 
char tanked = 0; 

void* thread_1_proc(void*) 
{ 
    while(1) { //make it work forever 
     pthread_mutex_lock(&mu); //lock the mutex 
     if(!tanked) { // is it my turn? 
      cout << "tanking\n"; 
      for(int i=0; i<5; i++) 
       bucket[i] = rand(); //actually, rand() returns int, doesn't matter 
      tanked = 1; 
     } 
     pthread_mutex_unlock(&mu); // unlock the mutex 
    } 
} 

void* thread_2_proc(void*) 
{ 
    while(1) { 
     pthread_mutex_lock(&mu); 
     if(tanked) { // is it my turn? 
      cout << "reading\n"; 
      for(int i=0; i<5; i++) 
       cout << bucket[i] << " "; //read each element in the bucket 
      tanked = 0; 
     } 
     pthread_mutex_unlock(&mu); // unlock the mutex 
    } 
} 

上面的代码应该按预期工作。然而,正如其他人所指出的,其结果必然是与其他两种选项之一更好地完成:

  • 按顺序。由于生产者和消费者必须交替,所以你不需要两个线程。一个坦克然后读取的循环就足够了。该解决方案还可以避免上述代码中发生的繁忙等待。
  • 使用信号量。如果生产者能够连续运行几次,在桶中累积元素(不过在原始代码中不是这种情况),这将是解决方案。 http://en.wikipedia.org/wiki/Producer-consumer_problem#Using_semaphores
+0

检查锁外的标志有什么意义?这在形式上是不正确的(尽管它可以在大多数实现上工作,如果不是全部的话),并且假设它没有坏处,它实际上实现的只是让你忙碌得更快,因为你不抓取和释放锁。 – 2012-01-06 13:16:21

+0

@SteveJessop这不正式不正确,正是因为忙于等待。但我同意双重锁定并没有改善这里的代码(因为忙碌的等待,因为只有一个生产者和一个消费者),所以我已经删除它。 – igorrs 2012-01-06 13:35:44

+0

它在形式上是不正确的,因为不能保证在一个线程中''tanked'的写入*永远*被执行的线程看到'while(1)if(tanked){anything; }'。任何东西都不会被执行,因此不会引入任何类型的内存障碍。实际上,大多数体系结构都会传播写入信息(通过缓存一致性,或者最坏的情况是在线程被取消预定时),但AFAIK在C++ 11或Posix中没有保证它会这样做,或者优化器不会做任何事情与硬件无关。 – 2012-01-06 13:38:10