Модераторы: Daevaorn
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Thread-safe, или как безопасно работать с потоками, мьютексы, критические секции и т.п. 
:(
    Опции темы
mr.DUDA
Дата 27.8.2003, 11:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


3D-маньяк
****


Профиль
Группа: Экс. модератор
Сообщений: 8244
Регистрация: 27.7.2003
Где: город-герой Минск

Репутация: 25
Всего: 232



Народ, кто-нибудь уже делал многопоточные приложения ?

Нужно написать класс типа:
Код
class CShareable
{
   public:
       enum {WR, RD} m_CurrState;
       HANDLE hMutex;

       bool   TryLock(int newState, int nWaitMillisecs=0);
       bool   Unlock();

       CShareable()  {hMutex=0; m_CurrState=RD;}
       ~CShareable()  {Unlock(); if(hMutex) CloseHandle(hMutex);}
};

bool  CShareable::TryLock(int newState, int nWaitMillisecs)
{
       if(newState==RD && m_CurrState==RD) return true;
       if(!hMutex)
       {
               hMutex = CreateMutex(0, true, 0);
               if(!hMutex) return false;
       }
       else
       {
               if(WaitForSingleObject(hMutex, nWaitMillisecs) != WAIT_OBJECT_0)
                       return false;
       }
       m_CurrState = newState;
       return true;
}

void CShareable::Unlock()
{
       if(ReleaseMutex(hMutex))
               m_CurrState = RD;
}


т.е. так, чтобы объекты классов, производных от CShareable можно было бы разделять между двумя и более потоками, например:

Код
class  CSomeClass: public CShareable {/* здесь что угодно */};

// функция потока, например запись в объект некоторых данных:
DWORD WINAPI Thread1Func(LPVOID lParam)
{
       CSomeClass *pObj = (CSomeClass *)lParam;
       if(pObj->TryLock(WR, 5000))
       {
               // (пишем в объект данные)

               // ...

               // освобождаем объект для записи другим потоком:
               pObj->Unlock();
       }
}

void  main()
{
       CSomeClass  obj;
       // запускаем 2 потока
       CreateThread(0, 0, &Thread1Func, (LPVOID)&obj, 0, 0);
       CreateThread(0, 0, &Thread1Func, (LPVOID)&obj, 0, 0);
       // ... ждем их завершения
}


ЗЫ, может как-то можно сделать без мьютексов ? Или с критическими секциями (работают быстрее, чем мьютексы) ??

Это сообщение отредактировал(а) mr.DUDA - 27.8.2003, 12:07


--------------------
user posted image
PM MAIL WWW   Вверх
Fantasist
Дата 29.8.2003, 19:31 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Лентяй
***


Профиль
Группа: Участник Клуба
Сообщений: 1517
Регистрация: 24.3.2002

Репутация: 4
Всего: 41



Обычно используются критические секции. Правда Lock с тайм-аутом не очень удобно делать. Однако можно замутить что-то типа:

Код

class CShareable
{
  public:
      enum {WR, RD} m_CurrState;
      CRITICAL_SECTION cs;

      bool   TryLock(int newState, int nWaitMillisecs=0);
      bool   Unlock();

      CShareable()  {InitializeCriticalSection(&cs); m_CurrState=RD;}
      ~CShareable()  {Unlock(); DeleteCriticalSection(&cs);}
};

bool  CShareable::TryLock(int newState, int nWaitMillisecs)
{

      int wait=nWaitMillisecs;
      while (wait>=0&&TryEnterCriticalSection(&cs) )
      {
          sleep(10)
          wait-=10;
      }

      if (wait<=0) return false;
         
      m_CurrState = newState;
      return true;
}

void CShareable::Unlock()
{
      if(LeaveCriticalSection(&cs))
              m_CurrState = RD;
}



Mutex'ы делать не удобно. Для каждего экземляра класса надо создать уникальный mutex, а значит уникальное имя. Опять же, создавать mutex при каждом Lock'e как-то не красиво и не удобно. И в Unlock его надо удалять, а не Release'ать.

Однако наиболее интересна концепция Lock'ирующих указателей. Типа:

Код


template <class T>
class lock_ptr
{
private:
  T* mRes;
  CRITICAL_SECTION cs;
  HANDLE mOwner;
public:
  lock_ptr(T* Res): mRes(Res), mOwner(0); {InitializeCriticalSection(&cs);};
  ~lock_ptr()  {Unlock(); DeleteCriticalSection(&cs);}

 
 void Lock()
 {
    HANDLE tmp=GetCurrentThread();
    if (tmp==mOwner) return;
 
    EnterCriticalSection(&cs);
    mOwner= tmp;
 }

 void Unlock() {LeaveCriticalSection(&cs); mOwner=0;}
 
 T* operator->()
 {
    if (mOwner==0)  throw "Not locked!";
    return mRes;
 }
 
 
 bool Lock(int nWaitMillisecs)
 {
    HANDLE tmp=GetCurrentThread();
    if (tmp=mOwner) return true;
 
    while (nWaitMillisecs>=0&&TryEnterCriticalSection(&cs) )
    {
        sleep(10)
        nWaitMillisecs-=10;
    }

    if (nWaitMillisecs<=0) return false;
    mOwner= tmp;
    return true;
 }

}


Оператор-> следит за тем, чтобы к объекту не обращались бы не заблокировав его вначале. Если не хочеться терять эффективность, то проверку можно окружить #ifdef _DEBUG ... #endif

Применять просто:
Код

{
  C obj;
  lock_ptr<C> p_obj(&obj);
 
  p_obj.Lock();

  p_obj->DoMethod1();
  p_obj->DoMethod2();

  p_obj.Unlock();
}


Можно еще добавить operator T*. Тогда в этом указателе можно будет блокировать не только экземпляры классов, а вообще любые переменные.


--------------------
Волны гасят ветер...
PM MAIL   Вверх
mr.DUDA
Дата 29.8.2003, 23:36 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


3D-маньяк
****


Профиль
Группа: Экс. модератор
Сообщений: 8244
Регистрация: 27.7.2003
Где: город-герой Минск

Репутация: 25
Всего: 232



Fantasist, спасибо за отличную идею с lock_ptr !!!

Пару вопросов (м.б. глупых):
1) а нельзя ли делать WaitForSingleObject для критических секций (внутри CRITICAL_SECTION есть интересная переменная LockSemaphore):
Код
typedef struct _RTL_CRITICAL_SECTION {
   PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
   LONG LockCount;
   LONG RecursionCount;
   HANDLE OwningThread;
   HANDLE LockSemaphore; // <----------------------------------
   DWORD SpinCount;
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

2) почему:
Цитата
Mutex'ы делать не удобно. Для каждего экземляра класса надо создать уникальный mutex, а значит уникальное имя.

если не задать имя для объекта WinAPI при его создании (а передать NULL), то мьютекс будет без имени, но тоже в единственном экземпляре.

Это сообщение отредактировал(а) mr.DUDA - 29.8.2003, 23:37


--------------------
user posted image
PM MAIL WWW   Вверх
Fantasist
Дата 2.9.2003, 06:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Лентяй
***


Профиль
Группа: Участник Клуба
Сообщений: 1517
Регистрация: 24.3.2002

Репутация: 4
Всего: 41



Цитата
1) а нельзя ли делать WaitForSingleObject для критических секций (внутри CRITICAL_SECTION есть интересная переменная LockSemaphore):


Не знаю. Никогда не пробывал. Попробуй - нам расскажешь.

Цитата
если не задать имя для объекта WinAPI при его создании (а передать NULL), то мьютекс будет без имени, но тоже в единственном экземпляре.


Да это я ошибся. Думал по привычной схеме: синхронизация потоков через ownership handle'а объекта синхронизации с помощью явных функций захвата владения и освобождения (критические секции), а mutex'ы используем как глобальный именованный синхронизатор. Для меня mutex'ы без имени были бесполезны. Поэтому твою схему сразу и не понял.


--------------------
Волны гасят ветер...
PM MAIL   Вверх
Ars
Дата 3.9.2003, 10:43 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 325
Регистрация: 28.8.2003
Где: Московская обл.

Репутация: 2
Всего: 2



Весьма удобно использовать следующее:
Код

class TCritSection
{
public:
   CRITICAL_SECTION CritSect;
   TCritSection(){InitializeCriticalSection(&CritSect);};
   ~TCritSection(){DeleteCriticalSection(&CritSect);};
};


class TSyncOp
{
private:
   LPCRITICAL_SECTION CritSect;
   void Enter(LPCRITICAL_SECTION ACritSect)
   {
       CritSect=ACritSect;
       EnterCriticalSection(CritSect);
   };
public:
   TSyncOp(TCritSection *ACritSection){Enter(&ACritSection->CritSect);};
   TSyncOp(LPCRITICAL_SECTION ACritSect){Enter(ACritSect);};
   ~TSyncOp(){LeaveCriticalSection(CritSect);};
};


Позволяет использовать несколько CriticalSection и не париться с обработкой exception:

Код

class MyClass
{
   TCritSection MyCritSect1,MyCritSect2;
   MyMethod()
   {
       // Do something
       {
           TSyncOp MySynchronization(&MyCritSect1);
           // Do threadsafe operations
       }
       TSyncOp MySynchronization(&MyCritSect2);
       // Do another threadsafe operations
   };
};



--------------------
Есть многое на свете, друг Горацио,
С чем очень долго можно прое..!
PM MAIL   Вверх
mr.DUDA
Дата 3.9.2003, 12:07 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


3D-маньяк
****


Профиль
Группа: Экс. модератор
Сообщений: 8244
Регистрация: 27.7.2003
Где: город-герой Минск

Репутация: 25
Всего: 232



У меня есть несколько вопросов, Ars и Fantasist:
1) сколько таких объектов можно создавать (если в каждом - хотя бы по одной критической секции) с точки зрения WinAPI, т.е. не запарится ли винда держать в памяти тысячу проинициализированных критических секций (если учесть, что под каждую выделяется не просто кусок памяти, а еще и семафор - объект ядра)

2) как долго выполняется InitializeCriticalSection/DeleteCriticalSection, и не уйдет ли всё процессорное время только на операции с крит. секциями (если ставить на все объекты, используемые в программе, защиту потоков). В моей проге возможен одновременный доступ нескольких потоков к любому объекту (а их может существовать от сотни до тысячи - как мелких, так и толстых)

3) чем круче критическая секция обычного мьютекса (если в крит. секции также используются объекты ядра - семафоры, и ожидающие функции - WaitForSingleObject), тем более что в критическую секцию можно попробовать войти, но нельзя дождаться её освобождения (выхода др. потока из неё) в течение определенного интервала.

ЗЫ, Fantasist, можно конечно
Код
while (wait>=0&&TryEnterCriticalSection(&cs) )
{
   sleep(10)
   wait-=10;
}

и так делать, но не проще ли мьютекс+WaitForSingleObject ?

Или есть какая-то другая причина для использования только критических секций ? Нужно определиться, что именно выгоднее в данной ситуации (когда критично время выполнения и корректность работы с кучей объектов).

ЗЫ(2), я пробовал с семафором внутри CRITICAL_SECTION - что-то не получается. Винда как-то хитро его использует.

Это сообщение отредактировал(а) mr.DUDA - 3.9.2003, 12:11


--------------------
user posted image
PM MAIL WWW   Вверх
Ars
Дата 3.9.2003, 13:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 325
Регистрация: 28.8.2003
Где: Московская обл.

Репутация: 2
Всего: 2



1. Думаю, ограничения могут быть связаны только с максимальным количеством системных объектов, поэтому пока создаются - можно создавать... smile.gif
2. В проге чаще выполняются не InitializeCriticalSection/DeleteCriticalSection, а EnterCriticalSection/LeaveCriticalSection (т.к. все объекты должны использовать одни и те же CriticalSection). У потока при ожидании накладные расходы минимальны.
3. В хелпе написано:
Цитата
Critical section objects provide synchronization similar to that provided by mutex objects, except that critical section objects can be used only by the threads of a single process. Event, mutex, and semaphore objects can also be used in a single-process application, but critical section objects provide a slightly faster, more efficient mechanism for mutual-exclusion synchronization.

К тому же, в critical section реализован механизм предотвращения deadlock'ов при повторном входе потока в одну и ту же critical section. Может так и у mutex'ов? Не проверял.
А Билли Гейтс запретил лазить в CRITICAL_SECTION. Говорит, что она only для системного использования smile.gif

P.S. IMHO для работы с большим количеством объектов лучше не перегружать объекты операциями синхронизации, а сделать менеджер объектов, который возьмет на себя эти функции.

Это сообщение отредактировал(а) Ars - 3.9.2003, 14:10


--------------------
Есть многое на свете, друг Горацио,
С чем очень долго можно прое..!
PM MAIL   Вверх
Ars
Дата 3.9.2003, 16:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 325
Регистрация: 28.8.2003
Где: Московская обл.

Репутация: 2
Всего: 2



mr.DUDA, прошу прощения. Из-за узости мышления не совсем въехал в суть и допустил несколько неточностей... sad.gif

Для решения твоей задачи надо разобраться с несколькими вопросами:
1. Что необходимо синхронизировать - доступ к многочисленным объектам или к данным этих объектов?
2. Как долго выполняется код между вызовами Lock и Unlock?

Некоторые соображения:
Если необходимо синхронизировать доступ к объектам или их данным и обращения к ним носят характер чтения-записи (без длительной обработки), то с этим вполне может справиться менеджер объектов, используя только один синхро-объект по схеме:
а) залочить список объектов;
б) создать или удалить объект, прочитать или записать его данные;
в) разлочить список объектов.
Если есть длительная обработка, то может ее можно вынести в отдельный поток со всеми данными для нее?
Ну а если все до кучи - видимо без синхро-объекта в каждом экземпляре класса не обойтись, но я с такими несчастными случаями не сталкивался.

Вообще, во многих случаях можно обойтись следующим кодом.
Грубо, но ни разу не глючило. smile.gif
Код

class
{
   BYTE IsSomeThreadInside;
   MyMethod()
   {
       while(IsSomeThreadInside) Sleep(10);
       IsSomeThreadInside=1;
       // Do pseudothreadsafe operations
       IsSomeThreadInside=0;
   };
};



--------------------
Есть многое на свете, друг Горацио,
С чем очень долго можно прое..!
PM MAIL   Вверх
Fantasist
Дата 3.9.2003, 18:31 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Лентяй
***


Профиль
Группа: Участник Клуба
Сообщений: 1517
Регистрация: 24.3.2002

Репутация: 4
Всего: 41



Цитата
В моей проге возможен одновременный доступ нескольких потоков к любому объекту (а их может существовать от сотни до тысячи - как мелких, так и толстых)


В этом случае полезно подумать, а нельзя ли разбить объекты на функционально связанные группы и блокировать сразу группами. Типа транзакции.

Цитата
Может так и у mutex'ов? Не проверял


Конечно, с mutex'ами также. С какой-то стороны они даже безопаснее, ибо в reference есть такое упоминание:

Цитата

If a thread calls LeaveCriticalSection when it does not have ownership of the specified critical section object, an error occurs that may cause another thread using EnterCriticalSection to wait indefinitely.


Про mutex'ов такого нет. Конечно, такая ситуация может возникнуть только в запутанной системе управления синхронизацией. На практике я не видел ничего близкого к подобной ситуации. И тем не менее.


--------------------
Волны гасят ветер...
PM MAIL   Вверх
mr.DUDA
Дата 3.9.2003, 21:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


3D-маньяк
****


Профиль
Группа: Экс. модератор
Сообщений: 8244
Регистрация: 27.7.2003
Где: город-герой Минск

Репутация: 25
Всего: 232



to Ars:
1) к данным любого объекта м.б. предоставлен доступ на чтение и запись, из любого потока
2) объекты упорядочены по иерархии (каждый объект имеет 2 списка дочерних объектов)
3) объектов м.б. очень много, и структура м.б. очень сложной
4) обработка данных объекта может занять определенное время (если например используются файловые или оконные функции для сохранения/чтения или отображения объекта)

имхо, можно было бы сделать внутри каждого потока свою CRITICAL_SECTION, и в каждом объекте хранить указатель на CRITICAL_SECTION потока-владельца (тогда любой другой поток сначала подождет полного освобождения критической секции, прежде чем писать или читать объект). Только недостаток этого подхода в том, что если скажем мы заблокировали одним потоком доступ сначала к объекту X, а потом - к объекту Y, то другой поток не сможет получить доступа к объекту X, пока не будет полностью освобождена критическая секция (т.е. нужно освободить доступ как к X, так и к Y).

С другой стороны, хранить внутри каждого объекта мьютекс или критическую секцию невыгодно.

Менеджер объектов даст только лишнюю головную боль.



--------------------
user posted image
PM MAIL WWW   Вверх
Ars
Дата 4.9.2003, 10:51 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 325
Регистрация: 28.8.2003
Где: Московская обл.

Репутация: 2
Всего: 2



mr.DUDA, да-а-а!..

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

Не совсем понял идею
Цитата
сделать внутри каждого потока свою CRITICAL_SECTION

CRITICAL_SECTION - это как раз то, что у них должно быть общее. Как они будут подменять указатель на CRITICAL_SECTION в объекте? Что будет делать поток-владелец, когда его CRITICAL_SECTION поимеют другие (потоки), т.е. как он вернет ее себе?

И вот еще. Стремно делать CRITICAL_SECTION (или другой синхрообъект с бесконечным ожиданием) в каждом объекте, если планируется залочивать более одного объекта одновременно, т.к. легко словить взаимную блокировку потоков.


--------------------
Есть многое на свете, друг Горацио,
С чем очень долго можно прое..!
PM MAIL   Вверх
mr.DUDA
Дата 4.9.2003, 17:03 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


3D-маньяк
****


Профиль
Группа: Экс. модератор
Сообщений: 8244
Регистрация: 27.7.2003
Где: город-герой Минск

Репутация: 25
Всего: 232



А если забить на многопотоковую модель и сделать 1 поток для выполнения списка действий ? А еще можно сделать 2-й поток, извлекающий из очереди сообщения одно за другим и посылающий их второму потоку-исполнителю; в случае "подвисания" второго потока, первый после таймаута перезапустит его.

Так можно снять вообще проблему разделения объектов несколькими потоками.

Суть решаемой проблемы - сделать возможной обработку т.н. "сообщений" (задаваемых числовым ID'ом и списком параметров) с "завёрткой" в тело сообщения указателей на объекты из общего для всех потоков дерева объектов. Для расширяемого (с пом. модулей) приложения нужно выбрать способ реакции внутри каждого модуля на "приходящее" в очередь сообщение: либо делать внутри каждого DLL свой поток обработки сообщений, либо делать внутри "главного модуля" (EXE-шки) один единственный поток (или 2, описанных выше), и при загрузке модуля регистрировать сообщения данного модуля так, чтобы при поступлении сообщения MSG_A вызывалась ф-ция из модуля MOD_A.

Дерево объектов общее, т.к. собственно решается задача совместной работы множества подключаемых модулей над одним объектом-"документом".



--------------------
user posted image
PM MAIL WWW   Вверх
Ars
Дата 15.9.2003, 11:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 325
Регистрация: 28.8.2003
Где: Московская обл.

Репутация: 2
Всего: 2



mr.DUDA, если это еще актуально, скинь мне на личку пересказ ТЗ в части описания пользователей (и/или внешних систем), их количества и их требований к скорости доступа к объектам. Подумаю на досуге...


--------------------
Есть многое на свете, друг Горацио,
С чем очень долго можно прое..!
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "С++:Общие вопросы"
Earnest Daevaorn

Добро пожаловать!

  • Черновик стандарта C++ (за октябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика(4.4мб).
  • Черновик стандарта C (за сентябрь 2005) можно скачать с этого сайта. Прямая ссылка на файл черновика (3.4мб).
  • Прежде чем задать вопрос, прочтите это и/или это!
  • Здесь хранится весь мировой запас ссылок на документы, связанные с C++ :)
  • Не брезгуйте пользоваться тегами [code=cpp][/code].
  • Пожалуйста, не просите написать за вас программы в этом разделе - для этого существует "Центр Помощи".
  • C++ FAQ

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Earnest Daevaorn

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.0604 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.