C++ c++;11对对象状态和永久递增索引使用原子的多读取器/多写入器队列
我使用原子和循环缓冲区来实现多读线程、多写线程对象池 这很难调查,因为插入代码会导致bug消失 模型 生产者(或写入线程)向C++ c++;11对对象状态和永久递增索引使用原子的多读取器/多写入器队列,c++,multithreading,c++11,atomic,C++,Multithreading,C++11,Atomic,我使用原子和循环缓冲区来实现多读线程、多写线程对象池 这很难调查,因为插入代码会导致bug消失 模型 生产者(或写入线程)向环请求元素,以便“准备”元素。终止时,writer线程更改元素状态,以便读取器可以“使用”它。之后,该元素再次可用于写入 使用者(或读线程)向环请求一个对象,以便“读取”该对象。 “释放”对象后,对象处于状态::就绪状态,例如可供读卡器线程使用。 如果没有对象可用,它可能会失败,例如环中的下一个可用对象未处于state::Unused状态 两个类,元素和环 元素: 要写入
环请求元素
,以便“准备”元素。终止时,writer线程更改元素状态,以便读取器可以“使用”它。之后,该元素再次可用于写入
使用者(或读线程)向环请求一个对象,以便“读取”该对象。
“释放”对象后,对象处于状态::就绪状态,例如可供读卡器线程使用。
如果没有对象可用,它可能会失败,例如环中的下一个可用对象未处于state::Unused
状态
两个类,元素
和环
元素
:
- 要写入,写入线程必须成功地将
\u state
成员从state::Unused
交换到state::LockForWrite
- 完成后,writer线程将状态强制为
state::Ready
(它应该是唯一处理此元素的线程)
- 要读取,rader线程必须成功地将
\u state
成员从state::Ready
交换到state::LockForRead
- 完成后,读卡器线程将状态强制为
state::Unused
(它应该是唯一处理此元素的线程)
总结:
- 写入程序生命周期:
state::Unused
->state::LockForWrite
->state::Ready
- 读卡器生命周期:
state::Ready
->state::LockForRead
->state::Unused
Ring
- 具有
元素的向量
,被视为循环缓冲区李>
std::原子读、写代码>是用于通过以下方式访问元素的两个索引:
\u elems[\u write%\u elems.size()]
对于编写器
\u elems[\u read%\u elems.size()]
供读者阅读
当读卡器成功地LockForRead
对象时,\u read
索引将递增。
当写入程序成功地锁定写入对象时,\u write
索引将递增
main
:
我们向向量中添加了一些共享相同环的写入线程和读取线程。每个线程只需尝试获取_read或_write元素,并在之后释放它们
基于元素
转换,一切都应该正常,但可以观察到环在某个点被阻塞,就像是因为环中的某些元素处于状态状态::就绪
,其上有一个\u write%\u elems.size(),环中的某些元素处于状态state::Unused
,其上有一个\u read%\u elems.size()
索引Both=死锁
#include<atomic>
#include<vector>
#include<thread>
#include<iostream>
#include<cstdint>
typedef enum : int
{
Unused, LockForWrite, Ready, LockForRead
}state;
class Element
{
std::atomic<state> _state;
public:
Element():_state(Unused){ }
// a reader need to successfully make the transition Ready => LockForRead
bool lock_for_read() { state s = Ready; return _state.compare_exchange_strong(s, LockForRead); }
void unlock_read() { state s = Unused; _state.store(s); }
// a reader need to successfully make the transition Unused => LockForWrite
bool lock_for_write() { state s = Unused; return _state.compare_exchange_strong(s, LockForWrite); }
void unlock_write() { state s = Ready; _state.store(s); }
};
class Ring
{
std::vector<Element> _elems;
std::atomic<int64_t> _read, _write;
public:
Ring(size_t capacity)
: _elems(capacity), _read(0), _write(0) {}
Element * get_for_read() {
Element * ret = &_elems[ _read.load() % _elems.size() ];
if (!ret->lock_for_read()) // if success, the object belongs to the caller thread as reader
return NULL;
_read.fetch_add(1); // success! incr _read index
return ret;
}
Element * get_for_write() {
Element * ret = &_elems[ _write.load() % _elems.size() ];
if (!ret->lock_for_write())// if success, the object belongs to the caller thread as writer
return NULL;
_write.fetch_add(1); // success! incr _write index
return ret;
}
void release_read(Element* e) { e->unlock_read();}
void release_write(Element* e) { e->unlock_write();}
};
int main()
{
const int capacity = 10; // easy to process modulo[![enter image description here][1]][1]
std::atomic<bool> stop=false;
Ring ring(capacity);
std::function<void()> writer_job = [&]()
{
std::cout << "writer starting" << std::endl;
Element * e;
while (!stop)
{
if (!(e = ring.get_for_write()))
continue;
// do some real writer job ...
ring.release_write(e);
}
};
std::function<void()> reader_job = [&]()
{
std::cout << "reader starting" << std::endl;
Element * e;
while (!stop)
{
if (!(e = ring.get_for_read()))
continue;
// do some real reader job ...
ring.release_read(e);
}
};
int nb_writers = 1;
int nb_readers = 2;
std::vector<std::thread> threads;
threads.reserve(nb_writers + nb_readers);
std::cout << "adding writers" << std::endl;
while (nb_writers--)
threads.push_back(std::thread(writer_job));
std::cout << "adding readers" << std::endl;
while (nb_readers--)
threads.push_back(std::thread(reader_job));
// wait user key press, halt in debugger after 1 or 2 seconds
// in order to reproduce problem and watch ring
std::cin.get();
stop = true;
std::cout << "waiting all threads...\n";
for (auto & th : threads)
th.join();
std::cout << "end" << std::endl;
}
#包括
#包括
#包括
#包括
#包括
typedef枚举:int
{
未使用、锁定写入、就绪、锁定读取
}国家;
类元素
{
原子态;
公众:
元素():_状态(未使用){}
//读卡器需要成功地使转换就绪=>LockForRead
bool lock_for_read(){states s=Ready;返回_state.compare_exchange_strong(s,LockForRead);}
void unlock_read(){state s=Unused;_state.store;}
//读卡器需要成功地使转换未使用=>LockForWrite
bool lock_for_write(){states s=Unused;返回_state.compare_exchange_strong(s,LockForWrite);}
void unlock_write(){state s=Ready;_state.store;}
};
阶级戒指
{
标准:向量元素;
std::原子读,写;
公众:
环(尺寸和容量)
:_elems(容量),_read(0),_write(0){
元素*get_for_read(){
元素*ret=&_元素[_read.load()%_elems.size()];
if(!ret->lock_for_read())//如果成功,则对象属于作为读取器的调用线程
返回NULL;
_read.fetch_add(1);//成功!增加读取索引
返回ret;
}
元素*get_for_write(){
元素*ret=&_元素[_write.load()%_elems.size()];
if(!ret->lock_for_write())//如果成功,则对象属于作为writer的调用线程
返回NULL;
_write.fetch_add(1);//成功!增加写入索引
返回ret;
}
void release_read(元素*e){e->unlock_read();}
void release_write(元素*e){e->unlock_write();}
};
int main()
{
const int capacity=10;//易于处理模[![在此处输入图像描述][1][1]
标准::原子停止=假;
环(容量);
std::函数编写器_作业=[&]()
{
std::cout在两个共享计数器的增量(读取和写入)周围没有原子部分。
这对我来说很糟糕,你可以毫无意义地切换另一个元素
想象一下这个场景,
1名读者R1和1名作家W正在愉快地合作
读卡器2执行:Element*ret=&_elems[_read.load()%_elems.size()];
然后被推离cpu
现在R1和W仍然在一起玩,所以_read和_write的位置现在是任意的W.r.t。R2指向的元素ret
现在,在某个时刻R2被调度,并且碰巧*ret_uu是可读的(同样可能,R1和W在块中循环了几次)
哎哟,正如你所看到的,我们将阅读它,并增加“_read”,但是_read与_ret没有关系。这会创建一种孔,未被读取的元素,但在_read索引之下
因此,制作关键部分以确保在sa中完成_read/_write的增量
class Element
{
std::atomic<state> _state;
public:
Element():_state(Unused){ }
// a reader need to successfully make the transition Ready => LockForRead
bool lock_for_read() {
state s = Ready;
return _state.compare_exchange_strong(s, LockForRead);
}
void abort_read() { _state = Ready; }
void unlock_read() { state s = Unused; _state.store(s); }
// a reader need to successfully make the transition Unused => LockForWrite
bool lock_for_write() {
state s = Unused;
return _state.compare_exchange_strong(s, LockForWrite);
}
void abort_write() { _state = Unused; }
void unlock_write() { state s = Ready; _state.store(s); }
};
class Ring
{
std::vector<Element> _elems;
std::atomic<int64_t> _read, _write;
public:
Ring(size_t capacity)
: _elems(capacity), _read(0), _write(0) {}
Element * get_for_read() {
auto i = _read.load();
Element * ret = &_elems[ i % _elems.size() ];
if (ret->lock_for_read()) {
// if success, the object belongs to the caller thread as reader
if (_read.compare_exchange_strong(i, i + 1))
return ret;
// Woops, reading out of order.
ret->abort_read();
}
return NULL;
}
Element * get_for_write() {
auto i = _write.load();
Element * ret = &_elems[ i % _elems.size() ];
if (ret->lock_for_write()) {
// if success, the object belongs to the caller thread as writer
if (_write.compare_exchange_strong(i, i + 1))
return ret;
// Woops, writing out of order.
ret->abort_write();
}
return NULL;
}
void release_read(Element* e) { e->unlock_read();}
void release_write(Element* e) { e->unlock_write();}
};