C++ atomic "сравнить и установить на ноль или приращение"
Рассмотрим следующую (надуманную) арену памяти (пул):
template<typename T>
class Arena {
public:
Arena(size_t size)
: m_buffer(new char[size * sizeof(T)]),
m_next_available(0),
m_size(size) { }
void* placement() {
return m_buffer.get() + (m_next_available++) * sizeof(T);
}
private:
std::unique_ptr<char> m_buffer;
std::atomic<size_t> m_next_available;
size_t m_size;
};
Как видите, он использует атомарную переменную m_next_available
отслеживать следующий доступный блок памяти.
Когда запрашивается новый блок памяти, Arena
экземпляр должен предоставить указатель на соответствующий блок (как указано) и получить следующий доступный; вот где у меня проблемы.
Я хотел бы атомарной операции, способной выразить следующее: если следующий доступный блок больше, чем размер арены, то он должен быть установлен на ноль (я буду перезаписывать на местах памяти).
Для справки, неатомарная версия того же Arena
представлен ниже. Обратите внимание, как, когда я иду мимо пяти элементов (размер Arena
), адрес нового элемента - это адрес, соответствующий первому блоку (как и ожидалось).
#include<cstddef>
#include<iostream>
#include<memory>
template<typename T>
class Arena {
public:
Arena(size_t size)
: m_buffer(new char[size * sizeof(T)]),
m_next_available(0),
m_size(size) { }
void* placement() {
// this is the logic that I'd like to make atomic
if(m_next_available == m_size) {
m_next_available = 0;
}
return m_buffer.get() + (m_next_available++) * sizeof(T);
}
private:
std::unique_ptr<char> m_buffer;
size_t m_next_available;
size_t m_size;
};
template<typename T>
void* operator new(size_t sz, Arena<T>& a) {
(void)sz; // to avoid "warning: unused variable sz"
return a.placement();
}
int main() {
Arena<double> a(5);
double x;
while(std::cin>>x) {
double *data = new(a) double(x);
std::cout<<"address of new item: "<<data<<std::endl;
}
}
Скомпилировано с GCC 4.8.1 на OS X 10.7.4 (g++ example.cpp -std=c++11
)
1
address of new item: 0x7fb48b4008a0
2
address of new item: 0x7fb48b4008a8
3
address of new item: 0x7fb48b4008b0
4
address of new item: 0x7fb48b4008b8
5
address of new item: 0x7fb48b4008c0
1
address of new item: 0x7fb48b4008a0 # this is the same as the first one
Редактировать:
Согласно предыдущим попыткам и ценным предложениям Стива Джессопа, я просто увеличу атомарно m_next_available
счетчик и по модулю m_size
результирующее число для получения цикла. Если вам интересно, приведенный ниже код работает.
#include<cstddef>
#include<iostream>
#include<memory>
#include<atomic>
#include<thread>
#include<vector>
template<typename T>
class Arena {
public:
Arena(size_t size)
: m_buffer(new char[size * sizeof(T)]),
m_next_available(0),
m_size(size) { }
void* placement() {
return m_buffer.get() + (m_next_available++ % m_size) * sizeof(T);
}
size_t allocations() const {
return m_next_available;
}
void peek() const {
// print whatever you can
for(size_t k=0; k<m_size; k++) {
std::cout<<(*reinterpret_cast<double*>(m_buffer.get() + k * sizeof(T)))
<<" ";
}
std::cout<<std::endl;
}
private:
std::unique_ptr<char[]> m_buffer;
std::atomic<size_t> m_next_available;
size_t m_size;
};
template<typename T>
void* operator new(size_t sz, Arena<T>& a) {
(void)sz; // to avoid "warning: unused variable sz"
return a.placement();
}
Arena<double> arena(10);
std::atomic<bool> continue_printing;
struct Worker {
void operator()() const {
for(size_t k=0; k<10000; k++) {
new(arena) double(k);
}
}
};
int main() {
continue_printing = true;
std::thread t([](){ while(continue_printing) arena.peek(); });
t.detach();
std::vector<std::thread> threads;
for(size_t k=0; k<100; k++) {
threads.emplace_back(Worker());
}
for(auto & thread : threads) {
thread.join();
}
continue_printing = false;
std::cout<<"all threads finished"<<std::endl
<<"final population in the arena: "<<std::endl;
arena.peek();
std::cout<<"Number of elements that requested allocation: "
<<arena.allocations()<<std::endl;
}
Выход:
$ ./a.out
0 9 57 949 90 371 144 976 132 384
876 679 600 926 610 948 622 589 632 1480
4553 4580 4499 4592 4597 4518 7512 6344 4546 6362
7597 4595 4659 7626 4616 6459 6470 6480 4689 7676
4666 6544 7738 6562 7755 7766 6582 6593 6604 4727
[----- snip ----- snip ----- snip -----]
9409 9925 9934 9446 9956 9966 9977 9490 9508 9549
9720 9811 9892 9953 9994 9995 9996 9997 9998 9999
9990 9991 9992 9993 9994 9995 9996 9997 9998 9999
9990 9991 9992 9993 9994 9995 9996 9997 9998 9999
all threads finished
final population in the arena:
9990 9991 9992 9993 9994 9995 9996 9997 9998 9999
Number of elements that requested allocation: 1000000