Имеется четыре функции для работы с критическими разделами.
Перед тем, как использовать функции для работы с критическими разделами, необходимо определить объект критический раздел, который является глобальной переменной типа CRITICAL_SECTION. Например:
- Тип данных CRITICAL_SECTION является структурой, но ее поля используются только самой Windows.
Объект критический раздел сначала должен быть инициализирован одним из потоков программы с помощью функции:
- Эта функция создает критический раздел с именем cs. Чаще всего этот объект создается при инициализации процесса, т.е. главным потоком в функции WinMain.
После инициализации объекта критического раздела любой поток может войти в критический раздел, осуществляя вызов:
- В этот момент именно этот поток становится владельцем объекта.
Два различных потока не могут быть владельцами одного объекта критический раздел одновременно. Следовательно, если один поток вошел в критический раздел, то следующий поток, вызывая функцию EnterCriticalSection с тем же самым объектом критический раздел, будет задержан внутри функции EnterCriticalSection. Возврат из функции произойдет только тогда, когда первый поток покинет критический раздел, вызвав функцию
Ошибка сборки участника предварительной оценки windiws. Решение
- В этот момент второй поток, задержанный в функции EnterCriticalSection, станет владельцем критического раздела, и его выполнение будет возобновлено.
Обычно вызовы функций EnterCriticalSection и LeaveCriticalSection осуществляют до и после фрагмента кода потока, при выполнении которого могут возникнуть проблемы с разделением данных:
EnterCriticalSection( // критичекий фрагмент кода потока. LeaveCriticalSection(
Когда объект критический раздел больше не нужен процессу, его можно удалить (обычно в функции WinMain) с помощью функции:
- Это приведет к освобождению всех ресурсов системы, задействованных для поддержки объекта критический раздел.
Механизм критических разделов основан на принципе взаимного исключения (mutual exclusion). Только один поток может быть владельцем критического раздела в каждый конкретный момент времени. Следовательно, один поток может войти в критический раздел, установить значения полей структуры и выйти из критического раздела. Другой поток, использующий эту структуру, также мог бы войти в критический раздел с тем же именем перед осуществлением доступа к полям структуры, а затем выйти из критического раздела.
Следует отметить, что
- Объект критический раздел не может быть программой перемещен или скопирован. Процесс также не должен модифицировать объект, а должен обращаться с ним, как с “черным ящиком”.
- Возможно определение нескольких объектов типа CRITICAL_SECTION. Если в программе есть несколько потоков, то потоки, разделяющие один набор данных, могут использовать первый объект типа CRITICAL_SECTION, другие потоки, разделяющие другие наборы данные, — второй объект, и т.д.
· · Следует очень осторожно использовать использование критического раздела в главном потоке. Если вторичный поток проводит слишком много времени в его собственном критическом разделе, то это может привести к “зависанию” главного потока на слишком большой период времени.
Программа не предназначена для выполнения в Windows что делать как запустить
- Существует одно ограничение в использовании критических разделов. Оно заключается в том, что их можно применять для синхронизации потоков только в рамках одного процесса.
Бывают случаи, когда необходимо синхронизировать действия потоков различных процессов, которые разделяют какие-либо ресурсы (например, память). Использовать критические разделы в такой ситуации нельзя. Вместо них используются мьютексы (mutex object).
Приведем еще одно средство для координации и синхронизации действий потоков — семафоры. Семафоры можно использовать для ограничения количества параллельных доступов к разделяемому ресурсу. При создании семафора указывается это максимальное количество. Каждый раз, когда поток, ожидавший семафор, занимает ресурс, счетчик семафора уменьшается на единицу. При освобождении семафора этот счетчик вновь увеличивается на единицу.
Уведомление при помощи посылки сообщений
- Очень часто в процессе работы потокам следует знать информацию о состоянии другого потока (работает он или уже завершил свою работу), а также выполнять что-то после уведомления о каком-либо событии со стороны другого потока.
Рассмотрим случай, когда какому-либо потоку необходимо проинформировать первичный поток (или другой поток, умеющий обрабатывать сообщения, т.е. имеющий оконную процедуру) о своем состоянии или о том, что этому потоку следует что-либо сделать.
Любой поток может функцией SendMessage (или PostMessage) послать в оконную процедуру потока, умеющего обрабатывать сообщения, сообщение, определенное в программе. Получив такое сообщение, оконная процедура потока-адресата может сделать какие-либо действия, или может просто быть информированной о состоянии потока-отправителя, пославшего это сообщение.
Например, модифицируем функцию вторичного потока предыдущего примера так, чтобы она посылала сообщение в первичный поток через каждые 1000 итераций работы своего цикла, а функцию главного окна приложения — чтобы она обрабатывала это сообщение:
. #define MESSAGE_FROM_OTHER_THREAD (WM_USER+1). LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) <. switch(msg) <. case MESSAGE_FROM_OTHER_THREAD: // первичный поток может здесь обработать данное сообщение . >;break;. > return 0l; > void ThreadFun(void *lpvoid) < long num=0;. while(!p->stop) < num++; if (num%1000==0) // посылка сообщения первичному потоку SendMessage(p->wnd,MESSAGE_FROM_OTHER_THREAD,0,0L); >. > _endthread(); > Объекты ядра
Следующие объекты ядра бывают в свободном или занятом состоянии:
- процессы
- потоки
- задания
- файлы
- консольный ввод
- уведомления об изменении файлов
- события
- ожидаемые таймеры
- семафоры
- мьютексы
Так, объекты ядра «процесс» сразу после создания всегда находятся в занятом состоянии. В момент завершения процесса операционная система автоматически освобождает его объект ядра «процесс», и он на всегда остается в этом состоянии
Объект ядра «процесс» пребывает в занятом состоянии, пока выполняется сопоставленный с ним процесс, и переходит в свободное состояние, когда процесс завершается Внутри этого объекта поддерживается булева переменная, которая при создании объекта инициализируется как FALSE («занято»). По окончании работы процесса операционная система меняет значение этой переменной на TRUE, сообщая тем самым, что объект свободен.
Источник: studopedia.su
Ошибки потоков
В базовом классе ios определено поле state, которое представляет собой состояние потока в виде совокупности битов:
googbit = 0x00, // Нет ошибок
eofbit = 0x01, // Достигнут конец файла
failbit = 0x02, // Ошибка форматирования или преобразования
badbit = 0x04, // Серьезная ошибка, после которой пользоваться потоком
hardbit = 0x08 // Неисправность оборудования
Состоянием потока можно управлять с помощью следующих методов и операций:
int rdstate ( ) – возвращает текущее состояние потока;
int eof ( ) – возвращает ненулевое значение, если установлен флаг eofbit;
int fail ( ) – возвращает ненулевое значение, если установлен один из флагов failbit, badbit или hardbit;
int good ( ) – возвращает ненулевое значение, если сброшены все флаги ошибок;
void clear (int = 0) – параметр принимается в качестве состояния ошибки, при отсутствии параметра состояние ошибки устанавливается 0;
operator void *( ) – возвращает нулевой указатель, если установлен хотя бы один бит ошибки;
operator ! ( ) – возвращает ненулевой указатель, если установлен хотя бы один бит ошибки.
Пример. Наиболее часто используемые операции с флагами состояния потока:
// Проверить, установлен ли флаг flag
if (steam_obj.rdstate ( ) ~ios : : flag)
// Установить флаг flag
stream_obj.clear (rdstate ( ) | ios : : flag)
// Установить флаг flag и сбросить все остальные
stream_obj.clear (ios : : flag)
// Сбросить все флаги
Операция void *( ) неявно вызывается всякий раз, когда поток сравнивается с нулем. Это поволяет записывть циклы вида:
// Все в порядке, можно производить ввод/вывод
Файловые потоки
Стадартная библиотека содержит три класса для работы с файлами:
ifstream – класс входных файловых потоков;
ofstream – класс выходных файловых потоков;
fstream – класс двунаправленных файловых потоков.
Эти классы являются производными от классов istream, ostream и iostream соответственно.
Использование файлов в программе предполагает следующие операции:
Каждый класс файловых потоков содержит конструкторы, с помощью которых можно создавать объекты этих классов различными способами.
Конструкторы без параметров создают объект соответствующего класса, не связывая его с файлом:
Конструкторы с параметрами создают объект соответствующего класса, открывают файл с указанным именем и связывают файл с объектом:
ifstream (const char * name, int mode = ios : : in);
ofstream (const char * name, int mode = ios : : out | ios : : trunc);
fstream (const char * name, int mode = ios : : in | ios : : out);
здесь второй параметр – это режим открытия файла.
Открыть файл в программе можно с использованием либо конструкторов, либо метода open, имеющего такие же параметры, как и в соответствующем конструкторе.
ifstream inpf (“input.txt”); // используем конструктор
f.open (“output.txt”, ios : : out); //используем метод open
Чтение и запись выполняются либо с помощью операций чтения и извлечения, аналогичных потоковым классам, либо с помощью методов классов.
Пример. Вывод на экран содержимого файла.
char text [81], buf [81];
ifstream f (text);
Для закрытия потока определен метод close ( ), но поскольку он неявно выполняется деструктором, явный вызов необходим только тогда, когда требуется закрыть поток раньше конца его области видимости.
Перепишите вашу программу – телефонный справочник с использованием файловых потоков и форматированного ввода-вывода.
Источник: studfile.net
Правильная многопоточность: «Да» — плавности, «нет» — блокировкам!
Известно, что приложения бывают однопоточные и многопоточные. Single thread софт кодить легко и приятно: разработчику не надо задумываться о синхронизации доступа, блокировках, взаимодействии между нитями и так далее. В многопоточной среде все эти проблемы часто становятся кошмаром для программиста, особенно если опыта работы с тредами у него еще не было. Чтобы облегчить себе жизнь в будущем и сделать multi thread приложение надежным и производительным, нужно заранее продумать, как будет устроена работа с потоками. Эта статья поможет твоему мозгу увидеть правильное направление!
Асинхронное программирование набирает обороты. Пользователи любят, когда софт выполняет все действия практически моментально, а UI работает плавно и без лагов. Как бы ни росла производительность железа, но соответствовать таким высоким требованиям можно только с помощью тредов и асинхронности, а это, в свою очередь, создает множество мелких и не очень проблем, которые придется решать обычным программистам.
Для того чтобы понять, что за подводные камни таит в себе работа в многопоточной среде, нужно взглянуть на следующий код:
Singlethread код int Foo() < int res; // Что-то долго делаем и возвращаем результат retrun res; >// В основном потоке вызываем Foo auto x = Foo(); // .
У нас есть функция Foo, которая выполняет некоторые действия и возвращает результат. В главном потоке программы мы запускаем ее, получаем результат работы и идем делать дальше свои дела. Все хорошо, за исключением того, что выполнение Foo занимает довольно длительное время и ее вызов в GUI-потоке приведет к замерзанию всего интерфейса.
Это плохо, очень плохо. Современный юзер такого не переживет. Поэтому мы, немного подумав, решаем вынести действия, выполняемые в нашей функции, в отдельный поток. GUI не залипнет, а Foo спокойно отработает в своем треде.
Асинхронный вызов функции
С выходом C++11 жить стало проще. Теперь для создания своего треда не надо использовать сложные API Майкрософт или вызывать устаревшую _beginthread. В новом стандарте появилась нативная поддержка работы с потоками. В частности, сейчас нас интересует класс std::thread, который является не чем иным, как STL представлением потоков. Работать с ним — одно удовольствие, для запуска своего кода достаточно лишь передать его в конструктор std::thread в виде функционального объекта, и можно наслаждаться результатом.
Стоит также отметить, что мы можем дождаться, когда поток закончит свою работу. Для этого нам пригодится метод thread::join, который как раз и служит для этих целей. А можно и вовсе не дожидаться, сделав thread::detach. Наш предыдущий однопоточный пример может быть преобразован в multi thread всего лишь добавлением одной строки кода.
Многопоточность с помощью std::thread // . auto thread = std::thread(Foo); // Foo выполняется в отдельном потоке // .
Казалось бы, все хорошо. Мы запустили Foo в отдельном потоке, и, пока она отрабатывает, мы спокойно занимаемся своими делами, не заставляя основной поток ждать завершения длительной операции. Но есть одно но.
Мы забыли, что Foo возвращает некий результат, который, как ни странно, нам нужен. Самые смелые могут попробовать сохранять результат в какую-нибудь глобальную переменную, но это не наш метод — слишком непрофессионально.
Специально для таких случаев в STL есть замечательные std::async и std::future — шаблонная функция и класс, которые позволяют запустить код асинхронно и получить по запросу результат его работы. Если переписать предыдущий пример с использованием новых примитивов, то мы получим приблизительно следующее:
Пробуем std::async и std::future // . std::future f = std::async(std::launch::async, Foo); // Работаем дальше в основном потоке // Когда нам нужно, получаем результат работы Foo auto x = f.get();
В std::async мы передали флаг std::launch::async, который означает, что код надо запустить в отдельном потоке, а также нашу функцию Foo. В результате мы получаем объект std::future. После чего мы опять продолжаем заниматься своими делами и, когда нам это понадобится, обращаемся за результатом выполнения Foo к переменной f, вызывая метод future::get.
Другие статьи в выпуске:
Хакер #174. Собираем квадрокоптер
- Содержание выпуска
- Подписка на «Хакер» -60%
Выглядит все идеально, но опытный программист наверняка задаст вопрос: «А что будет, если на момент вызова future::get функция Foo еще не успеет вернуть результат своих действий?» А будет то, что главный поток остановится на вызове get до тех пор, пока асинхронный код не завершится.
Таким образом, при использовании std::future главный поток может заблокироваться, а может и нет. В итоге мы получим нерегулярные лаги нашего GUI. Наша цель — полностью избежать таких блокировок и добиться того, чтобы главный тред работал быстро и без фризов.
Concurrent Queue
В примере с future::get мы фактически использовали мьютекс. Во время попытки получения значения из std::future код шаблонного класса проверял, закончил ли свою работу поток, запущенный с помощью std::async, и если нет, то ожидал его завершения. Для того чтобы один поток никогда не ждал, пока отработает другой, умные программисты придумали потокобезопасную очередь.
Любой кодер знает такие структуры данных, как вектор, массив, стек и так далее. Очередь — это одна из разновидностей контейнеров, работающая по принципу FIFO (First In First Out). Thread-safe очередь отличается от обычной тем, что добавлять и удалять элементы можно из разных потоков и при этом не бояться, что мы одновременно попробуем записать или удалить что-нибудь из очереди, тем самым с большой долей вероятности получив падение программы или, что еще хуже, неопределенное поведение.
Concurrent queue можно использовать для безопасного выполнения кода в отдельном потоке. Выглядит это примерно так: в потокобезопасную очередь мы кладем функциональный объект, а в это время в рабочем потоке крутится бесконечный цикл, который на каждой итерации обращается к очереди, достает из нее переданный ей код и выполняет его. Чтобы лучше понять, можно взглянуть на код:
Реализация потокобезопасной очереди команд class WorkQueue < public: typedef std::functionCallItem; WorkQueue() : done(false), thread([=]() < while (!done) queue.pop()(); >) < >void PushBack(CallItem workItem) < queue.push(workItem); >// . private: concurrent_queue queue; bool done; std::thread thread; >;
Теперь наш код выполняется в очереди, в другом потоке. Более того, мы можем отправить на выполнение не только Foo, но и другие функции, которые выполняются слишком долго. При этом мы не будем каждый раз создавать отдельный thread для каждой функции.
Но мы опять забыли про возвращаемое значение. Одним из способов решения этой проблемы будет обратный вызов. Мы должны передавать в рабочую очередь не только код, требующий выполнения, но и callback-функцию, которая будет вызвана вычисленным значением в качестве параметра.
Callback для возврата значения class WorkQueue < public: typedef std::functionWorkItem; typedef std::function CallbackItem; WorkQueue() : done(false), thread([=]() < while (!done) queue.pop()(); >) < >void PushBack(WorkItem workItem, CallbackItem callback) < queue.push([=]() < auto res = workItem(); callback(res); >); > // . private: typedef std::function CallItem; concurrent_queue queue; bool done; std::thread thread; >;
Но тут следует помнить, что обратный вызов будет сделан в рабочем потоке, а не в клиентском, поэтому заранее следует позаботиться о безопасной передаче значения. Если все приложение построено на основе архитектуры потокобезопасных очередей, то есть у каждого объекта есть своя очередь команд, то решение данной проблемы становится очевидным — мы просто будем поручать выполнение обратного вызова очереди, в которой работает объект, запросивший у нас выполнение Foo.
На словах это звучит довольно просто, но на практике придется решать множество проблем, которые так или иначе связаны с многопоточностью. Так как все происходит асинхронно, то вполне может быть, что по окончании выполнения какого-либо кода объект, запросивший это выполнение, уже не будет существовать и, возвращая ему значение вычислений, мы можем сломать всю программу. Такие нюансы надо учитывать с самого начала, чтобы на этапе проектирования исключить все подобные ситуации.
Заключение
Асинхронное программирование нынче в тренде. Пользовательские интерфейсы iOS и Windows Phone работают плавно и без лагов как раз из-за того, что в их основу заложены принципы, позволяющие избегать блокировок потоков в ожидании результатов работы тех или иных длительных действий. И чем дальше, тем более ярко будет выражено движение в сторону асинхронности работы ПО.
Источник: xakep.ru