c-生产者-消费者程序:我应该在关键部分内外更改信号量的值吗



我正在编写一个简单的生产者-消费者程序,该程序应该基于固定大小的队列。

我正在使用列表来实现它。

我的两个问题是:

  1. 在下面的代码中,我将检查列表是否为空,以了解消费者是否应该并且有东西要消费

我的说明如下:使用链表+只使用一个互斥锁,消费者应该执行一段时间的(不为空..)

所以我写的消费者函数是:(同时我们被要求只写伪代码)

/*  start function: handler function of consumer:   */
/*  while program is running */

/*  while linked list is not empty: */

/*  lock mutex - critical section entrance  */
/*  pop the last element and save its data in a variable */
/*  unlock mutex - critical section exit    */

/* print the popped data */              
/*  end of while is not empty   */

/*  end of while    */
/*  end function    */

有人告诉我:

  • "你不应该把时间花在关键部分。你不需要在那里打印东西。我们在关键部分的目标是尽快退出。做所有与数据相关的事情结构,在这种情况下,列表,在关键部分内在这种情况下,消费打印出删除的数据">

他告诉我,我必须检查关键部分内的列表是否为空,因为这是一个与数据结构直接相关的操作。

我的问题是:为什么?为什么我不能检查列表是否为空,然后才能进入关键部分?

这是我在他的评论之后的固定代码:

/*  start function: handler function of consumer:   */
/*  while program is running */
/*  lock mutex - critical section entrance  */

/*  while linked list is not empty: */
/*  set global flag is_empty as 1   */
/*  break   */
/*  end of while is not empty   */

/*  if is_empty: */
/*  pop from the back of the list and save the popped data  */
/*  end if      */

/*  unlock mutex - critical section exit    */

/*  consume - print popped data */
/*  set is_empty as 0   */

/*    end of while    */
/*  end function    */

我的第二个问题是:

如果我们使用信号量,我们应该在关键部分内外更改它们的值吗?为什么?

例如,在以下代码中:

/*  start function: handler function of a producer: */
/*  while program is running:   */
/*  decrement the value of the semaphore num_of_empty_bins  */
/*  lock mutex_producer - critical section entrance */
/*  enqueue data    */
/*  increment the value of the semaphore num_of_full_bins   */
/*  unlock mutex_producer - critical section exit   */

/*    end of while    */
/*  end function    */

正如你所看到的,有增量&临界段内的递减函数。最初,我是在互斥锁之前和之后的部分之外编写的。但我的朋友告诉我,他认为应该在上锁/解锁之前。

我们在任何地方都找不到答案,所以我们不知道是否有区别或理由这样做。

这些递增/递减操作是否应该是原子操作,即使它在关键部分内?或者它们应该是原子的,只有在部分之外的情况下?

谢谢。

看看:线程和数据竞赛(cppreference.com)

当一个表达式的求值写入一个内存位置,而另一个求值读取或修改同一内存位置时,称表达式冲突。一个有两个相互冲突的评估的程序有一个数据竞赛,除非

  • 两个冲突的求值都是原子操作
  • 一个冲突的求值发生在另一个求值之前(请参见memoryorder)

如果发生数据争用,则程序的行为未定义。

您的第一个问题:"为什么?为什么我不能检查列表是否为空,然后才能进入关键部分">

另一个线程可以在您进入关键部分之前修改列表的状态。你相信这个列表是空的,但事实并非如此(反之亦然)。因此,在读取或写入共享变量之前,每个线程都必须进入关键部分。

第二个问题:

这里也是如此。只要不使用原子数据类型,就必须输入关键部分。

我的问题是:为什么?为什么我不能检查列表是否为空,然后才能进入关键部分?

因为对共享变量(如列表及其成员)的非原子访问必须仅根据足以防止多个线程同时访问的同步措施执行。这是一个严格意义上比限制访问关键部分更强的约束。例如,必须防止一个线程从一个关键部分访问共享对象,而另一个线程则从不同的关键部分访问同一共享对象。

如果这个同步要求没有得到满足,那么程序行为是未定义的。在实践中,这种不确定性可以表现为线程看不到彼此的更新、数据损坏以及其他非常真实和有影响力的问题。这些往往也很难调试,因此从一开始就要仔细注意正确的同步。

如果我们使用信号量,我们应该在关键部分内外更改它们的值吗?为什么?

信号量是同步对象,可以而且经常充当互斥对象。因此,它们与您描述的特定任务无关,在该任务中,我认为除了互斥之外还使用信号量至少违反了";只有一个互斥";限制如果您不希望它具有同步属性,那么纯整数也可以起到同样的作用。

话虽如此,如果您确实在程序中添加了一个信号量,那么主要关注的是使用互斥和信号量的多个线程是否构成死锁风险。您应该能够确保它们不会这样做,因为遵循的一般原则是,每个打算同时持有给定锁对的线程都必须以相同的相对顺序获取它们。锁定互斥锁和减少信号量一次都算作获取锁。你应该可以选择任何一个订单,只要你是一致的。特别是,操作信号量本身并不需要保护不同的同步对象。

即使在关键部分内,这些递增/递减操作是否也必须是原子操作?或者它们应该是原子的,只有在部分之外的情况下?

如上所述,如果共享变量(处处)正确同步,则对共享变量的增量和减量操作不需要是原子操作。在某些情况下,可以在没有其他同步的情况下替换原子修改,但这需要根据更大的上下文来考虑。尽管在任何关键部分之外的原子修改本身可能是可以的,但它可能会产生或允许不需要的程序行为。

最新更新