繁体   English   中英

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

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

我正在尝试编写一个缓冲区,该缓冲区可以将数据推送到缓冲区,检查是否已满,并在必要时交换缓冲区。 另一个线程可以获取文件输出的缓冲区。

我已经成功实现了缓冲区,但是我想添加一个ForceSwapBuffer方法,该方法将强制交换不完整的缓冲区并从不完整的缓冲区返回数据。 为了做到这一点,我检查读写缓冲区是否相同(试图强制交换缓冲区写入文件而没有其他可用的完整缓冲区时没有用)。 我希望该方法能够与GetBuffer方法并排运行(虽然不是必须的,但我想尝试一下并偶然发现了这个问题)。

GetBuffer将阻塞,并且当ForceSwapBuffer完成时它将仍然阻塞,直到新缓冲区完全充满为止,因为在ForceSwapBuffer中,我更改了原子_read_buffer_index。 我想知道这是否将一直有效? 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;
}

您的代码存在一些问题。 首先,在修改原子变量时要小心。 实际上只有一小部分操作是原子操作(请参阅http://en.cppreference.com/w/cpp/atomic/atomic ),并且原子操作的组合不是原子操作。 考虑:

_read_buffer_index = (_read_buffer_index + 1) % _count;

这里发生的是,您对变量有一个原子读取,一个增量,一个模运算和一个原子存储。 但是,整个语句本身不是原子的! 如果_count是2的幂,则只能使用++ -operator。 如果不是,则必须将_read_buffer_index读入一个临时变量,执行上述计算,然后在此期间未更改变量的情况下 ,使用compare_exchange函数存储新值。 显然,后者必须循环执行直到成功。 您还必须担心一个线程在第二个线程的read和compare_exchange之间增加变量_count次的可能性,在这种情况下,第二个线程错误地认为该变量未更改。

第二个问题是缓存行跳动。 如果在同一高速缓存行上有多个互斥锁,则如果两个或多个线程尝试同时访问它们,则性能将非常差。 高速缓存行的大小取决于您的平台。

主要问题是,虽然ForceSwapBuffer()Push()都锁定_force_swap_buffer互斥锁,但GetBuffer()却没有。 但是, GetBuffer()确实会更改_read_buffer_index 因此在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

这一假设_write_buffer_index == _read_buffer_indexif语句来实际上是无效的。

暂无
暂无

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

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