我想知道为什么以天真的方式实现这种队列是不正确的:
#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操作更快。
答案 0 :(得分:2)
使用POSIX线程,如果使用互斥锁,锁等,则线程之间只有数据一致性。并且连贯性与编译器没有明确定义的接口。 (而volatile
肯定不是它)不要这样做,所有事情都可能发生,因为优化的变量更新(这里volatile
可以帮助)或部分读或写。
C11,新的C标准有一个线程模型,包括数据一致性模型,线程创建函数和原子操作。似乎没有编译器完全实现这一点,但是在POSIX线程之上的gcc或clang实现了您需要的功能。如果您想尝试这一点并将来证明,P99为这些平台实现了包装,允许您使用新的C11接口。
C11的_Atomic
类型和操作是实现在线程之间运行的无锁队列的正确工具。
答案 1 :(得分:1)
在C中,volatile
关键字没有在多个线程中并发访问变量时应用的定义语义(并且pthreads不添加任何线程)。因此,了解它是否安全的唯一方法是查看volatile
对特定平台和编译器的影响,找出在这些特定硬件平台上出错的每种可能方式,并排除它们。 / p>
如果你有选择的话,这是一个非常糟糕的主意。便携式代码往往更加可靠。两大问题是:
新平台确实问世。当新的CPU,编译器或库被释放时,脆弱的代码可能会中断。
很难想到这可能出错的每一种方式,因为你真的不知道你在做什么。互斥体,原子操作等具有针对多个线程的精确定义的语义,因此您知道完全保证您拥有什么 - 在任何平台上,任何编译器,任何硬件。
顺便说一句,你的读者代码很糟糕。例如,在超线程CPU上,像这样的紧密旋转会使另一个虚拟核心饿死。更糟糕的是,你可能会以FSB速度旋转,使其他物理核心匮乏。当你退出旋转循环时 - 时间性能最关键 - 你基本上强迫错误预测的分支! (具体的效果取决于CPU的具体情况,这是使用这种代码的另一个原因。你需要至少rep nop。)