代码之家  ›  专栏  ›  技术社区  ›  Simon P Stevens

访问C中的简单布尔标记时,是否需要锁定或标记为易失性?

  •  42
  • Simon P Stevens  · 技术社区  · 16 年前

    假设您有一个在后台线程上运行的简单操作。您希望提供一种取消此操作的方法,以便从“取消”按钮的单击事件处理程序创建一个布尔标志,并将其设置为true。

    private bool _cancelled;
    
    private void CancelButton_Click(Object sender ClickEventArgs e)
    {
        _cancelled = true;
    }
    

    现在,您正在从GUI线程设置取消标志,但您正在从后台线程读取它。在进入前大梁之前需要锁定吗?

    是否需要执行此操作(并且显然也锁定了按钮单击事件处理程序):

    while(operationNotComplete)
    {
        // Do complex operation
    
        lock(_lockObject)
        {
            if(_cancelled)
            {
                break;
            }
        }
    }
    

    或者可以这样做(不带锁):

    while(!_cancelled & operationNotComplete)
    {
        // Do complex operation
    }
    

    或者将_cancelled变量标记为volatile。有必要吗?

    [我知道BackgroundWorker类有其内置的cancelAsync()方法,但我对这里的语义和锁和线程变量访问的使用感兴趣,而不是具体的实现,代码只是一个例子。]

    似乎有两种理论。

    1)因为它是一个简单的内置类型(并且对内置类型的访问在.NET中是原子的),并且因为我们只在一个地方写入它,并且只在后台线程上读取,所以不需要锁定或标记为易失性。
    2)您应该将其标记为易失性,因为如果不这样做,编译器可能会优化while循环中的读取,因为它认为没有任何东西可以修改该值。

    哪种技术是正确的?(为什么?)

    [编辑:关于这一点,似乎有两个明确界定的对立学派。我正在寻找一个明确的答案,因此,如果可能,请发布您的原因,并在您的答案中引用您的来源。]

    5 回复  |  直到 10 年前
        1
  •  37
  •   Community Mohan Dere    8 年前

    首先,线程是复杂的;-p

    是的,尽管有很多相反的谣言 要求 任何一个 使用 lock volatile (但不是两者)访问 bool 来自多个线程。

    对于简单类型和访问,如退出标志( 布尔 ) 不稳定的 这就足够了-这可以确保线程不会缓存其寄存器中的值(意味着:其中一个线程从未看到更新)。

    对于较大的值(原子性是一个问题),或者您希望在其中同步 序列 操作(典型的例子是“如果不存在并添加”字典访问),a 更通用。这起到了内存屏障的作用,因此仍然为您提供了线程安全性,但提供了其他功能,如脉冲/等待。请注意,您不应该使用 在值类型或 string 也不 Type this ;最好的选择是将自己的锁定对象作为字段( readonly object syncLock = new object(); )把这个锁上。

    例如,如果不同步,它会严重中断(即永远循环)- see here .

    要跨越多个程序,操作系统原语类似 Mutex *ResetEvent 可能也很有用,但这对单个exe来说是多余的。

        2
  •  6
  •   bruno conde    16 年前

    _cancelled 必须是 volatile . (如果不选择锁定)

    如果一个线程更改 取消的 ,其他线程可能看不到更新的结果。

    另外,我认为 取消的 原子的 :

    CLI规范第12.6.6节规定: “合格的CLI应保证 正确的读写访问 对齐的内存位置不大于 比本地单词的大小是原子的 当所有的写访问 位置大小相同。”

        3
  •  5
  •   ΩmegaMan    10 年前

    不需要锁定,因为您只有一个编写器场景,而布尔字段是一个简单的结构,不存在损坏状态的风险。( while it was possible to get a boolean value that is neither false nor true )但是你必须把这块地标为 volatile 以防止编译器进行一些优化。没有 不稳定的 修饰符编译器可以在工作线程上的循环执行期间将值缓存在寄存器中,反过来,循环将永远无法识别更改的值。此msdn文章( How to: Create and Terminate Threads (C# Programming Guide) )解决这个问题。 当需要锁定时,锁定将具有与标记字段相同的效果。 不稳定的 .

        4
  •  2
  •   yfeldblum    16 年前

    对于线程同步,建议使用 EventWaitHandle 类,例如 ManualResetEvent . 虽然使用一个简单的布尔标记比这里要简单得多(是的,您希望将其标记为 volatile )在我看来,最好是开始使用穿线工具。为了你的目的,你会这样做…

    private System.Threading.ManualResetEvent threadStop;
    
    void StartThread()
    {
        // do your setup
    
        // instantiate it unset
        threadStop = new System.Threading.ManualResetEvent(false); 
    
        // start the thread
    }
    

    在你的思路中……

    while(!threadStop.WaitOne(0) && !operationComplete)
    {
        // work
    }
    

    然后在GUI中取消…

    threadStop.Set();
    
        5
  •  1
  •   hughdbrown    16 年前

    仰望 Interlocked.Exchange() . 它可以很快地复制到一个局部变量中,用于比较。它比lock()快。