在C++类中构建互斥保护的线程安全方法
Thread-safe way to build mutex protection into a C++ class?
我正试图用C++为我正在处理的项目实现一个生产者/消费者模型的多线程程序。基本思想是主线程创建第二个线程来监视串行端口中的新数据,处理数据,并将结果放入缓冲区中,该缓冲区由主线程定期轮询。我以前从未写过多线程程序。我读了很多教程,但都是C语言。我想我已经掌握了基本概念,但我正在尝试用C++实现它。对于缓冲区,我想创建一个内置互斥保护的数据类。这就是我想到的。
1) 我是不是搞错了?有没有更聪明的方法来实现受保护的数据类?
2) 如果两个线程试图同时调用ProtectedBuffer::add_back()
,下面的代码会发生什么?
#include <deque>
#include "pthread.h"
template <class T>
class ProtectedBuffer {
std::deque<T> buffer;
pthread_mutex_t mutex;
public:
void add_back(T data) {
pthread_mutex_lock(&mutex);
buffer.push_back(data);
pthread_mutex_unlock(&mutex);
}
void get_front(T &data) {
pthread_mutex_lock(&mutex);
data = buffer.front();
buffer.pop_front();
pthread_mutex_unlock(&mutex);
}
};
编辑:谢谢你的建议。我试着在下面实现它们。我还添加了一些错误检查,所以如果一个线程以某种方式试图锁定同一个互斥对象两次,它将正常失败。我想。
#include "pthread.h"
#include <deque>
class Lock {
pthread_mutex_t &m;
bool locked;
int error;
public:
explicit Lock(pthread_mutex_t & _m) : m(_m) {
error = pthread_mutex_lock(&m);
if (error == 0) {
locked = true;
} else {
locked = false;
}
}
~Lock() {
if (locked)
pthread_mutex_unlock(&m);
}
bool is_locked() {
return locked;
}
};
class TryToLock {
pthread_mutex_t &m;
bool locked;
int error;
public:
explicit TryToLock(pthread_mutex_t & _m) : m(_m) {
error = pthread_mutex_trylock(&m);
if (error == 0) {
locked = true;
} else {
locked = false;
}
}
~TryToLock() {
if (locked)
pthread_mutex_unlock(&m);
}
bool is_locked() {
return locked;
}
};
template <class T>
class ProtectedBuffer{
pthread_mutex_t mutex;
pthread_mutexattr_t mattr;
std::deque<T> buffer;
bool failbit;
ProtectedBuffer(const ProtectedBuffer& x);
ProtectedBuffer& operator= (const ProtectedBuffer& x);
public:
ProtectedBuffer() {
pthread_mutexattr_init(&mattr);
pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_ERRORCHECK);
pthread_mutex_init(&mutex, &mattr);
failbit = false;
}
~ProtectedBuffer() {
pthread_mutex_destroy(&mutex);
pthread_mutexattr_destroy(&mattr);
}
void add_back(T &data) {
Lock lck(mutex);
if (!lck.locked()) {
failbit = true;
return;
}
buffer.push_back(data);
failbit = false;
}
void get_front(T &data) {
Lock lck(mutex);
if (!lck.locked()) {
failbit = true;
return;
}
if (buffer.empty()) {
failbit = true;
return;
}
data = buffer.front();
buffer.pop_front();
failbit = false;
}
void try_get_front(T &data) {
TryToLock lck(mutex);
if (!lck.locked()) {
failbit = true;
return;
}
if (buffer.empty()) {
failbit = true;
return;
}
data = buffer.front();
buffer.pop_front();
failbit = false;
}
void try_add_back(T &data) {
TryToLock lck(mutex);
if (!lck.locked()) {
failbit = true;
return;
}
buffer.push_back(data);
failbit = false;
}
};
几件事:
-
您需要在构造函数中用
pthread_mutex_init
初始化mutex
,并在析构函数中用pthread_mutex_destroy
释放它。 -
您必须使类不可复制且不可赋值(或者以其他方式正确实现复制构造函数和赋值运算符;请参见上文)。
-
值得为锁创建一个SBRM帮助程序类:
class Lock { pthread_mutex_t & m; public: explicit Lock(pthread_mutex_t & _m) : m(_m) { pthread_mutex_lock(&m); } ~Lock() { pthread_mutex_unlock(&m); } };
现在您可以制作一个类似
{ Lock lk(mutex); /* ... */ }
的同步作用域。
至于问题2:并发访问是通过锁定互斥锁来序列化的。其中一个竞争线程将在获取互斥锁时休眠。
我是不是搞错了?有没有更聪明的方法来实现受保护的数据类?
对于你的实施,我认为你有一个良好的开端。既然您询问了C++编译,那么如果您有一个支持C++11的编译器,您就可以使用新的线程支持。
你提到你希望主线程轮询这个缓冲区,但我没有看到任何机制允许它这样做。要么get_front
应该在缓冲区中没有任何东西时提供错误,要么get_buffer
应该阻止调用方,直到数据可用。
#include <deque>
#include <mutex>
#include <condition_variable>
#include <stdexcept>
template <class T>
class ProtectedBuffer {
std::deque<T> buffer;
std::mutex mtx;
std::condition_variable empty_cnd;
void get_front_i(T &data) {
data = buffer.front();
buffer.pop_front();
}
public:
void add_back(T data) {
std::lock_guard<std::mutex> g(mtx);
bool was_empty = buffer.empty();
buffer.push_back(data);
if (was_empty) empty_cnd.notify_one();
}
void get_front_check(T &data) {
std::lock_guard<std::mutex> g(mtx);
if (buffer.empty()) throw std::underflow_error("no data");
get_front_i(data);
}
void get_front_block(T &data) {
std::lock_guard<std::mutex> g(mtx);
std::unique_lock<std::mutex> u(mtx);
while (buffer.empty()) empty_cnd.wait(u);
get_front_i(data);
if (!buffer.empty()) empty_cnd.notify_one();
}
};
如果您想绑定添加到缓冲区的数据量,可以添加一个类似的full_cnd
条件变量来检查add_back
调用等待的完整条件(如果为true)。然后,get_front_i
方法可以在缓冲区不再满时发出信号。
如果两个线程试图同时调用ProtectedBuffer::add_back(),下面的代码会发生什么?
由于add_back
受到保护而不被互斥,因此如果两个线程同时调用它,则一个线程将被阻止调用push_back
,直到另一个线程完成为止。
您已经掌握了基本知识,但我会更进一步,将互斥对象本身封装在它自己的RAII包装中,例如:
#include <deque>
#include "pthread.h"
class ProtectedMutex
{
pthread_mutex_t &mutex;
public:
ProtectedMutex(pthread_mutex_t &m)
: mutex(m);
{
pthread_mutex_lock(&mutex);
}
~ProtectedMutex()
{
pthread_mutex_unlock(&mutex);
}
};
template <class T>
class ProtectedBuffer {
std::deque<T> buffer;
pthread_mutex_t mutex;
public:
void add_back(T data) {
ProtectedMutex m(mutex);
buffer.push_back(data);
}
void get_front(T &data) {
ProtectedMutex m(mutex);
data = buffer.front();
buffer.pop_front();
}
};
'将结果放入主线程定期轮询的缓冲区中'-CPU浪费和延迟。
"我是不是搞错了?"-对我不知道您的系统中对辅助线程<>有什么样的支持GUI线程命令,但总是有PostMessage()API。
当然,您需要一个Buffer类,其中包含串行rx数据的数据成员和执行协议/"进程数据"的方法。你不需要太多其他东西。在第二个线程中,创建一个缓冲区类实例。加载它,处理数据并PostMessage/sdispatch/BeginInvoke它指向GUI线程的指针。在串行线程中的下一行代码中,在同一实例指针var中创建另一个实例,用于从串行端口下一次加载数据。在GUI中显示/记录/任何内容后,GUI线程应该删除()它接收到的*缓冲区。
没有延迟,没有CPU浪费,没有数据复制,没有串行线程和GUI线程在同一缓冲区实例上工作的机会,没有讨厌的、复杂的缓冲区共享代码,没有锁,没有麻烦。它会很好用的。
其他任何事情都会一团糟。
编辑-忘记了(2)-不知道,。不会用驳船杆子碰它。。
- 从不同线程使用int64的不同字节安全吗
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 在C++中使用cURL和多线程
- 为什么我的C#代码在调用回C++COM直到Task时会暂停.等待/线程.加入
- 在cuda线程之间共享大量常量数据
- 每个线程 C++ 保护以防止重入函数调用
- 死锁使用 std::mutex 来保护多个线程中的 cout
- 通过为每个线程独占使用对象(不在线程之间共享)是保护该类的成员函数所必需的
- C++,如何创建线程限制/受保护的变量和函数
- 多线程设计,在C 中保护全球成员
- 在多线程环境中使用atomic保护两个变量
- 我需要保护一个由一个线程编写并由多个线程读取的变量吗
- 在C++类中构建互斥保护的线程安全方法
- 变量的线程保护
- 多线程:我需要用只读方法保护我的变量吗
- 全局变量构造函数/析构函数是否需要线程保护
- C++11中的线程:接近锁保护的更好方法
- 保护方法内的Boost线程函数
- 无法获取 std::mutex 以正确保护线程访问
- 我需要在多线程环境中保护对STL容器的读取访问吗