【问题标题】:Intel Inspector reports a data race in my spinlock implementationIntel Inspector 在我的自旋锁实现中报告了数据竞争
【发布时间】:2010-12-01 00:59:47
【问题描述】:

我使用 Windows 中的 Interlocked 函数制作了一个非常简单的自旋锁,并在双核 CPU(两个线程递增一个变量)上对其进行了测试;

该程序似乎工作正常(每次都给出相同的结果,当不使用同步时情况并非如此),但 Intel Parallel Inspector 表示在 value += j(见下面的代码)。使用关键部分而不是我的 SpinLock 时,警告会消失。

我的 SpinLock 实现是否正确?这真的很奇怪,因为所有使用的操作都是原子的,并且有适当的内存屏障,它不应该导致竞争条件。

class SpinLock
{
   int *lockValue;
   SpinLock(int *value) : lockValue(value) { }

   void Lock() {
      while(InterlockedCompareExchange((volatile LONG*)lockValue, 1, 0) != 0) {
          WaitABit();
      }
   }

   void Unlock() { InterlockedExchange((volatile LONG*)lockValue, 0); }
};

测试程序:

static const int THREADS = 2;
HANDLE completedEvents[THREADS];
int value = 0;
int lock = 0; // Global.

DWORD WINAPI TestThread(void *param) {
    HANDLE completed = (HANDLE)param;
    SpinLock testLock(&lock);

    for(int i = 0;i < 1000*20; i++) {
        for(int j = 0;j < 10*10; j++) {
            // Add something to the variable.
            testLock.Lock();
            value += j;
            testLock.Unlock();
        }
    }
    SetEvent(completed);
}

int main() {
   for(int i = 0; i < THREADS; i++) {
        completedEvents[i] = CreateEvent(NULL, true, false, NULL);
   }
   for(int i = 0; i < THREADS; i++) {
        DWORD id;
        CreateThread(NULL, 0, TestThread, completedEvents[i], 0, &id);
   }

   WaitForMultipleObjects(THREADS, completedEvents, true, INFINITE);
   cout<<value;
}

【问题讨论】:

    标签: c++ winapi synchronization spinlock intel-inspector


    【解决方案1】:

    Parallel Inspector 的 data race 文档建议使用临界区或互斥体来修复 Windows 上的竞争。其中没有任何内容表明 Parallel Inspector 知道如何识别您可能发明的任何其他锁定机制。

    用于分析新颖锁定机制的工具往往是静态工具,它会查看代码中每条可能的路径,Parallel Inspector 的文档暗示它只执行一次代码。

    如果您想尝试新的锁定机制,我在学术文献中看到的最常用的工具是Spin model checker。还有ESP,它可能会减少状态空间,但我不知道它是否适用于并发问题,还有mobility workbench,如果你可以用pi-calculus来解决你的问题,它会给出分析。英特尔 Parallel Inspector 看起来并不像这些工具那么复杂,而是旨在使用启发式方法检查常见问题。

    【讨论】:

      【解决方案2】:

      对于与我有类似情况的其他穷人:英特尔确实提供了一组包含和库来完成这类事情。检查 Inspector 安装目录(您将在安装目录中看到 \include、\lib32 和 \lib64)以获取这些材料。关于如何使用它们的文档(截至 2018 年 6 月,尽管英特尔并不关心保持链接的一致性):

      https://software.intel.com/en-us/inspector-user-guide-windows-apis-for-custom-synchronization

      有3个功能:

      void __itt_sync_acquired(void *addr)
      void __itt_sync_releasing(void *addr)
      void __itt_sync_destroy(void *addr)
      

      【讨论】:

      • 我用我自己的自旋锁互斥体对此进行了测试,它按预期工作(数据竞争从 Parallel Inspector 报告中消失)
      【解决方案3】:

      我很确定应该按如下方式实现:

      class SpinLock
      {
         long lockValue;
         SpinLock(long value) : lockValue(value) { }
      
         void Lock() {
            while(InterlockedCompareExchange(&lockValue, 1, 0) != 0) {
                WaitABit();
            }
         }
      
         void Unlock() { InterlockedExchange(&lockValue, 0); }
      };
      

      【讨论】:

      • 你提出的和我做的差不多,只是锁旋转周围的 int 包含在类中......这将是一个缺点,因为 RAII 不能再使用(该类还有一个自动释放锁的析构函数)。想了想,我试过你说的,还是一样:程序运行正常,但是 Intel Parallel Inspector 说存在竞态条件。也许 Inspector 有错误?但可能不是:(
      • 你也应该使用 long 开始而不是进行显式转换,并且在构造函数中不带参数,只是解锁它。如果创建它的东西需要从一开始就锁定它,他们可以在创建它之后但在共享它之前锁定它。 @Igratian - 在这种情况下你不需要 RAII,因为析构函数没有什么要清理的(它只是很长)。
      • 已编辑。不会费心添加解构器......因为这个问题并不保证完全纠正他的代码。我只是想解决他的问题。
      猜你喜欢
      • 1970-01-01
      • 2019-11-30
      • 1970-01-01
      • 2012-10-27
      • 2013-11-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-07
      相关资源
      最近更新 更多