何时使用互斥

when to use mutex

本文关键字:何时使      更新时间:2023-10-16

事情是这样的:有一个浮点数组float bucket[5]和2个线程,比如thread1和thread2。

Thread1负责储存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 << "tankingn";
        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 << "readingn";
        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()吗?

您的代码在技术上是正确的,但它没有多大意义,也没有按照您的假设执行。

代码所做的是,它原子地更新一段数据,并从该段中原子地读取。然而,您不知道这种情况发生的顺序,也不知道在读取数据之前写入数据的频率(或者根本不知道!)。

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

一般来说,"我什么时候应该使用互斥锁"的答案是"如果你能帮助的话,永远不要"。线程应该发送消息,而不是共享状态。这使得互斥锁在大多数情况下都是不必要的,并提供了并行性(这是最初使用线程的主要动机)。

互斥锁使线程步调一致地运行,所以您也可以只在一个线程中运行。

线程运行没有隐含的顺序。这意味着您不应期待任何订单。更重要的是,可以让线程一遍又一遍地运行,而不让另一个线程运行。这是特定于实现的,应该假定为随机的。

你展示的案例更倾向于一个信号量,它是在添加了每个元素的情况下"发布"的。

然而,如果它总是像:

  • 写入5个元素
  • 读取5个元素

您应该有两个互斥对象:

  • 阻止生产者直到消费者完成
  • 阻止消费者直到生产者完成

所以代码应该是这样的:

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应锁定。

正如我所说,您的代码似乎是信号量或条件变量的更好例子。Mutex不适用于此类情况(这并不意味着你不能使用它们,它只是意味着有更方便的工具来解决这个问题)。

您无权因为希望线程按特定顺序运行而认为,实现会弄清楚您想要什么,并实际按该顺序运行它们。

为什么thread2不应该在thread1之前运行?为什么每个线程不应该在另一个线程有机会运行到获取互斥对象的行之前完成多次循环呢?

如果您希望执行以可预测的方式在两个线程之间切换,那么您需要使用信号量、条件变量或其他机制在这两个线程间传递消息。在这种情况下,sleep似乎会按照你想要的顺序出现,但即使有睡眠,你也没有做足够的工作来保证它们会交替出现。我不知道为什么sleep会对哪个线程首先运行产生影响——这在几次运行中是一致的吗?

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

如果没有线程,您也不需要互斥锁。

这里并不是真正的问题。

睡眠只允许"另一个"线程访问互斥锁(碰巧,它正在等待锁,所以很可能它会有互斥锁),但你无法确保第一个线程不会重新锁定互斥锁,并让另一个线程访问它。

Mutex用于保护数据,因此两个线程不会:a) 同时写入b) 一个在写,另一个在读

它不是为了让线程按特定的顺序工作(例如,如果你想要这种功能,可以放弃线程方法或使用标志来指示"油箱"已满)。

到目前为止,从其他答案来看,应该已经清楚了原始代码中的错误是什么。所以,让我们试着改进一下:

/* 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 << "tankingn";
            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 << "readingn";
            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
    }
}

上面的代码应该按预期工作。然而,正如其他人所指出的,使用以下两种选择之一会更好地实现结果:

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