Сообщений 7    Оценка 407        Оценить  
Система Orphus

Многопоточность с немногопоточными типами? Это просто !

Автор: Олег Зубарьков
Опубликовано: 13.10.2001
Исправлено: 23.05.2005
Версия текста: 1.0

Исходный код (класс GuardedT ) - 2 Кб
Примеры использования ("многопоточные" cout и vector) - 4 Кб

STL и многие другие библиотеки написаны с учётом эффективности и удобства, но без учёта многопоточности процессов ( и это правильно ), но как же использовать эту кучу кода в многопоточных приложениях без написания эквивалентной кучи кода? Чтобы решить эту проблему, я написал простой класс (заголовочный файл "mthread.h")

GuardedT<"класс, доступ к объекту которого должен быть синхронизирован", 
         "класс блокировки">

позволяющий использовать немногопоточные типы в многопоточной среде:

///////////////////////////////////////////////////////////////////////////////////////////////
template <class T, class G>
class GuardedT {
public:

    typedef T value_type; // что защищаем
    typedef G guard_type; // чем защищаем
    typedef void (*destroy_type)( T*, G* ); // тип функции очистки
        // вы можете описать свою функцию ( не член ) и передать её конструктору

    // наиболее часто встречающиеся ( у меня :) варианты
    static void deleteAll( T* t, G* g ) { delete t; delete g; }
    static void deleteOnlyGuard( T*, G* g ) { delete g; }
    static void deleteOnlyValue( T* t, G* ) { delete t; }
    static void deleteNothing( T*, G* ) { }
    static void deleteArrayValueAndGuard( T* t, G* g ) { delete[] t; delete g; }

    // указатели позволяют вызывать конструкторы с любыми параметрами
    // GuardedT<deque<int>,CritSect> gints( new deque<int>(...), new CritSect(...) );
    GuardedT( T* t, G* g, destroy_type destr = GuardedT<T,G>::deleteAll ) : mT(*t), mG(*g), mDestroy(destr) { }

    // просто очищаем с помощью переданной функции очистки
    ~GuardedT() { mDestroy( &mT, &mG ); }

    // эти функции позволяют заблокировать доступ к защищаемому типу
    // пока, скажем, мы пробегаем по нему итератором ( если это какой-нибудь контейнер )
    void lock() { mG.lock(); }
    void unlock() { mG.unlock(); }

    // этот класс позволяет блокировать только одну операцию над защищаемым типом
    // например вывод в cout или push_back для vector
    // блокировка разблокируется даже если операция вызовет исключение
    class GL {
    public:

        ~GL() { mG.unlock(); }
        T* operator->() { return &mT; } 
        T& operator*() { return mT; } // этот нужен чтобы можно было вызывать operator .. (...)
            // через предыдущий такие операторы не вызвать ( разве тока ->operator[](...) - бред )

    private:
        T& mT;
        G& mG;

        // запрещаем копирование
        GL& operator=( const GL& );

        friend class GuardedT<T,G>;
        GL( T& t, G& g ) : mT(t), mG(g) { mG.lock(); }
    }; // class GuardedT<T,G>::GL

    // две следующих функции блокируют доступ на время вызова какой-либо функции-члена защищаемого объекта
    GL operator->() { return GL( mT, mG ); }
    GL operator*() { return GL( mT, mG ); }

    // а эти можно использовать для незащищённого доступа
    // например, до их вызова мы заблокировались с помощью lock()
    T& value() { return mT; }
    G& guard() { return mG; }

private:
    T& mT;
    G& mG;
    destroy_type mDestroy; 

    // запрещаем копирование
    GuardedT( const GuardedT& gt );
    GuardedT& operator=( const GuardedT& );
}; // class GuardedT<T,G>
///////////////////////////////////////////////////////////////////////////////////////////////

Также в этом заголовочном файле находятся классы Mutex, CritSect, Semaphore и Event. Они написаны мной для использования в одном приложении, поэтому защиту ( SECURITY_ATTRIBUTES ) и доступ к этим объектам из других процессов я не реализовывал. Вы можете написать любые блокировочные типы и использовать их с GuardedT<...>, которому важно лишь существование функций-членов lock() и unlock(). А можно написать класс с пустыми lock() и unlock()... ну ладно, извращайтесь сами.

Важные особенности

Примеры использования

Примеры использования смотри в файлах
test.cpp - показывает, как вообще работать с этими классами,
test_ostream.cpp - показывает, что происходит при несинхронизированном выводе через cout и
test_vector.cpp - показывает, что происходит при несинхронизированном доступе к vector<int>
батники ( *.bat ) - для компиляции.

От автора

Вы имеете право на адвоката... тьфу ты, вы можете использовать и переделывать этот код (под другие типы исключений, под другие стандарты именования, например, всё начинается с заглавной буквы, и т.п.) неограниченно, но указывайте у кого взяли код (да-да блокировочные классы не очень, но здесь я ставлю на GuardedT<...>) (я ни у кого такого хорошего кода ( да и похуже также ) не видел, а идея синхронизации возникла вместе с идеей о многопоточности).

НИ НА ЧЬЁ АВТОРСТВО Я НЕ ПОКУШАЮСЬ. ИДЕЮ И КЛАСС GuardedT<...> Я ПРИДУМАЛ И НАПИСАЛ САМ, ВОЗМОЖНО, ЧТО ПОДОБНОЕ УЖЕ КТО-ТО ПРИДУМАЛ И НАПИСАЛ ДО МЕНЯ, И КТО-ТО ПРИДУМАЕТ И НАПИШЕТ ПОЗЖЕ. НУ ЧТО Ж, ИНОГДА У ЛЮДЕЙ МОГУТ ВОЗНИКНУТЬ ОДНИ И ТЕ ЖЕ МЫСЛИ КАК И, ТИПА, "ПОРА ПОЖРАТЬ" И В СТОЛОВОЙ ОЧЕРЕДЬ ДО ДРУГОЙ СТОЛОВОЙ И ВСЁ ТАКОЕ :)

ЮЗАЙТЕ НА ЗДОРОВЬЕ


Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав.
    Сообщений 7    Оценка 407        Оценить