Is it necessary to lock an array that is *only written to* from one thread and *only read from* another?

前端 未结 10 1398
后悔当初
后悔当初 2020-12-13 19:26

I have two threads running. They share an array. One of the threads adds new elements to the array (and removes them) and the other uses this array (read operations only).

相关标签:
10条回答
  • 2020-12-13 20:02

    To answer your question: maybe.

    Simply put, the way that the question is framed doesn't provide enough information about whether or not a lock is required.

    In most standard use cases, the answer would be yes. And most of the answers here are covering that case pretty well.

    I'll cover the other case.

    When would you not need a lock given the information you have provided?

    There are some other questions here that would help better define whether you need a lock, whether you can use a lock-free synchronization method, or whether or not you can get away with no explicit synchronization.

    Will writing data ever be non-atomic? Meaning, will writing data ever result in "torn data"? If your data is a single 32 bit value on an x86 system, and your data is aligned, then you would have a case where writing your data is already atomic. It's safe to assume that if your data is of any size larger than the size of a pointer (4 bytes on x86, 8 on x64), then your writes cannot be atomic without a lock.

    Will the size of your array ever change in a way that requires reallocation? If your reader is walking through your data, will the data suddenly be "gone" (memory has been "delete"d)? Unless your reader takes this into account (unlikely), you'll need a lock if reallocation is possible.

    When you write data to your array, is it ok if the reader "sees" old data?

    If your data can be written atomically, your array won't suddenly not be there, and it's ok for the reader to see old data... then you won't need a lock. Even with those conditions being met, it would be appropriate to use the built in atomic functions for reading and storing. But, that's a case where you wouldn't need a lock :)

    Probably safest to use a lock since you were unsure enough to ask this question. But, if you want to play around with the edge case of where you don't need a lock... there you go :)

    0 讨论(0)
  • 2020-12-13 20:04

    It depends. One situation where it could be bad is if you are removing an item in one thread then reading the last item by its index in your read thread. That read thread would throw an OOB error.

    0 讨论(0)
  • 2020-12-13 20:04

    If it's a fixed-size array, and you don't need to communicate anything extra like indices written/updated, then you can avoid mutual exclusion with the caveat that the reader may see:

    • no updates at all
      • If your memory ordering is relaxed enough that this happens, you need a store fence in the writer and a load fence in the consumer to fix it
    • partial writes
      • if the stored type is not atomic on your platform (int generally should be)
      • or your values are un-aligned, and especially if they may span cache lines

    This is all dependent on your platform though - hardware, OS and compiler can all affect it. You haven't told us what they are.

    The portable C++11 solution is to use an array of atomic<int>. You still need to decide what memory ordering constraints you require, and what that means for correctness and performance on your platform.

    0 讨论(0)
  • 2020-12-13 20:05

    As far as I know, this is exactly the usecase for a lock. Two threads which access one array concurrently must ensure that one thread is ready with its work. Thread B might read unfinished data if thread A did not finish work.

    0 讨论(0)
  • 2020-12-13 20:08

    Lock? No. But you do need some synchronization mechanism.

    What you're describing sounds an awful like a "SPSC" (Single Producer Single Consumer) queue, of which there are tons of lockfree implementations out there including one in the Boost.Lockfree

    The general way these work is that underneath the covers you have a circular buffer containing your objects and an index. The writer knows the last index it wrote to, and if it needs to write new data it (1) writes to the next slot, (2) updates the index by setting the index to the previous slot + 1, and then (3) signals the reader. The reader then reads until it hits an index that doesn't contain the index it expects and waits for the next signal. Deletes are implicit since new items in the buffer overwrite previous ones.

    You need a way to atomically update the index, which is provided by atomic<> and has direct hardware support. You need a way for a writer to signal the reader. You also might need memory fences depending on the platform s.t. (1-3) occur in order. You don't need anything as heavy as a lock.

    0 讨论(0)
  • 2020-12-13 20:09

    If you use e.g. vector for your array (so that it can dynamically grow), then reallocation may occur during the writes, you lose.

    If you use data entries larger than is always written and read atomically (virtually any complex data type), you lose.

    If the compiler / optimizer decides to keep certain things in registers (such as the counter holding the number of valid entries in the array) during some operations, you lose.

    Or even if the compiler / optimizer decides to switch order of execution for your array element assignments and counter increments/decrements, you lose.

    So you certianly do need some sort of synchronization. What is the best way to do so (for example it may be worth while to lock only parts of the array), depends on your specifics (how often and in what pattern do the threads access the array).

    0 讨论(0)
提交回复
热议问题