2011-04-24 87 views
3

以下是比较原子动作吗?也就是说,它可以减少到单个CPU指令吗?是一个比较原子操作吗?

char flag = 2; 

for(;;) 
{ 
    if (!flag) // <-- this 
     break; 

    // sleep 
} 

下面是我在做什么:

int main() 
{ 
    sf::Mutex Mutex; 
    char flag = 2; 

    coordinatorFunction(flag); 

    for(;;) 
    { 
     if (!flag) 
      break; 

     // sleep 
    } 
} 

void workerFunction(void* a) 
{ 
    char* p = static_cast<char*>(a); 

    // work 

    GlobalMutex.Lock(); 
    --*p; 
    GlobalMutex.Unlock(); 
} 

void coordinatorFunction(char& refFlag) 
{ 
    sf::Thread worker1(&workerFunction, &refFlag); 
    sf::Thread worker2(&workerFunction, &refFlag); 

    worker1.Launch(); 
    worker2.Launch(); 
} 
+0

我认为在执行比较之前,它总是必须读入寄存器,因为您已将指针传递给它。所以不,它不会是原子的。这就是我的想法,我不知道。我相信有办法做原子比较和写,虽然(不是在我猜的C++语言本身).. – falstro 2011-04-24 19:55:03

+2

它不遵循,如果东西可以减少到一个单一的CPU指令,那么它是原子。首先并非所有架构上的所有CPU指令都是原子的,其次,仅仅是因为某些东西可以简化为一个原子的指令并不意味着它会是原子的。 – 2011-04-24 20:00:37

回答

7

这是错误的做法。

您的主线程正在尽可能快地烧毁CPU周期,除了等待flag达到零之外什么都不做。每次尝试时都会失败,除了最后一次。不要这样做,而是使用线程对象最有可能使主线程自行挂起的“加入”工具,直到所有工作人员完成。

这样,不是巧合,你不会在乎测试是否是原子的,因为你根本不需要它。

+0

该死的。我应该想到这一点。谢谢。 – Truncheon 2011-04-24 20:02:05

+0

干得好,很好的答案。 +1 – Mehrdad 2011-04-24 20:03:37

+0

我相信原来的代码没有忙着等待:) – seand 2011-04-24 20:04:02

1

没有,C++不保证任何操作是原子。你问题中的代码很可能被编译成一个从内存加载到一个寄存器中的代码,它本身可能需要多个指令,然后进行测试。

+0

那么我需要更改代码以使其线程安全,还是已经? – Truncheon 2011-04-24 19:59:06

+0

@Truncheon术语“线程安全”是相当无意义的 - 线程安全性完全取决于上下文。但总的来说,从多个线程访问的任何资源(变量,无论)都必须受到某种锁定的保护。留下他们自己的设备,编译器永远不会生成“线程安全”的代码。 – 2011-04-24 20:04:19

2

C++操作没有保证是原子操作。

2

在C++中没有保证是原子的。

1

比较不是原子的,因为它使得几个机器语言指令来完成它(从内存加载到一个寄存器等)。由于内存模型的灵活性和缓存,执行测试的线程可能不会“看到”马上就有另一个线程的结果。

您可能会安全地做一个简单测试变量是否标记为易失性,但这将是平台特定的。正如其他人指出的那样,C++本身并不保证。

1

甲比较涉及读数二者片数据,以及执行实际的比较。 数据可以在读取和比较指令之间改变,所以它不是原子的。

但是,因为您正在比较相等性,所以_InterlockedCompareExchange固有内存(适用于x86中的lock cmp xchg指令)可能会执行您所需的操作,但它会涉及替换数据。

+0

但是,在许多平台上,如果比较结果为零,则有专门的操作码可以执行该操作。这意味着如果该值已经在寄存器中,它确实可以以原子方式完成。 – Kylotan 2011-04-24 19:58:30

+0

@Kylotan:我认为它不重要,你仍然需要将它读入一个寄存器,然后*然后*比较。问题不是比较,而是中间状态。但是如果你有指向内存的指针(而不是寄存器中的值),那么是的,这是正确的;我已经更新了我的答案。 – Mehrdad 2011-04-24 19:59:34

+0

*如果该值已经在寄存器中,但您将如何保证? – seand 2011-04-24 20:01:11

2

不可以。据我所知,C++并不保证什么是原子,什么不是 - 这是特定于平台的。即使您的平台确保可以自动进行比较,也不能保证您的C++编译器会选择该指令。

然而,在实践中,简单的值类型比如char,int,float等的比较可能是原子的。无论是在编译器级别还是在处理器级别,您仍然需要了解可能的指令重新排序。在这种情况下,这可能并不重要,但在一般情况下,它可以并且确实如此。您还需要了解,即使比较结果是比较后分支也不是原子的,因此如果您尝试使用标志来规范该访问,则2个线程都可以进入相同的代码块。

如果您需要适当的保证,Windows上有各种Interlocked functions,gcc上有各种atomic builtins

0

你应该问的问题是“是 - 原子”?这就是所有重要的事情。你想做些什么时,标志达到0

你不关心这样的场景:

1. Main thread reads flag, and it is 1. 
2. Worker changes flag with -- 
3. Main thread doesn't see that flag is actually 0. 

因为在1纳秒,主线程绕一圈并再次尝试。

你关心 - 不是原子和两个线程在同一时间更改它会跳过递减:

1. Thread A reads flag, flag is 2 
2. Thread B reads flag, flag is 2 
3. Thread A decrements its copy of flag, 2, and writes to flag, flag is 1 
4. Thread B decrements its copy of flag, also 2, and writes to flag, flag is 1. 

你失去了一个递减。你想使用__sync_fetch_and_sub(&标志,1),它会自动递减标志。

最后,旋转睡眠并不是最好的方式。您想要等待一个条件,请等待signal。让工作线程在他们意识到已将标志递减为0时提升条件或信号。