简体   繁体   中英

Self-implemented thread-safe string buffer in C++

Is this piece of code considered as thread-safe? When I consume the buffer, it will crash sometimes and i think it is contributed to data-racing problems, is there any problem with this implementation?

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

There's a race between the threads trying to set the isBusy variable. With std::atomic<> , loads and stores are guaranteed to be atomic, but there's a time windows between those two operations in the code. You need to use a different set of functions that provide the two atomically. See compare_exchange .

You can make your life easier by using the tools offered by the C++ standard library. To make sure only one thread accesses the given area (has an exclusive access) at a time, you can use std::mutex . Further you can use std::lock_guard , which will automatically lock (and unlock with the end of the scope) the mutex for you.

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
}

The mutex variable needs to be shared between the threads, make it a member variable of the class.

There's an alternative to using std::mutex by creating your own locking mechanism if you want to make sure the thread never goes to sleep. As pointed out in the comments, you probably don't need this and the usage of std::mutex will be fine. I'm keeping it here just for a reference.

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;
};

Notice the use of the more lightweight std::atomic_flag . Now you can use the class like this:

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
}

"is there any problem with this implementation?"

One problem I spot, is that std::atomic<bool> isBusy; wouldn't replace a std::mutex for locking concurrent access to m_buffer . You never set the value to true .

But even if you do so (as seen from your edit), store() and load() operations for the isBusy value don't form a lock to protect access to m_buffer in whole. Thread context switches may occur in between.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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