Are the final templated functions allocate and deallocate thread safe? The Cache object is a thread_local and that also makes FreeList a thread_local, the only parts that I'm still in doubt are the calls to std::malloc and std::free. Are they always thread safe or that depends on the implementation? Is it better to put a lock on them?
I think that the cached memory doesn't need locks since all the objects here are thread_local and each thread has a local copy of it. The only problem really are std::malloc and std::free, are they already synchronized?
Here's a self contained example of all of my code:
#include <iostream>
class FreeList {
struct Node {
Node * next;
};
Node * this_head;
public:
static constexpr auto const NODE_SIZE = sizeof(Node);
FreeList() noexcept
: this_head() {
}
FreeList(FreeList &&) = delete;
FreeList(FreeList const &) = delete;
FreeList & operator =(FreeList &&) = delete;
FreeList & operator =(FreeList const &) = delete;
void push(void * const address) noexcept {
auto const head = static_cast<Node *>(address);
head->next = this_head;
this_head = head;
}
auto pop() noexcept {
void * const head = this_head;
if (head) {
this_head = this_head->next;
}
return head;
}
};
template <uint64_t const SIZE>
class Cache {
static_assert(SIZE >= FreeList::NODE_SIZE);
FreeList this_free_list;
public:
Cache() noexcept
: this_free_list() {
}
Cache(Cache &&) = delete;
Cache(Cache const &) = delete;
~Cache() noexcept {
while (auto const address = this_free_list.pop()) {
// Do I need a lock here?
std::free(address);
}
}
Cache & operator =(Cache &&) = delete;
Cache & operator =(Cache const &) = delete;
auto allocate() {
if (auto const address = this_free_list.pop()) {
return address;
}
// Do I need a lock here?
if (auto const address = std::malloc(SIZE)) {
return address;
}
throw std::bad_alloc();
}
void deallocate(void * const address) noexcept {
if (address) {
this_free_list.push(address);
}
}
};
template <typename TYPE>
auto & get() noexcept {
thread_local TYPE local;
return local;
}
template <typename TYPE>
auto & get_cache() noexcept {
return get<Cache<sizeof(TYPE)>>();
}
// Are these thread safe?
template <typename TYPE>
auto allocate() {
auto const address = get_cache<TYPE>().allocate();
return static_cast<TYPE *>(address);
}
template <typename TYPE>
void deallocate(void * const address) noexcept {
get_cache<TYPE>().deallocate(address);
}
int main() {
auto x = allocate<int64_t>();
*x = 5;
std::cout << *x << '\n';
deallocate<int64_t>(x);
}