单一生产者单一消费者锁定免费队列c

时间:2012-07-07 17:57:50

标签: c multithreading pthreads queue lock-free

我想知道为什么以天真的方式实现这种队列是不正确的:

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>


void *print_message_function( void *ptr );

void *reader( void *ptr );
void *writer( void *ptr );



int queue[500000];


int main(int argc, char **argv) 
{
   pthread_t thread1, thread2;

   char *message1 = "Thread 1";
   char *message2 = "Thread 2";
   int  iret1, iret2; 


   iret1 = pthread_create( &thread1, NULL, writer, (void*) message1);
   iret2 = pthread_create( &thread2, NULL, reader, (void*) message2);

   usleep(2000);

   void pthread_exit(void *iret1 );
   void pthread_exit(void *iret2 );

   exit(0);

}



void *writer( void *ptr )
{
  // make local copy of queue head
  register int *pos = queue; 

  //   struct thread_param *tp = arg;
  int counter = 0;

  while(1)
  {
    //Write to head of queue
    *pos = 5;

    pos++;

    print_message_function(  ptr);
  }
}


void *reader( void *ptr )
{
  int counter = 0;

  // make local copy of queue head
  register int *pos = queue; 

  while(1)
  {

    // Read from tail of queue - loop when nothing
    if ( *pos == 5 ) 
    { 
      print_message_function( ptr ); 
      pos++; 
    }
  }
}



void *print_message_function( void *ptr )
{
      char *message;
      message = (char *) ptr;
      printf("%s \n", message);
}

我打算缓存对齐队列。

我不相信内存重新排序是一个问题,因为队列头的副本是在启动时创建的,并且只有一个读写器。

我想要这个的原因是它应该比互斥锁或CAS操作更快。

2 个答案:

答案 0 :(得分:2)

使用POSIX线程,如果使用互斥锁,锁等,则线程之间只有数据一致性。并且连贯性与编译器没有明确定义的接口。 (而volatile肯定不是它)不要这样做,所有事情都可能发生,因为优化的变量更新(这里volatile可以帮助)或部分读或写。

C11,新的C标准有一个线程模型,包括数据一致性模型,线程创建函数和原子操作。似乎没有编译器完全实现这一点,但是在POSIX线程之上的gcc或clang实现了您需要的功能。如果您想尝试这一点并将来证明,P99为这些平台实现了包装,允许您使用新的C11接口。

C11的_Atomic类型和操作是实现在线程之间运行的无锁队列的正确工具。

答案 1 :(得分:1)

在C中,volatile关键字没有在多个线程中并发访问变量时应用的定义语义(并且pthreads不添加任何线程)。因此,了解它是否安全的唯一方法是查看volatile对特定平台和编译器的影响,找出在这些特定硬件平台上出错的每种可能方式,并排除它们。 / p>

如果你有选择的话,这是一个非常糟糕的主意。便携式代码往往更加可靠。两大问题是:

  1. 新平台确实问世。当新的CPU,编译器或库被释放时,脆弱的代码可能会中断。

  2. 很难想到这可能出错的每一种方式,因为你真的不知道你在做什么。互斥体,原子操作等具有针对多个线程的精确定义的语义,因此您知道完全保证您拥有什么 - 在任何平台上,任何编译器,任何硬件。

  3. 顺便说一句,你的读者代码很糟糕。例如,在超线程CPU上,像这样的紧密旋转会使另一个虚拟核心饿死。更糟糕的是,你可能会以FSB速度旋转,使其他物理核心匮乏。当你退出旋转循环时 - 时间性能最关键 - 你基本上强迫错误预测的分支! (具体的效果取决于CPU的具体情况,这是使用这种代码的另一个原因。你需要至少rep nop。)