理论上,释放锁时,需要考虑执行乱序的问题,需要有Memory Barrier(即执行mfence)避免临界区内的改动在临界区外生效。不过,我在本机上测试,发现无论如何不会出现乱序的情况,这让我以为处理器有机制避免这种乱序问题。考虑到引入mfence会带来明显的性能下降,我就去掉了这个语句。
结果,QA在服务器上测试时,就发现了异常问题,而且看上去和内存管理有关。在排除了其他bug以后(事实上,这个版本有很多其他各色问题),仍然没有彻底解决。我考虑可能和未使用mfence有关,便重新加上,果然恢复正常了。
测试表明,若无mfence,桌面的处理器和AMD的服务器处理器似乎都不会有乱序执行导致错误,而XEON则很容易出现问题。
一段用于测试乱序执行的代码:
makefile
a.out : main.cpp
g++ -O2 -g -lpthread -o $@ lt;
main.cpp
#include
#include
#include
#include
#include
volatile unsigned int a1 = 0;
volatile unsigned int b1 = 0;
int lock = 0;
int failed_count = 0;
#define spin_lock(p) while (__sync_lock_test_and_set(p, 1)) __asm("pause")
#if 0
// No mfence
#define spin_unlock(p) *p = 0
#else
// Use mfence
#define spin_unlock(p) __sync_synchronize(); *p = 0
#endif
static void* f(void* arg)
{
unsigned int c1, d1;
for(;;)
{
spin_lock(&lock);
c1 = a1;
d1 = b1;
a1++;
b1++;
spin_unlock(&lock);
if (c1 != d1)
{
failed_count++;
spin_lock(&lock);
a1 = b1;
spin_unlock(&lock);
}
}
}
static void* g(void* arg)
{
struct timeval now, last;
int us;
gettimeofday(&last, NULL);
for(;;)
{
spin_lock(&lock);
{
spin_lock(&lock);
c1 = a1;
d1 = b1;
a1++;
b1++;
spin_unlock(&lock);
if (c1 != d1)
{
failed_count++;
spin_lock(&lock);
a1 = b1;
spin_unlock(&lock);
}
}
}
static void* g(void* arg)
{
struct timeval now, last;
int us;
gettimeofday(&last, NULL);
for(;;)
{
spin_lock(&lock);
b1++;
a1++;
if (b1 >= 0x1000000)
break;
spin_unlock(&lock);
}
gettimeofday(&now, NULL);
us = (now.tv_sec - last.tv_sec) * 1000000;
us += (now.tv_usec - last.tv_usec);
printf("us = %d.\n", us);
printf("failed_count = %d.\n", failed_count);
exit(0);
}
int main(int argc, const char* argv[])
{
pthread_t pid1,pid2;
a1++;
if (b1 >= 0x1000000)
break;
spin_unlock(&lock);
}
gettimeofday(&now, NULL);
us = (now.tv_sec - last.tv_sec) * 1000000;
us += (now.tv_usec - last.tv_usec);
printf("us = %d.\n", us);
printf("failed_count = %d.\n", failed_count);
exit(0);
}
int main(int argc, const char* argv[])
{
pthread_t pid1,pid2;
if (pthread_create(&pid1, 0, f, 0))
{
printf("Create thread1 error\n");
exit(-1);
}
if (pthread_create(&pid2, 0, g, 0))
{
printf("Create thread2 error\n");
exit(-1);
}
while (1)
sleep(1);
return 0;
}
通过调整#if 0/1的开关,可以选择使用/不使用mfence。
注意:出现failed count说明受到乱序执行的影响;但是若没有出现failed count并不意味不存在问题,可能只是没有引发问题;另外,编译时必须开启优化,否则生成臃肿的代码很可能会掩盖问题。
{
printf("Create thread1 error\n");
exit(-1);
}
if (pthread_create(&pid2, 0, g, 0))
{
printf("Create thread2 error\n");
exit(-1);
}
while (1)
sleep(1);
return 0;
}
通过调整#if 0/1的开关,可以选择使用/不使用mfence。
注意:出现failed count说明受到乱序执行的影响;但是若没有出现failed count并不意味不存在问题,可能只是没有引发问题;另外,编译时必须开启优化,否则生成臃肿的代码很可能会掩盖问题。
没有评论:
发表评论