简体   繁体   English

用原子索引锁定互斥锁数组中的互斥锁

[英]Lock a mutex from mutex array with atomic index

I'm trying to write a buffer which can push data to the buffers, checks if full, swaps the buffer if necessary. 我正在尝试编写一个缓冲区,该缓冲区可以将数据推送到缓冲区,检查是否已满,并在必要时交换缓冲区。 Another thread can get a buffer for file output. 另一个线程可以获取文件输出的缓冲区。

I've successfully implemented the buffer but I wanted to add a ForceSwapBuffer method that would force an incomplete buffer to be swapped and return the data from the incomplete buffer. 我已经成功实现了缓冲区,但是我想添加一个ForceSwapBuffer方法,该方法将强制交换不完整的缓冲区并从不完整的缓冲区返回数据。 In order to do this I check if the read and write buffer are the same (there is no use in trying to force swap a buffer to write to a file while there are still other full buffers that could be written). 为了做到这一点,我检查读写缓冲区是否相同(试图强制交换缓冲区写入文件而没有其他可用的完整缓冲区时没有用)。 I want this method to be able to run side by side with the GetBuffer method (not really necessary but I wanted to try it and stumbled upon this problem). 我希望该方法能够与GetBuffer方法并排运行(虽然不是必须的,但我想尝试一下并偶然发现了这个问题)。

The GetBuffer would block and when ForceSwapBuffer is finished it would still block until the new buffer is completely full, because in the ForceSwapBuffer I change the atomic _read_buffer_index. GetBuffer将阻塞,并且当ForceSwapBuffer完成时它将仍然阻塞,直到新缓冲区完全充满为止,因为在ForceSwapBuffer中,我更改了原子_read_buffer_index。 I wonder if this will always work? 我想知道这是否将一直有效? Will the blocking lock of GetBuffer detect the change of the atomic read_buffer_index and change the mutex it is trying to lock or would it check at the start of the lock what mutex it has to lock and keep trying to lock the same mutex even when the index changes? GetBuffer的阻塞锁会检测到原子read_buffer_index的更改并更改它试图锁定的互斥锁,还是会在锁定开始时检查它必须锁定的互斥锁,并即使在索引处于锁定状态时也继续尝试锁定同一互斥锁变化?

/* selection of member data */
unsigned int _size, _count;

std::atomic<unsigned int> _write_buffer_index, _read_buffer_index;
unsigned int _index;

std::unique_ptr< std::unique_ptr<T[]>[] > _buffers;
std::unique_ptr< std::mutex[] > _mutexes;

std::recursive_mutex _force_swap_buffer;

/* selection of implementation of member functions */
template<typename T> // included to show the use of the recursive_mutex
void Buffer<T>::Push(T *data, unsigned int length) {
    std::lock_guard<std::recursive_mutex> lock(_force_swap_buffer);
    if (_index + length <= _size) {
        memcpy(&_buffers[_write_buffer_index][_index], data, length*sizeof(T));
        _index += length;
    } else {
        memcpy(&_buffers[_write_buffer_index][_index], data, (_size - _index)*sizeof(T));
        unsigned int t_index = _index;
        SwapBuffer();
        Push(&data[_size - t_index], length - (_size - t_index));
    }
}

template<typename T>
std::unique_ptr<T[]> Buffer<T>::GetBuffer() {
    std::lock_guard<std::mutex> lock(_mutexes[_read_buffer_index]); // where the magic should happen
    std::unique_ptr<T[]> result(new T[_size]);
    memcpy(result.get(), _buffers[_read_buffer_index].get(), _size*sizeof(T));
    _read_buffer_index = (_read_buffer_index + 1) % _count;
    return std::move(result);
}

template<typename T>
std::unique_ptr<T[]> Buffer<T>::ForceSwapBuffer() {
    std::lock_guard<std::recursive_mutex> lock(_force_swap_buffer); // lock that forbids pushing and force swapping at the same time

    if (_write_buffer_index != _read_buffer_index)
        return nullptr;

    std::unique_ptr<T[]> result(new T[_index]);
    memcpy(result.get(), _buffers[_read_buffer_index].get(), _index*sizeof(T));

    unsigned int next = (_write_buffer_index + 1) % _count;

    _mutexes[next].lock();
    _read_buffer_index = next; // changing the read_index while the other thread it blocked, the new mutex is already locked so the other thread should remain locked
    _mutexes[_write_buffer_index].unlock();

    _write_buffer_index = next;
    _index = 0;

    return result;
}

There are some problems with your code. 您的代码存在一些问题。 First, be careful when modifying atomic variables. 首先,在修改原子变量时要小心。 Only a small set of operations is really atomic (see http://en.cppreference.com/w/cpp/atomic/atomic ), and combinations of atomic operations are not atomic. 实际上只有一小部分操作是原子操作(请参阅http://en.cppreference.com/w/cpp/atomic/atomic ),并且原子操作的组合不是原子操作。 Consider: 考虑:

_read_buffer_index = (_read_buffer_index + 1) % _count;

What happens here is that you have an atomic read of the variable, an increment, a modulo operation, and an atomic store. 这里发生的是,您对变量有一个原子读取,一个增量,一个模运算和一个原子存储。 However, the whole statement itself is not atomic! 但是,整个语句本身不是原子的! If _count is a power of 2, you can just use the ++ -operator. 如果_count是2的幂,则只能使用++ -operator。 If it is not, you have to read _read_buffer_index into a temporary variable, perform the above calculations, and then use a compare_exchange function to store the new value if the variable was not changed in the mean time . 如果不是,则必须将_read_buffer_index读入一个临时变量,执行上述计算,然后在此期间未更改变量的情况下 ,使用compare_exchange函数存储新值。 Obviously the latter has to be done in a loop until it succeeds. 显然,后者必须循环执行直到成功。 You also have to worry about the possibility that one thread increments the variable _count times between the read and compare_exchange of a second thread, in which case the second thread erroneously thinks the variable was not changed. 您还必须担心一个线程在第二个线程的read和compare_exchange之间增加变量_count次的可能性,在这种情况下,第二个线程错误地认为该变量未更改。

The second problem is cache-line bouncing. 第二个问题是缓存行跳动。 If you have multiple mutexes on the same cache line, then if two or more threads try to access them simultaneously, the performance will be very bad. 如果在同一高速缓存行上有多个互斥锁,则如果两个或多个线程尝试同时访问它们,则性能将非常差。 What the size of a cache-line is depends on your platform. 高速缓存行的大小取决于您的平台。

The main problem is that while ForceSwapBuffer() and Push() both lock the _force_swap_buffer mutex, GetBuffer() does not. 主要问题是,虽然ForceSwapBuffer()Push()都锁定_force_swap_buffer互斥锁,但GetBuffer()却没有。 GetBuffer() however does change _read_buffer_index . 但是, GetBuffer()确实会更改_read_buffer_index So in ForceSwapBuffer() : 因此在ForceSwapBuffer()

std::lock_guard<std::recursive_mutex> lock(_force_swap_buffer);

if (_write_buffer_index != _read_buffer_index)
    return nullptr;

// another thread can call GetBuffer() here and change _read_buffer_index

// rest of the code here

The assumption that _write_buffer_index == _read_buffer_index after the if -statement is actually invalid. 这一假设_write_buffer_index == _read_buffer_indexif语句来实际上是无效的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM