0

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

#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 操作更快。

4

2 回答 2

2

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

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

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

于 2012-07-07T18:48:44.603 回答
1

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

如果你有选择的话,这样做是一个非常糟糕的主意。可移植代码往往更可靠。两个大问题是:

  1. 新平台确实出现了。当发布新的 CPU、编译器或库时,脆弱的代码可能会中断。

  2. 很难想到这可能会出错的每一种方式,因为您并不真正知道自己在使用什么。互斥锁、原子操作等具有针对多个线程的精确定义的语义,因此您确切地知道您拥有什么保证——在任何平台、任何编译器、任何硬件上。

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

于 2012-07-07T18:11:00.530 回答