非阻塞线程安全内存池实现

发布于 2024-10-03 13:51:43 字数 3581 浏览 4 评论 0原文

我需要一个简单的非阻塞静态块大小内存池。我在网上没有找到这样的。所以每个人都需要这样的解决方案。这是免费的...仅适用于 Win32。

最好的问候,

弗里德里希

#ifndef MEMPOOL_HPP_INCLUDED
#define MEMPOOL_HPP_INCLUDED

#include "atomic.hpp"
#include "static_assert.hpp"

#pragma warning( push )
#pragma warning( disable : 4311 ) // warning C4311: 'Typumwandlung'

/// @brief Block-free memory-pool implemenation
/// @tparam T Object-type to be saved within the memory-pool.
/// @tparam S Capacy of the memory-pool.
template <typename T, int S>
class MemoryPool
{
private:
    STATIC_ASSERT(sizeof(int) == sizeof(void*), "Well, ...");

public:
    /// @brief Object-type saved within the pool.
    typedef T TYPE;
    enum
    {
        /// @brief Capacy of the memory-pool.
        SIZE = S
    };

private:
    /// @brief Chunks, that holds the memory
    struct Chunk
    {
        /// @brief Single-linked list.
        Chunk* Next;
        /// @brief The value
        /// We do not call the default constructor this way.
        char Value[sizeof(TYPE)];
    };

    /// @brief The root object.
    Chunk* Root;

    /// @brief The pool
    Chunk Pool[SIZE];

private:
    // do not allow copying
    MemoryPool(const MemoryPool&);
    MemoryPool& operator=(const MemoryPool&);

    void free(Chunk* c)
    {
        c->Next = Root;
        while(!CompareAndSwap((int*)&Root, (int)c->Next, (int)c))
        {
            c->Next = Root;
        }
    }

public:
    /// Default constructor
    /// Creates an empty memory-pool.
    /// Invalidates all the memory.
    MemoryPool()
    :   Root(0)
    {
        for(int i = 0; i < SIZE; i++)
        {
            MemoryPool::free(&Pool[i]);
        }
    }

    /// @brief Frees a chunk of memory, that was allocated by MemoryPool::malloc
    /// @param _Chunk A chunk of memory, that was allocated by MemoryPool::malloc
    /// This function will not call the destructor.
    /// Thread-safe, non-blocking
    void free(T* _Chunk)
    {
        if(!_Chunk)
            return;

        Chunk* c = (Chunk*)((int)(_Chunk) - sizeof(Chunk*));

        if(c < &Pool[0] || c > &Pool[SIZE - 1])
            return;

        MemoryPool::free(c);
    }

    /// @brief Returns a pointer to a chunk of memory
    /// @return 0 on a memory shortage
    /// @return A pointer to a chunk of memory
    /// This function will not call the constructor.
    /// Thread-safe, non-blocking
    T* malloc()
    {
        Chunk* r = Root;
        if(!r)
            return 0;

        while(!CompareAndSwap((int*)&Root, (int)r, (int)r->Next))
        {
            r = Root;
            if(!r)
                return 0;
        }

        return &(r->Value);
    }
};

#pragma warning( pop )

#endif // MEMPOOL_HPP_INCLUDED

和 CompareAndSwap

/// @brief Atomic compare and set
/// Atomically compare the value stored at *p with cmpval and if the
/// two values are equal, update the value of *p with newval. Returns
/// zero if the compare failed, nonzero otherwise.
/// @param p Pointer to the target
/// @param cmpval Value as we excpect it
/// @param newval New value
static inline int CompareAndSwap(volatile int *_ptr, int _old, int _new)
{
    __asm {
        mov eax, [_old]                // place the value of _old to EAX
        mov ecx, [_new]                // place the value of _new to ECX
        mov edx, [_ptr]                // place the pointer of _ptr to EDX
        lock cmpxchg [edx], ecx        // cmpxchg old (EAX) and *ptr ([EDX])
    }
    return 1;
}

I needed a simple non-blocking static-block-size memory-pool. I didn't find such on the web. So everyone, who needs such a solution. This one is free... only works on Win32.

Best regards,

Friedrich

#ifndef MEMPOOL_HPP_INCLUDED
#define MEMPOOL_HPP_INCLUDED

#include "atomic.hpp"
#include "static_assert.hpp"

#pragma warning( push )
#pragma warning( disable : 4311 ) // warning C4311: 'Typumwandlung'

/// @brief Block-free memory-pool implemenation
/// @tparam T Object-type to be saved within the memory-pool.
/// @tparam S Capacy of the memory-pool.
template <typename T, int S>
class MemoryPool
{
private:
    STATIC_ASSERT(sizeof(int) == sizeof(void*), "Well, ...");

public:
    /// @brief Object-type saved within the pool.
    typedef T TYPE;
    enum
    {
        /// @brief Capacy of the memory-pool.
        SIZE = S
    };

private:
    /// @brief Chunks, that holds the memory
    struct Chunk
    {
        /// @brief Single-linked list.
        Chunk* Next;
        /// @brief The value
        /// We do not call the default constructor this way.
        char Value[sizeof(TYPE)];
    };

    /// @brief The root object.
    Chunk* Root;

    /// @brief The pool
    Chunk Pool[SIZE];

private:
    // do not allow copying
    MemoryPool(const MemoryPool&);
    MemoryPool& operator=(const MemoryPool&);

    void free(Chunk* c)
    {
        c->Next = Root;
        while(!CompareAndSwap((int*)&Root, (int)c->Next, (int)c))
        {
            c->Next = Root;
        }
    }

public:
    /// Default constructor
    /// Creates an empty memory-pool.
    /// Invalidates all the memory.
    MemoryPool()
    :   Root(0)
    {
        for(int i = 0; i < SIZE; i++)
        {
            MemoryPool::free(&Pool[i]);
        }
    }

    /// @brief Frees a chunk of memory, that was allocated by MemoryPool::malloc
    /// @param _Chunk A chunk of memory, that was allocated by MemoryPool::malloc
    /// This function will not call the destructor.
    /// Thread-safe, non-blocking
    void free(T* _Chunk)
    {
        if(!_Chunk)
            return;

        Chunk* c = (Chunk*)((int)(_Chunk) - sizeof(Chunk*));

        if(c < &Pool[0] || c > &Pool[SIZE - 1])
            return;

        MemoryPool::free(c);
    }

    /// @brief Returns a pointer to a chunk of memory
    /// @return 0 on a memory shortage
    /// @return A pointer to a chunk of memory
    /// This function will not call the constructor.
    /// Thread-safe, non-blocking
    T* malloc()
    {
        Chunk* r = Root;
        if(!r)
            return 0;

        while(!CompareAndSwap((int*)&Root, (int)r, (int)r->Next))
        {
            r = Root;
            if(!r)
                return 0;
        }

        return &(r->Value);
    }
};

#pragma warning( pop )

#endif // MEMPOOL_HPP_INCLUDED

And the CompareAndSwap

/// @brief Atomic compare and set
/// Atomically compare the value stored at *p with cmpval and if the
/// two values are equal, update the value of *p with newval. Returns
/// zero if the compare failed, nonzero otherwise.
/// @param p Pointer to the target
/// @param cmpval Value as we excpect it
/// @param newval New value
static inline int CompareAndSwap(volatile int *_ptr, int _old, int _new)
{
    __asm {
        mov eax, [_old]                // place the value of _old to EAX
        mov ecx, [_new]                // place the value of _new to ECX
        mov edx, [_ptr]                // place the pointer of _ptr to EDX
        lock cmpxchg [edx], ecx        // cmpxchg old (EAX) and *ptr ([EDX])
    }
    return 1;
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

孤寂小茶 2024-10-10 13:51:43

这种方法的问题在于 malloc 中存在竞争条件:

while(!CompareAndSwap((int*)&Root, (int)r, (int)r->Next))

考虑以下操作序列:

  1. 最初 Root = A, A->next = B, ...
  2. 一个线程读取 r = Root,因此 r = A 并且(进入寄存器)读取 ecx = r->Next = B
  3. 初始线程被抢占(或者,在另一个 CPU 上)发生一系列 mallocfree ,使得 A 使用一段时间并最后释放。
  4. 新列表状态为 Root = A, A->next = ZZZ, ...
  5. 原始线程唤醒并执行 cmpxchg 并成功,因为 Root == r == A 并因此设置 Root = ecx = B
  6. 现在您的列表已损坏。

如果您有双字 cmpxchg,例如 cmpxchg8b,则可以解决此问题。您只需在列表头旁边添加一个序列号,这样如果您像上面 (3) 中那样被中断,比较就会失败。只要每个 malloc 都交换指针并递增序列号,free 端就可以使用窄版本。

The problem with this approach is that there is a race condition in malloc:

while(!CompareAndSwap((int*)&Root, (int)r, (int)r->Next))

Consider the following sequence of operations:

  1. Initially Root = A, A->next = B, ...
  2. One thread reads r = Root so r = A and (into a register) it reads ecx = r->Next = B
  3. Initial thread is preempted (or, on another CPU) a series of malloc and free occur such that A is used for a while and freed last.
  4. New list state is Root = A, A->next = ZZZ, ...
  5. Original thread wakes up and does cmpxchg and succeeds because Root == r == A and thus sets Root = ecx = B
  6. Now your list is corrupted.

You can solve this problem if you have a double-word cmpxchg, such as cmpxchg8b. You just include a serial number next to the list head so that if the compare fails if you are interrupted as in (3) above. The free side can use the narrow version as long as each malloc both exchanges the pointer and increments the serial number.

甜`诱少女 2024-10-10 13:51:43

感谢您的任何评论。该软件可与 WinXP 及更高版本一起使用。前面提到的实现仍然可以与 PowerPC 体系结构一起使用(如果您有 CompareAndSwap 的正确实现,请参阅“http://publib.boulder.ibm.com/infocenter/aix/v6r1/topic/com.ibm.aix”。 aixassem/doc/alangref/stwcx.htm")。

最好的问候,

弗里德里希

/// @brief Lock-free memory-pool implementation
/// @tparam T Type stored within the memory-pool
/// @tparam S Number of elements stored in the memory-pool.
template <typename T, int S>
class MemoryPool
{
public:
    /// @brief Type stored within the memory-pool.
    typedef T TYPE;
    enum
    {
        /// @brief Number of enrties in the memory-pool.
        SIZE = S
    };

private:

// we need to align the memory-pool-chunks.
#pragma pack(push, MEMORY_ALLOCATION_ALIGNMENT)

    /// @brief The memory-chunk used by the memory-pool.
    template <typename TYPE>
    struct MemoryChunk
    {
        /// @brief Next entry in the single-linked list.
        SLIST_ENTRY Next;
        /// @brief The value stored within the memory-pool.
        /// Do not call the constructor
        char Value[sizeof(TYPE)];
    };
    typedef MemoryChunk<TYPE> CHUNK_TYPE;

#pragma pack(pop, MEMORY_ALLOCATION_ALIGNMENT)

    /// @brief Head of the single-linked list.
    SLIST_HEADER Head;

    /// @brief The pool itself
    CHUNK_TYPE Pool[SIZE];

    // no copying is supported
    MemoryPool& operator=(const MemoryPool&);
    MemoryPool(const MemoryPool&);

public:
    /// @brief Constructs the memory-pool.
    MemoryPool()
    {
        InitializeSListHead(&Head);
        for(int i = 0; i < SIZE; i++)
        {
            InterlockedPushEntrySList(&Head, &Pool[i].Next);
        }
    }

    /// @brief Free the memory-pool.
    ~MemoryPool()
    {
        InterlockedFlushSList(&Head);
    }

    /// @brief Allocates a memory chunk
    /// @return 0 if none is free
    /// @return Pointer to a free memory chunk (the constructor is not called!)
    TYPE* Allocate()
    {
        CHUNK_TYPE* c = reinterpret_cast<CHUNK_TYPE*>(InterlockedPopEntrySList(&Head));
        if(c)
            return reinterpret_cast<TYPE*>(&c->Value[0]);
        else
            return 0;
    }

    /// @brief Deallocates a memory chunk (the destructor is not called)
    /// @param c Point to the memory-chunk allocated by us.
    void Deallocate(void* c)
    {
        if(c < static_cast<void*>(&Pool[0]) || c > static_cast<void*>(&Pool[SIZE]))
            return; // was not allocated by us
        char* p = static_cast<char*>(c);
        p -= sizeof(SLIST_ENTRY);
        CHUNK_TYPE* t = reinterpret_cast<CHUNK_TYPE*>(p);
        InterlockedPushEntrySList(&Head, &t->Next);
    }
};

Thank you for any comments. This one may be used with WinXP and newer. The implementation mentioned before may still be used with a PowerPC architecture (if you have a proper implementation of CompareAndSwap, see "http://publib.boulder.ibm.com/infocenter/aix/v6r1/topic/com.ibm.aix.aixassem/doc/alangref/stwcx.htm").

Best regards,

Friedrich

/// @brief Lock-free memory-pool implementation
/// @tparam T Type stored within the memory-pool
/// @tparam S Number of elements stored in the memory-pool.
template <typename T, int S>
class MemoryPool
{
public:
    /// @brief Type stored within the memory-pool.
    typedef T TYPE;
    enum
    {
        /// @brief Number of enrties in the memory-pool.
        SIZE = S
    };

private:

// we need to align the memory-pool-chunks.
#pragma pack(push, MEMORY_ALLOCATION_ALIGNMENT)

    /// @brief The memory-chunk used by the memory-pool.
    template <typename TYPE>
    struct MemoryChunk
    {
        /// @brief Next entry in the single-linked list.
        SLIST_ENTRY Next;
        /// @brief The value stored within the memory-pool.
        /// Do not call the constructor
        char Value[sizeof(TYPE)];
    };
    typedef MemoryChunk<TYPE> CHUNK_TYPE;

#pragma pack(pop, MEMORY_ALLOCATION_ALIGNMENT)

    /// @brief Head of the single-linked list.
    SLIST_HEADER Head;

    /// @brief The pool itself
    CHUNK_TYPE Pool[SIZE];

    // no copying is supported
    MemoryPool& operator=(const MemoryPool&);
    MemoryPool(const MemoryPool&);

public:
    /// @brief Constructs the memory-pool.
    MemoryPool()
    {
        InitializeSListHead(&Head);
        for(int i = 0; i < SIZE; i++)
        {
            InterlockedPushEntrySList(&Head, &Pool[i].Next);
        }
    }

    /// @brief Free the memory-pool.
    ~MemoryPool()
    {
        InterlockedFlushSList(&Head);
    }

    /// @brief Allocates a memory chunk
    /// @return 0 if none is free
    /// @return Pointer to a free memory chunk (the constructor is not called!)
    TYPE* Allocate()
    {
        CHUNK_TYPE* c = reinterpret_cast<CHUNK_TYPE*>(InterlockedPopEntrySList(&Head));
        if(c)
            return reinterpret_cast<TYPE*>(&c->Value[0]);
        else
            return 0;
    }

    /// @brief Deallocates a memory chunk (the destructor is not called)
    /// @param c Point to the memory-chunk allocated by us.
    void Deallocate(void* c)
    {
        if(c < static_cast<void*>(&Pool[0]) || c > static_cast<void*>(&Pool[SIZE]))
            return; // was not allocated by us
        char* p = static_cast<char*>(c);
        p -= sizeof(SLIST_ENTRY);
        CHUNK_TYPE* t = reinterpret_cast<CHUNK_TYPE*>(p);
        InterlockedPushEntrySList(&Head, &t->Next);
    }
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文