當只有一個線程寫入共享變量時,我需要鎖嗎? (Do I need a lock when only a single thread writes to a shared variable?)


問題描述

當只有一個線程寫入共享變量時,我需要鎖嗎? (Do I need a lock when only a single thread writes to a shared variable?)

I have 2 threads and a shared float global.  One thread only writes to the variable while the other only reads from it, do I need to lock access to this variable?  In other words:

volatile float x;

void reader_thread() {
    while (1) {
        // Grab mutex here?
        float local_x = x;
        // Release mutex?
        do_stuff_with_value(local_x);
    }
}

void writer_thread() {
    while (1) {
        float local_x = get_new_value_from_somewhere();
        // Grab mutex here?
        x = local_x;
        // Release mutex?
    }
}

My main concern is that a load or store of a float not being atomic, such that local_x in reader_thread ends up having a bogus, partially updated value.

  1. Is this a valid concern?
  2. Is there another way to guarantee atomicity without a mutex?
  3. Would using sig_atomic_t as the shared variable work, assuming it has enough bits for my purposes?

The language in question is C using pthreads.


參考解法

方法 1:

Different architectures have different rules, but in general, memory loads and stores of aligned, int-sized objects are atomic.  Smaller and larger may be problematic.  So if sizeof(float) == sizeof(int) you might be safe, but I still wouldn't depend on it in a portable program.

Also, the behavior of volatile isn't particularly well-defined...  The specification uses it as a way to prevent optimizing away accesses to memory-mapped device I/O, but says nothing about its behavior on any other memory accesses.

In short, even if loads and stores are atomic on float x, I would use explicit memory barriers (though how varies by platform and compiler) in instead of depending on volatile.  Without the guarantee of loads and stores being atomic, you would have to use locks, which do imply memory barriers.

方法 2:

According to section 24.4.7.2 of the GNU C library documentation:

  

In practice, you can assume that int and other integer types no longer than int are atomic. You can also assume that pointer types are atomic; that is very convenient. Both of these assumptions are true on all of the machines that the GNU C library supports and on all POSIX systems we know of.

float technically doesn't count under these rules, although if a float is the same size as an int on your architecture, what you could do is make your global variable an int, and then convert it to a float with a union every time you read or write it.

The safest course of action is to use some form of mutex to protect accesses to the shared variable.  Since the critical sections are extremely small (reading/writing a single variable), you're almost certainly going to get better performance out of a light-weight mutex such as a spin lock, as opposed to a heavy-weight mutex that makes system calls to do its job.

方法 3:

I would lock it down. I'm not sure how large float is in your environment, but it might not be read/written in a single instruction so your reader could potentially read a half-written value. Remember that volatile doesn't say anything about atomicity of operations, it simply states that the read will come from memory instead of being cached in a register or something like that.

方法 4:

The assignment is not atomic, at least for some compilers, and in the sense that it takes a single instruction to perform. The following code was generated by Visual C++ 6.0 - f1  and f2 are of type float.

4:        f2 =  f1;
00401036   mov         eax,dword ptr [ebp-4]
00401039   mov         dword ptr [ebp-8],eax

方法 5:

I believe you should use a mutex.  Someday, your code might run on a system that does not have real floating point hardware and uses emulation instead, resulting in non-atomic float variables. For an example, look at -msoft-float here.

My answer was not very useful.  gcc -msoft-float is possibly just a specific case where loads and stores of floats are not atomic.

(by Daniel DickisonephemientAdam RosenfieldD.Shawleyanonsigjuice)

參考文件

  1. Do I need a lock when only a single thread writes to a shared variable? (CC BY-SA 3.0/4.0)

#locking #mutex #multithreading #C






相關問題

C# / ASP.NET - Web 應用程序鎖定 (C# / ASP.NET - Web Application locking)

在程序文件夾中創建鎖定文件會導致異常 (Creating Lock file in Programs Folder causes exception)

我什麼時候會使用 AutoResetEvent 和 ManualResetEvent 而不是 Monitor.Wait()/Monitor.Pulse()? (When would I use AutoResetEvent and ManualResetEvent instead of Monitor.Wait()/Monitor.Pulse()?)

鎖定一個 JavaScript 函數 (Lock a JavaScript Function)

當只有一個線程寫入共享變量時,我需要鎖嗎? (Do I need a lock when only a single thread writes to a shared variable?)

為什麼 lock(objLock) 比 lock(this) 好 (Why is it better to lock(objLock) than lock(this))

雙重檢查鎖定的修復有什麼問題? (What's wrong with this fix for double checked locking?)

在表格行上調用 Dibs (Calling Dibs on a table row)

我怎樣才能優雅地寫 lock {}? (how can I write lock {} elegantly?)

Double Check Lock 不能在這個 java 代碼上工作? (Doubly Check Lock Dosen't work on this java code?)

LINQPad / LINQ To SQL - 簡單查詢僅在循環內執行時才會引發內存不足 (LINQPad / LINQ To SQL - Simple Query Throws Out of Memory Only When Executed Inside a Loop)

如何在 dynamoDB 中實現 50 次寫入的事務? (How can I implement a transaction of 50 writes in dynamoDB?)







留言討論