Является ли этот кусок кода рассмотренным как поточно-безопасный? Когда я использую буфер, он будет иногда сбой, и я думаю, что это способствовало возникновению проблем с данными, есть ли проблемы с этой реализацией?
TSByteBuf.cpp
#include "TSByteBuf.h"
int TSByteBuf::Read(byte* buf, int len)
{
while (true)
{
if (isBusy.load())
{
//Sleep(10);
}else
{
isBusy.store(true);
int dByteGet = m_buffer.sgetn((char*) buf, len);
isBusy.store(false);
return dByteGet;
}
}
}
int TSByteBuf::Write(byte* buf, int len)
{
while (true)
{
if (isBusy.load())
{
//Sleep(10);
}else
{
isBusy.store(true);
int dBytePut = m_buffer.sputn((char*) buf, len);
isBusy.store(false);
return dBytePut;
}
}
}
TSByteBuf.h
#ifndef TSBYTEBUF_H
#define TSBYTEBUF_H
#include <sstream>
#include <atomic>
typedef unsigned char byte;
class TSByteBuf
{
public:
std::stringbuf m_buffer;
//bool Write(byte* buf, int len);
//bool Read(byte* buf, int len);
int Write(byte* buf, int len);
int Read(byte* buf, int len);
protected:
std::atomic<bool> isBusy;
};
#endif
Там есть пробег между нитями, пытающимися установить переменную isBusy
. При использовании std::atomic<>
нагрузки и хранилища гарантированно будут атомарными, но есть временные окна между этими двумя операциями в коде. Вам нужно использовать другой набор функций, которые обеспечивают два атомарно. См. Compare_exchange.
Вы можете сделать вашу жизнь проще, используя инструменты, предлагаемые стандартной библиотекой C++. Чтобы гарантировать, что только один поток обращается к данной области (имеет эксклюзивный доступ) за раз, вы можете использовать std::mutex
. Далее вы можете использовать std::lock_guard
, который автоматически блокирует (и разблокирует с концом области) мьютекс для вас.
int TSByteBuf::Read(byte* buf, int len)
{
std::lock_guard<std::mutex> lg(mutex);
// do your thing, no need to unlock afterwards, the guard will take care of it for you
}
Переменная mutex
должна делиться между потоками, сделать ее переменной-членом класса.
Существует альтернатива использованию std::mutex
, создавая свой собственный механизм блокировки, если вы хотите, чтобы нить никогда не спала. Как указано в комментариях, вам, вероятно, это не нужно, и использование std::mutex
будет в порядке. Я держу его здесь только для справки.
class spin_lock {
public:
spin_lock() : flag(ATOMIC_FLAG_INIT) {}
void lock() {
while (flag.test_and_set(std::memory_order_acquire))
;
}
void unlock() { flag.clear(std::memory_order_release); }
private:
std::atomic_flag flag;
};
Обратите внимание на использование более легкого std::atomic_flag
. Теперь вы можете использовать класс следующим образом:
int TSByteBuf::Read(byte* buf, int len)
{
std::unique_lock<spin_lock> lg(spinner);
// do your thing, no need to unlock afterwards, the guard will take care of it for you
}
std::mutex
?
std::mutex
может вращаться, но в конце концов засыпает.
"Есть ли проблемы с этой реализацией?"
Одна проблема, которую я вижу, заключается в том, что std::atomic<bool> isBusy;
не заменит std::mutex
для блокировки одновременного доступа к m_buffer
. Вы никогда не устанавливаете значение в true
.
Но даже если вы это сделаете (как видно из вашего редактирования), операции store()
и load()
для значения isBusy
не образуют блокировку для защиты доступа к m_buffer
целиком. Переключатели контекста потока могут возникать между ними.
true
. Таким образом, он не может обеспечить замену мьютекса.
isBusy
значение true.isBusy.store(true);
прямо перед изменением буфера