考虑以下(人为的)记忆竞技场(池):
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;
}
}
在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
根据之前的尝试和Steve Jessop的宝贵建议,我将简单地递增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