簡體   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