С критической секцией++ с таймаутом


Примечание: моя реализация основана на codeproject статья Владислава Гельфер.

На основе валдокас кодами, я переписал критической секции класса. Разница в том, что моя реализация комплексной рекурсивный(reentrantable) замок в одной критической класс раздел. (Кстати, валдока предусматривает 2 занятия: одно без рекурсии и других с использованием рекурсии.)

Я просто хочу убедиться, что моя реализация-это все-таки правильный и нетронутыми.

Пожалуйста, не пытайтесь утверждать, что внедрение бессмысленно использовать критические секции Win32. Я думаю, что эта реализация имеет много преимуществ по многим причинам. И одна из причин мне нужно, чтобы дать try_enter() функция для всех версий Windows.

#pragma once

#include <windows.h>
#include <intrin.h>
#pragma intrinsic(_WriteBarrier)
#pragma intrinsic(_ReadWriteBarrier)

class critical_section
{
private:
    struct cpu_type
    {
        enum type
        {
            unknown,
            single,
            multiple
        };
    };

public:
    critical_section(u32 spin_count=0)
        : m_semaphore(null)
        , m_thread_id(0)
        , m_wait_count(0)
        , m_spin_count(0)
        , m_recur_count(0)
    {
        // determine the cpu type
        if( m_cpu_type == cpu_type::unknown )
        {
            SYSTEM_INFO sys_info;
            ::GetSystemInfo(&sys_info);         

            m_cpu_type = (sys_info.dwNumberOfProcessors > 1)?cpu_type::multiple:cpu_type::single;
        }

        // set the spin count.
        set_spin_count(spin_count);
    }

    ~critical_section()
    {
        if(m_semaphore != null)
        {
            CloseHandle(m_semaphore);
            m_semaphore = null;
        }

        ::memset(this, 0, sizeof(*this));
    }

    void set_spin_count(u32 count)
    {
        // on single core, there should be no spinning at all.
        if(m_cpu_type == cpu_type::multiple)
        {
            m_spin_count = count;
        }
    }

public:
    bool enter(u32 timeout=INFINITE)
    {
        u32 cur_thread_id = ::GetCurrentThreadId();

        if(cur_thread_id == m_thread_id)
        {
            // already owned by the current thread.
            m_recur_count++;
        }
        else
        {
            if((!m_thread_id && lock_immediate(cur_thread_id))
                || (timeout && lock_internal(cur_thread_id, timeout)))
            {
                // successfully locked!
                m_recur_count = 1;
            }
            else
            {
                // failed to lock!
                return false;
            }
        }

        return true;
    }

    bool try_enter()
    {
        return enter(0);
    }

    void leave()
    {
        assert(m_recur_count > 0);
        if(--m_recur_count == 0)
        {
            unlock_internal();
        }
    }

    inline bool is_acquired() const
    {
        return (::GetCurrentThreadId() == m_thread_id);
    }

private:
    inline bool lock_immediate(u32 thread_id)
    {
        // return true only if m_thread_id was 0 (and, at the same time, replaced by thread_id).
        return (_InterlockedCompareExchange(reinterpret_cast<long volatile*>(&m_thread_id), thread_id, 0) == 0);
    }

    bool lock_kernel(u32 thread_id, u32 timeout)
    {
        bool waiter = false;

        for(u32 ticks=GetTickCount();;)
        {
            if(!waiter) _InterlockedIncrement(reinterpret_cast<long volatile*>(&m_wait_count));

            // try locking once again before going to kernel-mode.
            if(lock_immediate(thread_id)) return true;

            u32 wait;
            if(timeout==INFINITE)
            {
                wait = INFINITE;
            }
            else
            {
                // update the remaining time-out.
                wait = GetTickCount()-ticks;
                if(timeout<=wait) return false; // timed-out
                wait = timeout-wait;
            }

            // go kernel
            assert(m_semaphore!=null);
            switch(WaitForSingleObject(m_semaphore, wait))
            {
            case WAIT_OBJECT_0:
                // got a change!
                waiter = false;
                break;
            case WAIT_TIMEOUT:
                // timed-out.
                // but, there's one more change in the upper section of the loop.
                waiter = true;
                break;
            default:
                assert(false);
            }
        }
    }

    bool lock_internal(u32 thread_id, u32 timeout)
    {
        // try spinning and locking
        for(u32 spin=0; spin<m_spin_count; spin++)
        {
            if(lock_immediate(thread_id)) return true;

            // give chance to other waiting threads.
            // on single-core, it does nothing.
            YieldProcessor();
        }

        // prepare semaphore object for kernel-mode waiting.
        allocate_semaphore();

        bool locked = lock_kernel(thread_id, timeout);
        _InterlockedDecrement(reinterpret_cast<long volatile*>(&m_wait_count));

        return locked;
    }

    void unlock_internal()
    {
        // changes done to the shared resource are committed.
        _WriteBarrier();

        // reset owner thread id.
        m_thread_id = 0;

        // critical section is now released.
        _ReadWriteBarrier();

        // if there are waiting threads:
        if(m_wait_count > 0)
        {
            _InterlockedDecrement(reinterpret_cast<long volatile*>(&m_wait_count));

            // wake up one of them by incrementing semaphore count by 1.
            assert(m_semaphore);
            ReleaseSemaphore(m_semaphore, 1, null);
        }
    }

    void allocate_semaphore()
    {
        if(m_semaphore==null)
        {
            // create a semaphore object.
            HANDLE semaphore = CreateSemaphore(null, 0, 0x7FFFFFFF, null);
            assert(semaphore!=null);

            // try assign it to m_semaphore.
            if(InterlockedCompareExchangePointer(&m_semaphore, semaphore, null)!=null)
            {
                // other thread already created and assigned the semaphore.
                CloseHandle(semaphore);
            }
        }
    }

private:
    // prevent copying
    critical_section(const critical_section&);
    void operator=(const critical_section&);

private:
    // type of cpu: single-core or multiple-core
    static cpu_type::type m_cpu_type;

    // owner thread's id
    volatile u32 m_thread_id;
    // number of waiting threads
    volatile u32 m_wait_count;
    // spinning count
    volatile u32 m_spin_count;

    // recursion(reentrance) count
    s32 m_recur_count;

    // semaphore for kernel-mode wait
    volatile HANDLE m_semaphore;
};

Не беспокойтесь о статический член. критическую секцию::m_cpu_type определяется и инициализируется неизвестных в других .файл cpp.



3257
7
задан 13 апреля 2011 в 02:04 Источник Поделиться
Комментарии
1 ответ

Чтобы быть честным, это довольно сложно проверить такую вещь работает всухую код.

Я бы взял исходный код и дизайн некоторых тестовых случаев, которые показывают, что это работает.
Запустить их и без кода.

Затем конструкция некоторых тестовых случаев, которые не из-за проблемы вы нашли, или, возможно, не достаточно хорошо работают без усиления.

Запускать эти тестовые сценарии на новый код (все из них не только самые новые) и вуаля вы знаете, если ваш код работает.

4
ответ дан 30 мая 2012 в 10:05 Источник Поделиться