На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
Обратите внимание:
1. Прежде чем начать новую тему или отправить сообщение, убедитесь, что вы не нарушаете правил форума!
2. Обязательно воспользуйтесь поиском. Возможно, Ваш вопрос уже обсуждали. Полезные ссылки приведены ниже.
3. Темы с просьбой выполнить какую-либо работу за автора в этом разделе не обсуждаются.
4. Используйте теги [ code=cpp ] ...текст программы... [ /code ] для выделения текста программы подсветкой.
5. Помните, здесь телепатов нет. Старайтесь формулировать свой вопрос максимально грамотно и чётко: Как правильно задавать вопросы
6. Запрещено отвечать в темы месячной и более давности без веских на то причин.

Полезные ссылки:
user posted image FAQ Сайта (C++) user posted image FAQ Форума user posted image Наши Исходники user posted image Поиск по Разделу user posted image MSDN Library Online (Windows Driver Kit) user posted image Google

Ваше мнение о модераторах: user posted image B.V.
Модераторы: B.V.
  
> Критические секции , крэш
    Всем привет!
    Не могу понять – где косяк в этом коде?
    Запускаю, выводится только:
    ExpandedWrap disabled
      THREADS ARE CREATED AND RUNNING !!!
      PRESS A KEY TO PAUSE 'READ' THREAD...
      Write function thread...
    И затем крэш.
    Прикол в том, что если закомментить EnterCriticalSection и LeaveCriticalSection, всё работает чётко...

    ExpandedWrap disabled
      #include <stdio.h>
      #include <windows.h>
      #include <conio.h>
       
      #define CRITICAL_SECTIONS               // нужны ли критические секции (закомментируйте эту строку, если секции не нужны)
      #define THREAD_COUNT 2                  // кол-во потоков
      #define FINISH_THREADS_WAIT_TIME 5000           // время ожидания завершения потоков по умолчанию (миллисекунд)
       
      enum ThreadType { READ_THREAD, WRITE_THREAD };      // тип потока (читающий, записывающий), должно соответствовать кол-ву THREAD_COUNT
      enum ThreadStatus { THREAD_STOPPED, THREAD_RUNNING, THREAD_PAUSED };    // типы статусов работы потоков (остановлен, работает, на паузе)
       
      // Структура с информацией о потоках
      struct ThreadInfo {
        ThreadStatus Status[THREAD_COUNT];            // статусы работы потоков
        HANDLE hThread[THREAD_COUNT];             // хендлы потоков
        HANDLE hEvent[THREAD_COUNT];              // хендлы событий о паузах
      #ifdef CRITICAL_SECTIONS
        CRITICAL_SECTION CriticalSection;         // объект критической секции
      #endif
      };
       
      ThreadInfo ioThreads;                   // информация о потоках
       
      void ReadProc();
      void WriteProc();
       
      void (*ioThreadProc[THREAD_COUNT])() = {ReadProc, WriteProc};   // Указатели на функции, выполняемые в потоке (вызываются из главной функции потока)
       
      ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
       
      // Главная функция потока
      // lpParam - номер потока (от 0 до THREAD_COUNT-1)
      DWORD WINAPI MainThreadProc(LPVOID lpParam)
      {
        do {
          // Обработка паузы
          while (ioThreads.Status[(int)lpParam] == THREAD_PAUSED) {
            if (ioThreads.hEvent[(int)lpParam]) {
              WaitForSingleObject(ioThreads.hEvent[(int)lpParam], INFINITE);
            } else {
              Sleep(1);
            }
          };
       
          // Выполнение основной процедуры цикла
          ioThreadProc[(int)lpParam]();
       
        } while (ioThreads.Status[(int)lpParam] != THREAD_STOPPED);
        ExitThread(0);
      }
       
      // Атомарное (безопасное в плане межпотокового взаимодействия) сравнение var с expected и запись в var значения desired в случае совпадения
      // Возвращает TRUE, если запись произошла (т.е. var был равен expected); FALSE, если var не равен expected
      bool AtomicCompareAndExchange(int* var, int expected, int desired)
      {
        return __atomic_compare_exchange_n(var, &expected, desired, FALSE, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST);
      }
       
      // Создание потоков и событий
      // Возвращает кол-во успешно созданных потоков (должно быть THREAD_COUNT)
      int CreateThreads()
      {
        ZeroMemory(&ioThreads, sizeof(ioThreads));
        int total = 0;
        for (int i=0; i<THREAD_COUNT; ++i) {
          ioThreads.hThread[i] = CreateThread(NULL, 0, &MainThreadProc, LPVOID(i), 0, NULL);
          if (ioThreads.hThread[i]) {
            ioThreads.hEvent[i] = CreateEvent(NULL, FALSE, TRUE, NULL);
            ioThreads.Status[i] = THREAD_RUNNING;
            total++;
          }
        }
      #ifdef CRITICAL_SECTIONS
        InitializeCriticalSection(&ioThreads.CriticalSection);
      #endif
        return total;
      }
       
      // Поставить поток tt на паузу
      void PauseThread(ThreadType tt)
      {
        if (AtomicCompareAndExchange((int*)&ioThreads.Status[tt], THREAD_RUNNING, THREAD_PAUSED) and ioThreads.hEvent[tt]) {
          ResetEvent(ioThreads.hEvent[tt]);
        }
      }
       
      // Продолжить работу потока tt
      void ContinueThread(ThreadType tt)
      {
        if (AtomicCompareAndExchange((int*)&ioThreads.Status[tt], THREAD_PAUSED, THREAD_RUNNING) and ioThreads.hEvent[tt]) {
          SetEvent(ioThreads.hEvent[tt]);
        }
      }
       
      // Завершить работу потока tt (вернее, просигнализировать фукнции MainThreadProc, что нужно завершать работу)
      void StopThread(ThreadType tt)
      {
        ioThreads.Status[tt] = THREAD_STOPPED;
      }
       
      // Завершить потоки (сняв их с паузы, если они на паузе)
      // Максимальное время ожидания задаётся в Wait (по умолчанию FINISH_THREADS_WAIT_TIME секунд)
      // Force - нужно ли уничтожать потоки, если время ожидания истекло (!!!опасно!!!)
      // Возвращает TRUE, если дождался; FALSE, если нет
      bool FinishThreads(int Wait = FINISH_THREADS_WAIT_TIME, bool Force = FALSE)
      {
        for (int i=0; i<THREAD_COUNT; ++i) {
          StopThread(ThreadType(i));
        }
        int result = WaitForMultipleObjects(THREAD_COUNT, ioThreads.hThread, TRUE, Wait); // ждём завершения потоков
        for (int i=0; i<THREAD_COUNT; ++i) {
          if (ioThreads.hThread[i]) {
            if (Force && (result != WAIT_OBJECT_0)) TerminateThread(ioThreads.hThread[i], 0);
            CloseHandle(ioThreads.hThread[i]);
          }
          if (ioThreads.hEvent[i]) CloseHandle(ioThreads.hEvent[i]);
        }
      #ifdef CRITICAL_SECTIONS
        DeleteCriticalSection(&ioThreads.CriticalSection);
      #endif
        ZeroMemory(&ioThreads, sizeof(ioThreads));
        return (result == WAIT_OBJECT_0);
      }
       
      // Завершить потоки (предварительно сняв с паузы), уничтожив их, если время ожидания Wait истекло
      // !!! Используйте эту функцию с осторожностью, только в том случае, если иного выхода нет, поскольку если
      // какой-либо из потоков не завершился в течение таймаута Wait, скорее всего связано с ошибкой в работе потоков !!!
      bool ForceFinishThreads(int Wait = FINISH_THREADS_WAIT_TIME)
      {
        FinishThreads(Wait, TRUE);
      }
       
      #ifdef CRITICAL_SECTIONS
      // Начало критического кода потока
      void StartCriticalCode()
      {
        EnterCriticalSection(&ioThreads.CriticalSection);
      }
       
      // Окончание критического кода потока
      void EndCriticalCode()
      {
        LeaveCriticalSection(&ioThreads.CriticalSection);
      }
      #endif
       
      ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
       
      // Функция чтения
      void ReadProc()
      {
        StartCriticalCode();
        printf("Read function thread...\n");
        EndCriticalCode();
        Sleep(300);
      }
       
      // Функция чтения
      void WriteProc()
      {
        StartCriticalCode();
        printf("Write function thread...\n");
        EndCriticalCode();
        Sleep(700);
      }
       
      ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
       
      void pause()
      {
        int c = getch();
        if (!c || c == 224) getch();
      }
       
      int main()
      {
        // Создание и запуск потоков
        if (CreateThreads() != THREAD_COUNT) {
          printf("ERROR CREATING THREADS :(\n");
          ForceFinishThreads();               // на всякий случай, вдруг только один создался
          pause();
          return 1;
        };
       
        printf("THREADS ARE CREATED AND RUNNING !!!\n");
        printf("PRESS A KEY TO PAUSE 'READ' THREAD...\n");
        pause();
        PauseThread(READ_THREAD);
       
        printf("PRESS A KEY TO PAUSE 'WRITE' THREAD...\n");
        pause();
        PauseThread(WRITE_THREAD);
       
        printf("PRESS A KEY TO CONTINUE 'READ' THREAD...\n");
        pause();
        ContinueThread(READ_THREAD);
       
        printf("PRESS A KEY TO CONTINUE 'WRITE' THREAD...\n");
        pause();
        ContinueThread(WRITE_THREAD);
       
        printf("PRESS A KEY TO FINISH...\n");
        pause();
       
        if (ForceFinishThreads()) {
          printf("THREADS ARE FINISHED SUCCESSFULLY! :)\n");
        } else {
          printf("ERROR STOPPING THREADS :(\n");
        }
        printf("PRESS A KEY TO QUIT...\n");
        pause();
       
        return 0;
      }
      Ну, как минимум не следует делать им инициализацию после запуска рабочих ниток.
        Цитата Qraizer @
        Ну, как минимум не следует делать им инициализацию после запуска рабочих ниток.

        Там и события тоже после запуска создаются.

        Цитата Jin X @
        Не могу понять – где косяк в этом коде?


        Как только ты вызываешь CreateThread, у тебя запускается новый поток. Его код будет исполнен раньше, чем вызовется следующая строчка в программе.
        Соответственно все объекты, которые используются в функции MainThreadProc, должны быть созданы и проинициализированы до этого вызова.
        Сообщение отредактировано: Олег М -
          Цитата Qraizer @
          Ну, как минимум не следует делать им инициализацию после запуска рабочих ниток.
          Блин, точно ж! Прогон :wall:

          Цитата Олег М @
          Там и события тоже после запуска создаются.
          А события там и не используются раньше, чем будет вызвано PauseThread.

          Добавлено
          Теперь всё ок, работает :)
            Цитата Jin X @
            Цитата Qraizer @
            Ну, как минимум не следует делать им инициализацию после запуска рабочих ниток.
            Блин, точно ж! :wall:

            Но если очень хочется сначала создать поток, а потом всё остальное,
            создай его с параметром CREATE_SUSPENDED (5-й параметр).
            Когда всё будет готово, запустишь поток посредством "ResumeThread".
              Цитата Jin X @
              А события там и не используются раньше, чем будет вызвано PauseThread.
              Хотя во избежание косяков в будущем, лучше сделать заранее (вернее, даже CREATE_SUSPENDED, а потом ResumeThread)

              Добавлено
              ЫукпШ, :D
                Цитата Jin X @
                Хотя во избежание косяков в будущем, лучше сделать заранее (вернее, даже CREATE_SUSPENDED, а потом ResumeThread)

                Я лично плохо представляю для чего может понадобиться создавать поток с CREATE_SUSPENDED.

                Кстати в твоём случае эти статусы вообще не нужны. Достаточно только события и булевской переменной volatile bool Stop, даже не atomic. Хотя не очень понятно, что ты пытаешься сделать.
                  Можно без статусов, но решил сделать так.
                  Чуть меньше накладных расходов (на вызов WaitForSingleObject) + лишняя подстраховка на случай ошибки создания ивента (хотя я понимаю, что это кране маловероятная ситуация).
                  Код получился больше и сложнее, но мне так нравится.

                  А CREATE_SUSPENDED – чтобы не создавать ивенты лишний раз, если вдруг возникла ошибка создания потока (тоже маловероятно, но всё же предусмотреть это стоит).
                    Цитата Олег М @
                    Цитата Jin X @
                    Хотя во избежание косяков в будущем, лучше сделать заранее (вернее, даже CREATE_SUSPENDED, а потом ResumeThread)

                    Я лично плохо представляю для чего может понадобиться создавать поток с CREATE_SUSPENDED.

                    Поскольку такая возможность существует, значит у разработчиков были основания.
                    -----
                    Пожалуйста, пример:
                    1. Делаем много-процессное приложение.
                    2. Главный процесс может запустить несколько дочерних процессов.
                    Он делает какую-то предварительную работу - например готовит файл.
                    (Снимок экрана). Далее взводит событие, и запущенный дочерний процесс
                    просыпается своим потоком и отправляет на сервер приготовленный файл.
                    3. Хотелось бы все эти мероприятия решить в общем виде, оформить классом
                    и сложить в библиотеку.
                    4. Вопрос - какое имя будет у объекта синхронизации ? (Например, эвента).
                    ---
                    Делаем так - придумаем любое имя. Сообщим его дочернему процессу через командную строку.
                    Дочерний процесс добавит к имени свой идентификатор в текстовом виде.
                    Чтобы для разных дочерних процессов имена не совпадали.
                    Но как его узнать родителю, чтобы создать объект синхронизации ?
                    Используем CREATE_SUSPENDED. Процесс создан, (но его поток ещё не запущен).
                    После этого ID дочернего у родителя есть.
                    Теперь родитель делает эвент и запускает поток дочернего процесса.
                    Ребёнок получает доступ к эвенту, засыпает на нём и ждёт команды...
                    -----
                    Ещё пример:
                    1. Пишу некую программу-лоадер, которая запустит другую программу.
                    2. С флагом CREATE_SUSPENDED.
                    3. Процесс в памяти, не запущен, дочерний.
                    4. Это значит, что я могу корректировать коды дочернего процесса как захочу.
                    5. Программа-лоадер производит коррекции кодов дочернего процесса и запускает его.
                    6. После коррекции дочерний процесс ведёт себя иначе, чем планировал разработчик..
                    Сообщение отредактировано: ЫукпШ -
                      Как-то неубедительно. Всё это скорее "о! я придумал, где это можно применить", чем "о да, это клёвый сценарий". ID к примеру, легко заменяются инкрементируемыми для каждого потомка int-ами. Патчить код проще, прикинувшись отладчиком, всё равно и для того, и для другого нужны высокие привилегии.
                      Я вот тоже чуть не написал, что если нужно создать мульён потоков, но застартить их единомоментно, то CREATE_SUSPENDED подойдёт. Нет, не подойдёт, будить придётся всё равно в цикле. А правильно – сделать эвент "Go!Go!Go!", нехай после старта ждут, а опосля взвести его одной функцией.

                      Добавлено
                      И выходом командовать глобальным булевым признаком как-то некрасиво. Делаем опять же эвент, нехай его ждут WaitForMultiple..., всё равно нитки ждут операционных событий, от ещё одного эвента не убудет, и в нужный момент Set ему.
                      Сообщение отредактировано: Qraizer -
                        Цитата Qraizer @
                        И выходом командовать глобальным булевым признаком как-то некрасиво.
                        Ну а что плохого? То, что глобальная? А если в класс завернуть? :)
                        Но в целом решение с Multiple'ом нормальное :)
                          Цитата Jin X @
                          Можно без статусов, но решил сделать так.
                          Чуть меньше накладных расходов (на вызов WaitForSingleObject) + лишняя подстраховка на случай ошибки создания ивента (хотя я понимаю, что это кране маловероятная ситуация).
                          Код получился больше и сложнее, но мне так нравится.

                          Что-то не вижу у тебя этой подстраховки. Да и накладных расходов у тебя там получается на порядок больше. Думаешь atomic-операции они бесплатные?

                          Добавлено
                          Цитата ЫукпШ @
                          Поскольку такая возможность существует, значит у разработчиков были основания.


                          Думаю, основание там было одно - раз это сделать легко, значит надо сделать, авось потом кто-нибудь придумает зачем.

                          Цитата ЫукпШ @
                          Теперь родитель делает эвент и запускает поток дочернего процесса.


                          А как запустить поток в другом процессе?
                            Цитата Олег М @
                            А как запустить поток в другом процессе?

                            Всё так же.
                            Если я создаю дочерний процесс (с флагом CREATE_SUSPENDED),
                            то я могу запустить главный поток посредством "ResumeThread".
                            Поскольку хэндл потока у меня будет после создания.
                            ---
                            Останавливать/запускать можно любой поток, надо иметь хэндл ("рукоятка, чтобы порулить")
                            и права доступа.
                            По отношению к дочернему процессу имеются достаточные права доступа,
                            с ним можно сделать практически всё-что-угодно.
                            ---
                            Если ты хочешь "RemoteThread", то это сложнее. В Сети есть хорошие статьи на эту тему.
                            Сообщение отредактировано: ЫукпШ -
                              Цитата Олег М @
                              Что-то не вижу у тебя этой подстраховки.
                              Например:
                              ExpandedWrap disabled
                                      if (ioThreads.hEvent[(int)lpParam]) {
                                        WaitForSingleObject(ioThreads.hEvent[(int)lpParam], INFINITE);
                                      } else {
                                        Sleep(1);
                                      }

                              Цитата Олег М @
                              Да и накладных расходов у тебя там получается на порядок больше. Думаешь atomic-операции они бесплатные?
                              А где ты видишь в цикле atomic-операции?

                              Добавлено
                              А избавиться от atomic, если уж на то пошло, можно разделив стоп-флаг и флаг паузы на 2 части.
                              Но тут особого смысла нет, т.к. пауза используется редко.
                              Хотя было бы чуть проще в реализации, конечно.
                                Цитата Jin X @
                                Например:

                                      if (ioThreads.hEvent[(int)lpParam]) {
                                        WaitForSingleObject(ioThreads.hEvent[(int)lpParam], INFINITE);
                                      } else {


                                Здесь лучше проверять, что возвращает WaitForSingleObject. А ещё лучше, если CreateEvent вернул ошибку, вообще не запускать поток.
                                  CREATE_SUSPENDED активно используется в Delphi (TThread, например), т.к. при создании объекта создаётся и поток, поэтому присвоить значения полям объекта не представляется возможным иначе, как создавая поток остановленным. Однако, полагаю, что если бы не было возможности создавать поток приостановленным на старте, то это решалось бы другими способами.

                                  Однако у меня есть иная мысль, зачем CREATE_SUSPENDED может быть нужен.
                                  1. Если нужно либо создать и запустить ВСЕ потоки, либо ни одного.
                                  2. Если потоки активно взаимодействуют друг с другом через хендлы/id друг друга (и до запуска любого из них должен быть готовый массив хендлов всех потоков).
                                  3.1. Как в моём случае, если для потока нужно создать какие-либо другие объекты, которые нет смысла создавать при неудачном создании потока, но при удачном создании потока можно обойтись и без этих объектов. Особенно, если так не один Event, а ещё куча всего.
                                  3.2. Похожая ситуация: если действие, которое выполняется при успешном создании потока – это не просто создание ивента, которое можно откатить, а что-то более серьёзное (удаление файла, передеча по сети и пр), что нельзя так просто откатить назад при неудачном создании потока.

                                  Добавлено
                                  Цитата Олег М @
                                  Здесь лучше проверять, что возвращает WaitForSingleObject.
                                  Чем это лучше-то?

                                  Цитата Олег М @
                                  А ещё лучше, если CreateEvent вернул ошибку, вообще не запускать поток.
                                  Почему это?

                                  Добавлено
                                  Про CREATE_SUSPENDED:
                                  4. Если нужно сразу применить какие-либо свойства потоку, например, установить нужный приоритет выполнения, affinity mask.
                                    Цитата Qraizer @
                                    Как-то неубедительно. Всё это скорее "о! я придумал, где это можно применить", чем "о да, это клёвый сценарий". ID к примеру, легко заменяются инкрементируемыми для каждого потомка int-ами.

                                    Это не очень удачное предложение.
                                    Оно предполагает, что имеется только один процесс-родитель, который создаёт процессы-потомки.
                                    Допустим, имеется несколько независимых процессов-родителей, которые создают некоторое
                                    количество процессов-потомков. И какое значение int-а будет правильным ?
                                    Всегда правильное значение - это идентификатор процесса-потомка, он имеется у системы его я и добываю.

                                    Добавлено
                                    Цитата Jin X @
                                    Добавлено
                                    Цитата Олег М @
                                    Здесь лучше проверять, что возвращает WaitForSingleObject.
                                    Чем это лучше-то?

                                    Например, в результате чего-то хэндл оказался недействительным, Wait..xx вернул ошибку (никакого ожидания не будет),
                                    ничего не может работать правильно. В этом случае поток никогда не останавливается
                                    и потребляет время максимальным образом. Возможно, приложение придётся снимать внешними утилитами.
                                    Сообщение отредактировано: ЫукпШ -
                                      Цитата Jin X @
                                      А ещё лучше, если CreateEvent вернул ошибку, вообще не запускать поток.
                                      Почему это?


                                      Потому что, как правило, события нужны для выполнения каких-то осмысленных действий и, если объект не создался, то и запускать поток незачем.
                                        Цитата Олег М @
                                        Потому что, как правило, события нужны для выполнения каких-то осмысленных действий и,

                                        Нынешняя тема хорошо пересекается с недавней темой о передаче данных между потоками.
                                        Привожу 3-й пример, объясняющий кроме "CREATE_SUSPENDED" для чего нужен семафор.
                                        ---
                                        Делаем 2 потока (приёмник и передатчик данных) и очередь для передачи данных между ними.
                                        Очередь потокобезопасна, и все 3 сущности представляют собой
                                        экземпляры классов.
                                        1. Для обеспечения передачи данных передадим указатель на очередь каждому объекту-потоку.
                                        Уже понятно, что лучше сначала передать указатель, а потом запускать потоки.
                                        Поскольку одновременно при создании экземпляров классов это сделать нельзя.
                                        2. Хочу, чтобы при занесении данных в очередь автоматически генерировалось
                                        событие потоку-приёмнику. По этому событию он проснётся и начнёт работу.
                                        3. Для этого опишем класс-интерфейс RiseEvent c методом Rise_Event.
                                        4. Класс потока-приёмника унаследуем от класса-интерфейса.
                                        У класса-потока-приёмника появится метод Rise_Event.
                                        5. Классу-очереди передадим указатель на экземпляр класса-приёмника, который
                                        преобразуем в тип указателя на интерфейс RiseEvent.

                                        Теперь класс-очередь уже не просто очередь. Получился потокобезопасный коммуникационный объект.
                                        ---
                                        Итак, экземпляру класса потока-приёмника передадим указатель на очередь.
                                        А экземпляру класса-очереди нужен указатель на экземпляр класса-потока-приёмника.
                                        Это не очень удобно делать, когда поток уже запущен.
                                        ---
                                        Класс - передатчик получает данные, обрабатывает и помещает в коммуникационный
                                        объект. Что дальше он вообще не знает и это прекрасно ! Но он получает данные
                                        (допустим, из Сети) n [байт]/m [секунд]. От разных задержек и проблем "где-то там"
                                        может получиться и 5*n [байт]/5*m [секунд] сразу. Возможна вот такая пиковая нагрузка.
                                        Поток тупо соберёт данные и разместит 5 порций в очередь, которая возбудит 5 событий.
                                        При этом поток-приёмник не успеет обработать все сразу по мере размещения в очереди.
                                        Значит, надо использовать объект синхронизации, обеспечивающий
                                        многократное взведение/гашение. Это семафор.
                                        Скрытый текст

                                        Использовать эвент тоже можно. Для этого нужно сделать семафор из эвента программно.
                                          Цитата Jin X @
                                          Однако у меня есть иная мысль, зачем CREATE_SUSPENDED может быть нужен.
                                          ...
                                          Всё это не актуально. Просто любой CREATE_SUSPENDED легко заменяется на WaitFor...() при старте. Ну и в принципе SuspendThread()/ResumeThread() читерская штука, запросто могущая провоцировать дидлуки. Несложно придумать подобный сценарий и с CREATE_SUSPENDED.
                                          Цитата Jin X @
                                          2. Если потоки активно взаимодействуют друг с другом через хендлы/id друг друга
                                          Меня смущают алгоритмы, где потоки взаимодействуют друг с другом посредством явных обращений. Зачем? Это снижает гибкость архитектуры. Всегда выгоднее коммуницировать объектами синхронизации... О! Я знаю! PostThreadMessage() же! Вот где без ID нитки не обойтись. Правда, я её никогда не пользовал <_< , незачем было.
                                          Цитата ЫукпШ @
                                          Допустим, имеется несколько независимых процессов-родителей, которые создают некоторое
                                          количество процессов-потомков. И какое значение int-а будет правильным ?
                                          Нужен глобальный счётчик? Ото ты знаешь, как его оформить. Ну или в простейшем случае бери семафор. А вообще, коммуникация между родителями добавит гемора и сама по себе, так что внедрить туда ещё и счётчик на фоне этого гемора уже не будет казаться проблемой.
                                          Ну и в заключение. Нехрен вообще возиться со счётчиками. Создаём наследуемые неименованные объекты и передаём значения их хендлов в коммандных строках.
                                            Цитата ЫукпШ @
                                            Привожу 3-й пример, объясняющий кроме "CREATE_SUSPENDED" для чего нужен семафор.

                                            Не очень понял, как этот пример объясняет необходимость CREATE_SUSPENDED.

                                            Цитата ЫукпШ @
                                            3. Для этого опишем класс-интерфейс RiseEvent c методом Rise_Event.
                                            4. Класс потока-приёмника унаследуем от класса-интерфейса.
                                            У класса-потока-приёмника появится метод Rise_Event.
                                            5. Классу-очереди передадим указатель на экземпляр класса-приёмника, который
                                            преобразуем в тип указателя на интерфейс RiseEvent.


                                            Зачем очереди нужна ссылка на обработчик?
                                            Обычно для очереди делается событие, которое взводится при добавлении элемента. Обработчик потом просто ждёт это событие.
                                              Цитата ЫукпШ @
                                              Например, в результате чего-то хэндл оказался недействительным, Wait..xx вернул ошибку (никакого ожидания не будет),
                                              ничего не может работать правильно. В этом случае поток никогда не останавливается
                                              и потребляет время максимальным образом.
                                              Так, там же проверка статуса ещё идёт... хотя можно сделать проверку Wait'а и делать Sleep(1), чтобы не жрал в случае чего. Но с чего вдруг хендл станет недействительным?

                                              Цитата Олег М @
                                              Потому что, как правило, события нужны для выполнения каких-то осмысленных действий и, если объект не создался, то и запускать поток незачем.
                                              Ну это сомнительная логика. Вот к примеру, как работает та же функция EnterCriticalSection. Если секция уже захвачена и спины прокручены, она создаёт Event (если он ещё не создан). Но в случае ошибки используется NtWaitForKeyedEvent. По твоей логике Event можно было бы создавать уже в InitializeCriticalSection и сказать "ой извините, не получилось", если вдруг Event создать не получилось.
                                                Цитата Jin X @
                                                Ну это сомнительная логика. Вот к примеру, как работает та же функция EnterCriticalSection. Если секция уже захвачена и спины прокручены, она создаёт Event. Но в случае ошибки используется NtWaitForKeyedEvent. По твоей логике Event можно было бы создать уже в InitializeCriticalSection и сказать "ой извините, не получилось", если вдруг Event создать не получилось.

                                                Ну да, создавать именно там и надо, в InitializeCriticalSection. Ошибку при этом можно не выдавать.
                                                  Цитата Олег М @
                                                  Ну да, создавать именно там и надо, в InitializeCriticalSection. Ошибку при этом можно не выдавать.
                                                  А я думаю, что это сделано намеренно, чтобы не создавать лишних объектов, которые могут не понадобиться (и в 99% случаев не надобятся).
                                                    Цитата Jin X @
                                                    А я думаю, что это сделано намеренно, чтобы не создавать лишних объектов, которые могут не понадобиться (и в 99% случаев не надобятся).

                                                    В случае с критической секцией как раз наоборот - в 99% случаев понадобится.
                                                    Да и вообще, лучше узнать о возможных проблемах сразу при создании объекта, чем когда-то потом.
                                                    Что-то я сомневаюсь, что NtWaitForKeyedEvent работает эффективнее, чем просто событие.
                                                      Цитата Олег М @
                                                      В случае с критической секцией как раз наоборот - в 99% случаев понадобится.
                                                      Не думаю. Сильно зависит от задачи. Но что-то мне подсказывает, что в большинстве задач чаще нет, чем да.
                                                        Тут следует учесть, что назначением критических секций является служить лёгкой заменой мьютексам, которые, будучи объектами ядра, довольно тяжелы. Зачастую программистам требуется защитить малые и быстрые участки кода от параллельного исполнения, в которых ожидается в среднем малая вероятность коллизий, но таких участков может быть очень много разных, и они все заключены в одном приложении, что не требует межпроцессного осблуживания. Типичный пример – стандартные библиотечные функции, которые живут в языке с 70-ых годов прошлого века и манипулируют глобальными объектами и признаками, что в то время являлось нормальным, однако плохо совместимым в многопоточном окружении. Сейчас их уже не переделаешь, они глубоко завязли в Стандарте языка. Естественно, защищать от коллизий всякие там malloc(), printf(), asctime(), strtok() итп нужно, иначе будет хаос, но коллизии для них ожидаются очень редкими, поэтому защищая их объектами ядра, можно ожидать крайне большой потери производительности. К тому же, много объектов ядра, по одному на каждую функцию, сильно утяжелит требования приложений к ресурсам ОС, в особенности, если такие требования будет предъявлять большое число приложений.
                                                        Критические секции спроектированы как лёгкие объекты синхронизации. При отсутствии коллизий они требуют лишь пары десятков ассемблерных инструкций, исполняемых к тому же целиком в режиме пользователя, без переключения в ядро, а требуемые ими ресурсы ограничиваются ресурсами самого приложения, а не ОС. При отсутствии коллизий, особенно в многоядерных процессорах, где критические секции даже при наличии коллизий могут реализовать относительно короткие циклы ожидания через спин-блокировки, объект ядра запросто может быть так и не востребован ни разу. Увы, это же и означает, что захват критической секции может оказаться невозможным, если она ещё не имеет связанного объекта ядра, однако столкнулась с коллизией, с которой спин-блокировка не справилась, но у ОС тупо кончились ресурсы.
                                                          Цитата Qraizer @
                                                          Увы, это же и означает, что захват критической секции может оказаться невозможным, если она ещё не имеет связанного объекта ядра, однако столкнулась с коллизией, с которой спин-блокировка не справилась, но у ОС тупо кончились ресурсы.
                                                          Да почему же? Когда кончаются ресурсы, используется KeyedEvent, так что тут проблем тоже нет...

                                                          Добавлено
                                                          Глянь https://doxygen.reactos.org/d0/d06/critical_8c_source.html
                                                          В RtlpWaitForCriticalSection (который вызывается из RtlEnterCriticalSection) используется NtWaitForKeyedEvent:
                                                          ExpandedWrap disabled
                                                              150         if (CriticalSection->LockSemaphore == INVALID_HANDLE_VALUE)
                                                              151         {
                                                              152             /* Use the global keyed event (NULL as keyed event handle) */
                                                              153             Status = NtWaitForKeyedEvent(NULL,
                                                              154                                          CriticalSection,
                                                              155                                          FALSE,
                                                              156                                          &RtlpTimeout);
                                                              157         }
                                                              158         else
                                                              159         {
                                                              160             /* Wait on the Event */
                                                              161             Status = NtWaitForSingleObject(CriticalSection->LockSemaphore,
                                                              162                                            FALSE,
                                                              163                                            &RtlpTimeout);
                                                              164         }
                                                            Эти события недокументированы. Они были предназначены для единственной цели – гарантировать отсутствие отказов при использовании критических секций. Начиная с WinXP, эта гарантия имеется, до этого следовало самостоятельно ловить SEH-исключения.

                                                            Добавлено
                                                            Keyed Events
                                                            Сообщение отредактировано: Qraizer -
                                                              А зачем их документировать, если они используются внутри системы?

                                                              Цитата Qraizer @
                                                              Они были предназначены для единственной цели – гарантировать отсутствие отказов при использовании критических секций.
                                                              Ну, а ты говоришь что захват может оказаться невозможным. Ты про Win2K ?
                                                                Не, я про историю их создания. В MSDN явно говорится, что EnterCriticalSection() в доXPёвом окружении может бросить исключение в случае критически малого количества памяти, но не говорится, какого.

                                                                P.S. Однако оно упоминается в InitializeCriticalSection() в подобной же ситуации, и это STATUS_NO_MEMORY. Причём с Висты оно там уже устранено, скорее всего посредством как раз предсозданного ключевого события.
                                                                  Цитата Qraizer @
                                                                  Ну и в заключение. Нехрен вообще возиться со счётчиками.

                                                                  Вот и я об этом.
                                                                  Прежде, чем их делать, поинтересуемся ценой вопроса.
                                                                  Эта цена - 5 функциональных строк, а именно:
                                                                  1. Запуск остановленного процесса
                                                                  2. Преобразование ID в строку
                                                                  3. Сложение 2-х строк - постоянного имени + ID
                                                                  4. Создание именованного объекта синхронизации
                                                                  5. Запуск процесса
                                                                  ---
                                                                  Невыгодно связываться с чем-то ещё. Просто нет необходимости.
                                                                    По-твоему, получение hex-значения из командной строки и преобразование в HANDLE сложнее?
                                                                      Цитата Qraizer @
                                                                      По-твоему, получение hex-значения из командной строки и преобразование в HANDLE сложнее?

                                                                      Нет. Но это значение ещё где-то надо брать, в виде общего счётчика.
                                                                      А в многопоточном приложении - ещё и общей критической секции для этого счётчика.
                                                                      Нет удобства делать классы, объединённые этими статическими членами.
                                                                      я бы сделал, если бы иного пути не было.
                                                                      Именно этот вариант получается при наличии CREATE_SUSPENDED.
                                                                      Всё логично.

                                                                      Добавлено
                                                                      Цитата Олег М @
                                                                      Зачем очереди нужна ссылка на обработчик?

                                                                      я хочу, чтобы при удачном добавлении элемента в очередь,
                                                                      автоматически возбуждалось событие. Чтобы этой деятельностью
                                                                      больше никто не занимался.
                                                                        Да какая разница: командная строка или CREATE_SUSPENDED? Кому как удобнее.
                                                                        Командная строка тоже может быть забита какими-то параметрами. Тогда можно, конечно, сделать CreateFileMapping+MapViewOfFile (для передачи хендла), но проще ли это?
                                                                        Я не понимаю, почему такой негатив к CREATE_SUSPENDED... Что в нём плохого?
                                                                          Почему негатив-то? Просто ему не удаётся найти незаменимого применения, от чего от выглядит ненужным.
                                                                            Цитата Qraizer @
                                                                            Всё это не актуально. Просто любой CREATE_SUSPENDED легко заменяется на WaitFor...() при старте.
                                                                            Ты хочешь сказать, на CreateEvent + if (ошибка) + WaitForSingleObject + if (ошибка) ExitThread + висящий event (либо надо придумывать куда ещё CloseHandle засунуть, например, делать счётчик, который будет уменьшаться через atomic-инструкции)?
                                                                            Очень удобно, спору нет :)

                                                                            Добавлено
                                                                            Есть достаточно вещей, которые не являются незаменимыми, но являются более удобными, чем другие.
                                                                              А ты точно меня читал?
                                                                                Цитата Jin X @
                                                                                Да какая разница: командная строка или CREATE_SUSPENDED? Кому как удобнее.
                                                                                Командная строка тоже может быть забита какими-то параметрами. Тогда можно, конечно, сделать CreateFileMapping+MapViewOfFile (для передачи хендла), но проще ли это?
                                                                                Я не понимаю, почему такой негатив к CREATE_SUSPENDED... Что в нём плохого?


                                                                                Тот пример, который привёл ЫукпШ - это пример того, как размазать логику тонким слоем по разным модулям.
                                                                                Единственное для чего может понадобиться CREATE_SUSPENDED - это установить какие-то свойства процесса/потока до его запуска.
                                                                                Причём, в std::thread и pthreads вообще нет возможности создать поток остановленным.
                                                                                  Цитата Qraizer @
                                                                                  А ты точно меня читал?
                                                                                  Ну цитата ж твоя :)
                                                                                  А как иначе использовать WaitFor для того, чтобы запустить "дополнительные" потоки после инициализации чего-либо из "главного" потока?
                                                                                  Я говорю о необратимой инициализации типа удаления файла, передачи данных по сети. Которая не нужна в случае неудачной инициализации.

                                                                                  p.s. Кстати, я ещё забыл добавить в сценарий...
                                                                                  Цитата Jin X @
                                                                                  CreateEvent + if (ошибка) + WaitForSingleObject + if (ошибка) ExitThread + висящий event
                                                                                  обработку ситуации, когда инициализация не удалась...

                                                                                  Добавлено
                                                                                  Ну или когда нужно стартовать все потоки с idle-приоритетом.
                                                                                    Цитата Jin X @
                                                                                    А как иначе использовать WaitFor для того, чтобы запустить "дополнительные" потоки после инициализации чего-либо из "главного" потока?
                                                                                    Во-первых, зачем запускать поток, который ещё надо будет извне подкрутить? Почему бы не подкрутить до запуска? Во-вторых, если поток должен инициализироваться сам, то аварийный выход – это будет его задача, и выполняться она должна в неспящем состоянии. О чём вообще вопрос?
                                                                                    Цитата Jin X @
                                                                                    p.s. Кстати, я ещё забыл добавить в сценарий...
                                                                                    Тут и там выше я не понял, что это значит. Необходимые события создаются по-любому, и если что-то пошло не так, приложение не может функционировать дальше. Все сторонние инициализации выполняются по старту, до запуска любого потока. О чём вопрос?
                                                                                    Цитата Jin X @
                                                                                    либо надо придумывать куда ещё CloseHandle засунуть, например, делать счётчик, который будет уменьшаться через atomic-инструкции
                                                                                    Все деинициализации выполняются по выходу, после завершения любых потоков. Зачем счётчики? В чём проблема?
                                                                                      Qraizer, конкретная задача: запустить 10 потоков с idle приоритетом. Чтобы они сразу были idle. Как это сделать без SUSPEND_THREAD?
                                                                                        Пусть каждая делает себе в начале MainThreadProc(), до начала любых полезных действий. Один нормалприорити стэйтмент сложно будет пережить?

                                                                                        Добавлено
                                                                                        ExpandedWrap disabled
                                                                                          // Структура с информацией о потоках
                                                                                          struct ThreadInfo
                                                                                          {
                                                                                            HANDLE hThread[THREAD_COUNT];         // хендлы потоков
                                                                                            HANDLE evPause[THREAD_COUNT];         // хендлы событий о паузах
                                                                                          };
                                                                                           
                                                                                          ThreadInfo ioThreads;                   // информация о потоках
                                                                                          CRITICAL_SECTION CriticalSection;       // объект критической секции
                                                                                          HANDLE events[2];                       // общие события: "поступили данные для обработки" и "сигнал выхода"
                                                                                           
                                                                                          DWORD WINAPI MainThreadProc(LPVOID lpParam)
                                                                                          {
                                                                                            int idx = (int)lpParam;               // наш индекс в массивах
                                                                                           
                                                                                            SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_IDLE);
                                                                                           
                                                                                            for (;;)
                                                                                            {
                                                                                              if (WaitForSingleObject(ioThreads.evPause[idx], INFINITE) == WAIT_FAILED) break; // обработка паузы
                                                                                              if (WaitForMultipleObjects(2, events, FALSE, INFINITE)  == WAIT_OBJECT_0) break; // одно из двух событий
                                                                                           
                                                                                              // Выполнение основной процедуры цикла
                                                                                              ioThreadProc[idx]();
                                                                                            }
                                                                                            ExitThread(WaitForSingleObject(evExit, 0) == 0 ? 0 : 1);
                                                                                          }
                                                                                           
                                                                                          int CreateThreads()
                                                                                          {
                                                                                            int i;
                                                                                           
                                                                                            for (i = 0; i < THREAD_COUNT; ++i)
                                                                                            {
                                                                                              ioThreads.evPause[i] = CreateEvent (NULL, TRUE, TRUE, NULL);       // событие паузы (signaled == нет паузы)
                                                                                              // старт нитки
                                                                                              if (ioThreads.evPause[i]!= NULL)
                                                                                                ioThreads.hThread[i] = CreateThread(NULL, 0, &MainThreadProc, LPVOID(i), 0, NULL);
                                                                                              if (ioThreads.hThread[i] == NULL)   // по ошибке досрочно прекращаем
                                                                                              {
                                                                                                CloseHandle(ioThreads.evPause[i]);
                                                                                                break;
                                                                                              }
                                                                                            }
                                                                                            return i;                             // возвращаем количество запущенных ниток
                                                                                          }
                                                                                           
                                                                                          bool FinishThreads(int Wait = FINISH_THREADS_WAIT_TIME, bool Force = FALSE)
                                                                                          {
                                                                                            SetEvent(evExit);                     // всем выход
                                                                                           
                                                                                            int result = WaitForMultipleObjects(amount, ioThreads.hThread, TRUE, Wait); // ждём завершения потоков
                                                                                           
                                                                                            if (result == WAIT_TIMEOUT && Force)  // если послушались за отведённое время И заставляют
                                                                                              for (int i = 0; i < amount; ++i)
                                                                                                if (WaitForSingleObject(ioThreads.hThread[i], 0) == WAIT_TIMEOUT) // проверяем статус ...
                                                                                                  TerminateThread(ioThreads.hThread[i], 1);                       // ... и стопаем
                                                                                           
                                                                                            for (int i = 0; i < amount; ++i)
                                                                                            {
                                                                                              CloseHandle(ioThreads.evPause[i]);
                                                                                              CloseHandle(ioThreads.hThread[i]);
                                                                                            }
                                                                                           
                                                                                            return (result == WAIT_OBJECT_0);
                                                                                          }
                                                                                           
                                                                                          void ContinueThread(int idx)
                                                                                          {
                                                                                            SetEvent(ioThreads.evPause[idx]);
                                                                                          }
                                                                                           
                                                                                          void StopThread(int idx)
                                                                                          {
                                                                                            ResetEvent(ioThreads.evPause[idx]);
                                                                                          }
                                                                                           
                                                                                          int main()
                                                                                          {
                                                                                            InitializeCriticalSection(&CriticalSection);
                                                                                            evData = CreateEvent(NULL, FALSE,FALSE, NULL);
                                                                                            evExit = CreateEvent(NULL, TRUE, FALSE, NULL);
                                                                                           
                                                                                            // Создание и запуск потоков
                                                                                            if (evData == NULL || evExit == NULL) goto quit;
                                                                                            events[0] = evExit;
                                                                                            events[1] = evData;
                                                                                            amount = CreateThreads();
                                                                                           
                                                                                            /* ... */
                                                                                           
                                                                                            ForceFinishThreads();
                                                                                           
                                                                                          quit:
                                                                                            CloseHandle(evExit);
                                                                                            CloseHandle(evData);
                                                                                            DeleteCriticalSection(&CriticalSection);
                                                                                          }


                                                                                        Добавлено
                                                                                        Не проверял. Даже не компилил. :-?
                                                                                        Сообщение отредактировано: Qraizer -
                                                                                          Qraizer, ok :)
                                                                                            Архитектура немного странная. С одной стороны, нитки запускаются для выполнения каких-то действий, которые вроде бы не зависят от того, какая конкретно нитка отреагирует на какое конкретно действие, но с другой – в приложение заложена архитектура пониточного управления. В условиях полной взаимозаменяемости ниток это кажется избыточным. Либо нитки всё-таки каждая с особенностями, но тогда и передача данных на обработку должна быть направленной, либо нет необходимости паузить отдельные конкретные нитки, но возможно есть необходимость сразу все. Тогда достаточно будет одного события evPause, и код будет немного проще.

                                                                                            Добавлено
                                                                                            P.S. Подправил код. Неверно пауза обрабатывалась.

                                                                                            Добавлено
                                                                                            P.P.S. Ошибок было... не одна.
                                                                                              Qraizer, одна нитка постоянно читает какие-то данные, другая – постоянно пишет. Каждую из них отдельно можно поставить на паузу. Стартуют одновременно, завершаются тоже.
                                                                                                Тогда управляемый по evData сценарий не подходит. Но в целом, как пример сета одноранговых ниток, пример показателен.
                                                                                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                                                                                0 пользователей:


                                                                                                Рейтинг@Mail.ru
                                                                                                [ Script execution time: 0,1295 ]   [ 17 queries used ]   [ Generated: 29.03.24, 12:38 GMT ]