Volatile和CreateThread [英] Volatile and CreateThread

查看:174
本文介绍了Volatile和CreateThread的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刚刚提出了一个涉及volatile的问题: volatile array c ++



但是我的问题引起了对 volatile 的讨论。



使用 CreateThread(),您不必担心 volatiles
另一方面,当使用由 CreateThread()创建的两个线程时,Microsoft另外提供了一个 volatile



我在visual c ++ express 2010中创建了以下示例,如果您将 done 标记为 volatile 或不

  #includetargetver.h
#include< Windows.h>
#include< stdio.h>
#include< iostream>
#include< tchar.h>

using namespace std;

bool done = false;
DWORD WINAPI thread1(LPVOID args)
{
while(!done)
{

}
cout< Thread 1 done!\\\
;
return 0;
}
DWORD WINAPI thread2(LPVOID args)
{
Sleep(1000);
done = 1;
cout<< Thread 2 done!\\\
;
return 0;
}

int _tmain(int argc,_TCHAR * argv [])
{
DWORD thread1Id;
HANDLE hThread1;
DWORD thread2Id;
HANDLE hThread2;

hThread1 = CreateThread(NULL,0,thread1,NULL,0,& thread1Id);
hThread2 = CreateThread(NULL,0,thread2,NULL,0,& thread2Id);
Sleep(4000);
关闭Handle(hThread1);
CloseHandle(hThread2);

return 0;
}

你总是可以确定线程1将停止,如果 done 不是 volatile

解决方案

volatile 会执行以下操作:




  • 阻止编译器优化任何访问。

  • 每次读/写将导致读/写指令。
  • 阻止编译器重新排序访问


什么 volatile 不符合:




  • 使访问原子。

  • 防止编译器重新排序非易失性访问。




在跨平台C ++中不应该依赖的一些非便携式行为:




  • VC ++扩展了 volatile ,以防止与其他指令重新排序。其他编译器不会,因为它会对优化产生负面影响。

  • x86使指针大小和变量更小的对齐读取/写入原子,并立即对其他线程可见。 大多数时候,人们需要的是栅栏(也称为栅栏)障碍)和原子指令,如果你有一个C ++ 11编译器,或者通过编译器和架构相关的函数,则可以使用。



    ,在使用点,所有先前的读/写将完成。在C ++ 11中,使用 std :: memory_order 枚举在各个点控制栅栏。在VC ++中,你可以使用 _ReadBarrier() _WriteBarrier() _ReadWriteBarrier / code>来做这个。我不确定其他编译器。



    在某些架构(如x86)上,栅栏只是一种防止编译器重新排序指令的方法。在其他人,他们可能实际发出指令,以防止CPU本身重新排序的东西。



    这里是一个不当使用的例子:

      int res1,res2; 
    volatile bool finished;

    void work_thread(int a,int b)
    {
    res1 = a + b;
    res2 = a - b;
    finished = true;
    }

    void spinning_thread()
    {
    while(!finished); //自旋等待res设置。
    }

    这里,完成允许在之前重新排序到 res 已设置!好吧,volatile防止与其他volatile重新排序,对吧?让我们尝试让每个 res volatile:

      volatile int res1,res2 ; 
    volatile bool finished;

    void work_thread(int a,int b)
    {
    res1 = a + b;
    res2 = a - b;
    finished = true;
    }

    void spinning_thread()
    {
    while(!finished); //自旋等待res设置。
    }

    这个简单的例子实际上可以在x86上工作, 。一个,这迫使 res1 res2 之前设置,即使我们真的不在乎。 。我们只是想让它们在完成之前设置。强制在 res1 res2 之间进行此顺序只会阻止有效的优化,在性能上消失。



    对于更复杂的问题,你必须 volatile 。这会膨胀你的代码,很容易出错,变得很慢,因为它阻止了比你真正想要的更多的重新排序。



    这是不现实的。所以我们使用栅栏和原子。它们允许完全优化,并且只保证内存访问将在篱笆点完成:

      int res1,res2; 
    std :: atomic< bool>完成

    void work_thread(int a,int b)
    {
    res1 = a + b;
    res2 = a - b;
    finished.store(true,std :: memory_order_release);
    }

    void spinning_thread()
    {
    while(!finished.load(std :: memory_order_acquire));
    }

    这将适用于所有架构。 res1 res2 操作可以按照编译器的要求重新排序。执行原子释放确保所有非原子操作被排序以完成并且对执行原子获取的线程可见。


    I just asked a question involving volatile: volatile array c++

    However my question spawned a discussion on what volatile does.

    Some claim that when using the CreateThread(), you don't have to worry about volatiles. Microsoft on the other hand gives an example of volatile when using two threads created by CreateThread().

    I created the following sample in visual c++ express 2010, and it doesn't matter if you mark done as volatile or not

    #include "targetver.h"
    #include <Windows.h>
    #include <stdio.h>
    #include <iostream>
    #include <tchar.h>
    
    using namespace std;
    
    bool done = false;
    DWORD WINAPI thread1(LPVOID args)
    {
        while(!done)
        {
    
        }
        cout << "Thread 1 done!\n";
        return 0;
    }
    DWORD WINAPI thread2(LPVOID args)
    {
        Sleep(1000);
        done = 1;
        cout << "Thread 2 done!\n";
        return 0;
    }
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    DWORD thread1Id;
    HANDLE hThread1;
    DWORD thread2Id;
    HANDLE hThread2;
    
    hThread1 = CreateThread(NULL, 0, thread1, NULL, 0, &thread1Id);
    hThread2 = CreateThread(NULL, 0, thread2, NULL, 0, &thread2Id);
    Sleep(4000);
    CloseHandle(hThread1);
    CloseHandle(hThread2);
    
    return 0;
    }
    

    Can you ALWAYS be sure that thread 1 will stop if done is not volatile?

    解决方案

    What volatile does:

    • Prevents the compiler from optimizing out any access. Every read/write will result in a read/write instruction.
    • Prevents the compiler from reordering the access with other volatiles.

    What volatile does not:

    • Make the access atomic.
    • Prevent the compiler from reordering with non-volatile accesses.
    • Make changes from one thread visible in another thread.

    Some non-portable behaviors that shouldn't be relied on in cross-platform C++:

    • VC++ has extended volatile to prevent any reordering with other instructions. Other compilers don't, because it negatively affects optimization.
    • x86 makes aligned read/write of pointer-sized and smaller variables atomic, and immediately visible to other threads. Other architectures don't.

    Most of the time, what people really want are fences (also called barriers) and atomic instructions, which are usable if you've got a C++11 compiler, or via compiler- and architecture-dependent functions otherwise.

    Fences ensure that, at the point of use, all the previous reads/writes will be completed. In C++11, fences are controlled at various points using the std::memory_order enumeration. In VC++ you can use _ReadBarrier(), _WriteBarrier(), and _ReadWriteBarrier() to do this. I'm not sure about other compilers.

    On some architectures like x86, a fence is merely a way to prevent the compiler from reordering instructions. On others they might actually emit an instruction to prevent the CPU itself from reordering things.

    Here's an example of improper use:

    int res1, res2;
    volatile bool finished;
    
    void work_thread(int a, int b)
    {
        res1 = a + b;
        res2 = a - b;
        finished = true;
    }
    
    void spinning_thread()
    {
        while(!finished); // spin wait for res to be set.
    }
    

    Here, finished is allowed to be reordered to before either res is set! Well, volatile prevents reordering with other volatile, right? Let's try making each res volatile too:

    volatile int res1, res2;
    volatile bool finished;
    
    void work_thread(int a, int b)
    {
        res1 = a + b;
        res2 = a - b;
        finished = true;
    }
    
    void spinning_thread()
    {
        while(!finished); // spin wait for res to be set.
    }
    

    This trivial example will actually work on x86, but it is going to be inefficient. For one, this forces res1 to be set before res2, even though we don't really care about that... we just want both of them set before finished is. Forcing this ordering between res1 and res2 will only prevent valid optimizations, eating away at performance.

    For more complex problems, you'll have to make every write volatile. This would bloat your code, be very error prone, and become slow as it prevents a lot more reordering than you really wanted.

    It's not realistic. So we use fences and atomics. They allow full optimization, and only guarantee that the memory access will complete at the point of the fence:

    int res1, res2;
    std::atomic<bool> finished;
    
    void work_thread(int a, int b)
    {
        res1 = a + b;
        res2 = a - b;
        finished.store(true, std::memory_order_release);
    }
    
    void spinning_thread()
    {
        while(!finished.load(std::memory_order_acquire));
    }
    

    This will work for all architectures. res1 and res2 operations can be reordered as the compiler sees fit. Performing an atomic release ensures that all non-atomic ops are ordered to complete and be visible to threads which perform an atomic acquire.

    这篇关于Volatile和CreateThread的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆