- Потоки (threads) в WinAPI
- Потоки (Threads)
- Создание потоков — CreateThread
- Создание программы с двумя потоками
- Создание потока с windows
- Создание потоков и передача данных во время запуска Creating threads and passing data at start time
- Создание потока Creating a thread
- Передача данных в потоки Passing data to threads
- Извлечение данных из потоков с помощью методов обратного вызова Retrieving data from threads with callback methods
- Создание потока с помощью Visual C#
- Требования
- Создание приложения Visual C# с помощью потоков
- Проверка работы
- Устранение неполадок
- Ссылки
Потоки (threads) в WinAPI
Когда приложение начинает свою работу, для него создаётся процесс (process). Обычно, каждой программе соответствует один процесс.
При создании процесса для него выделяется память — виртуальное адресное пространство (virtual address space). Когда в отладчике мы смотрим на адреса переменных — мы видим адреса из этого пространства.
Потоки (Threads)
Каждый процесс имеет как минимум один поток (thread). До сих пор наши программы состояли из одного процесса и одного потока. В этом уроке мы научимся создавать дополнительные потоки в процессе.
Самый главный вопрос о потоках: для чего нужно несколько потоков? Разные потоки могут одновременно выполняться разными ядрами процессора. Например, в нашей однопоточной программе одна функция просчитывает искусственный интеллект, а другая — физику взаимодействия объектов. В этом случае сначала будет выполнена одна функция, а потом вторая. Если же мы разделим программу на два процесса и запустим программу на двухъядерном процессоре, то искусственный интеллект и физика будут просчитываться одновременно.
Все потоки имеют доступ к адресному пространству процесса. И это может стать серьёзной проблемой. Например, один поток обрабатывает данные и, одновременно, второй пытается вывести эти же данные на экран. Что произойдёт? Успеет ли первый поток обработать все данные, до того как до них доберётся второй поток? Или же второй поток обгонит первый, и часть данных пользователь увидит обработанными, а часть — нет? Эти вопросы мы обсудим в следующих уроках.
С точки зрения C++ поток — это обычная функция имеющая определённый прототип. Для создания потока используется функция CreateThread.
Создание потоков — CreateThread
Функция CreateThread возвращает описатель потока:
!1?HANDLE WINAPI CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );?1!
1. lpThreadAttributes — данный аргумент определяет, может ли создаваемый поток быть унаследован дочерним процессом. Мы не будем создавать дочерние процессы, поэтому ставим NULL.
2. dwStackSize — размер стека в байтах. Если передать 0, то будет использоваться значение по-умолчанию (1 мегабайт).
3. lpStartAddress — адрес функции, которая будет выполняться потоком. Т.е. можно сказать, что функция, адрес которой передаётся в этот аргумент, является создаваемым потоком. Данная функция должна соответствовать определённому прототипу — рассмотрим ниже. Имя функции может быть любым — вы сами его выбираете.
4. lpParameter — указатель на переменную, которая будет передана в поток.
5. dwCreationFlags — флаги создания. Здесь можно отложить запуск выполнения потока. Мы будем запускать поток сразу же, передаём 0.
6. lpThreadId — указатель на переменную, куда будет сохранён идентификатор потока. Нам идентификатор не нужен, передаём NULL.
Давайте посмотрим на код вызова CreateThread:
!1?HANDLE thread = CreateThread(NULL,0,thread2,NULL, 0, NULL);?1!
Здесь мы сохраняем описатель потока в переменной thread. Обратите внимание на третий аргумент — адрес функции потока. thread2 — имя функции, которая и будет являться вторым потоком. Вот её код:
Функция потока должна соответствовать следующему прототипу:
!1?DWORD WINAPI ThreadProc(LPVOID lpParameter)?1!
Аргумент, который может принимать данная функция передаётся четвёртым параметров функции CreateThread. Если отбросить все переопределения типов, то данный прототип выглядит так:
!1?unsigned long __stdcall ThreadProc(void* lpParameter)?1!
Напоследок рассмотрим пример создания второго потока:
Создание программы с двумя потоками
Код программы можно скачать в начале урока. Это простая консольная программа. Для работы с потоками необходимо включить файл windows.h. Рассмотрим основной код:
!1?DWORD WINAPI thread2(LPVOID); int main() < cout
Создание потока с windows
0, // размер стека используется по умолчанию
ThreadFunc, // функция потока
&dwThrdParam, // аргумент функции потока
0, // флажки создания используются по умолчанию
// При успешном завершении проверяет возвращаемое значение.
Для простоты, этот пример передает указатель на значение как на параметр функции потока. Это может быть указатель на любой тип данных или структуру, или это может быть пропущено совсем, при помощи передачи указателя NULL и удаления ссылок на параметр в ThreadFunc .
Опасно передавать адрес локальной переменной, если создающий поток заканчивает работу перед созданием нового потока, потому что указатель становится недопустимым. Вместо этого, или передайте указатель в динамически распределяемую память, или заставьте создающий поток ждать до тех пор, пока новый поток не завершит свое формирование. Данные могут также быть переданы и из создающего потока в новый поток, используя глобальные переменные. С глобальными переменными, обычно необходимо синхронизировать доступ ко многим потокам. Для получения дополнительной информации о синхронизации, см. статью Синхронизация исполнения многопоточного режима .
В процессах, где поток мог бы создать несколько потоков, чтобы исполнить некоторый код, неудобно использовать глобальные переменные. Например, процесс, который разрешает пользователю открыть одновременно несколько файлов на некоторое время, может создавать новый поток для каждого файла, с целью, чтобы каждый из потоков выполнял некоторую функцию потока. Создающий поток может передавать уникальную информацию (такую как имя файла) требующуюся для каждого экземпляра функции потока как параметр. Вы не можете использовать единственную глобальную переменную для этой цели, но Вы можете использовать динамически распределяемый строковый буфер.
Создающий поток может использовать параметры функции CreateThread , чтобы определить нижеследующее:
- Атрибуты системы безопасности для дескриптора нового потока . Эти атрибуты защиты включают в себя флажок наследования, который устанавливает, может ли дескриптор быть унаследован дочерними процессами. Атрибуты системы безопасности к тому же включают в себя дескриптор безопасности, который система использует, чтобы выполнить доступ, который контролирует все последующие использования дескриптора потока прежде, чем предоставляется обращение к нему.
- Начальный размер стека нового потока . Стек потока назначается автоматически в пространстве памяти процесса; система увеличивает стек насколько необходимо и освобождает его, когда поток заканчивает работу. Дополнительную информацию см. в статье Размер стека потока .
- Флажок создания , который разрешает Вам создать поток в состоянии ожидания. Когда произошла приостановка, поток не запускается до тех пор, пока не будет вызвана функция ResumeThread .
Вы можете также создать поток и путем вызова функции CreateRemoteThread . Эта функция используется процессами отладчика, чтобы создать поток, который запускается в адресном пространстве отлаживаемого процесса.
Создание потоков и передача данных во время запуска Creating threads and passing data at start time
При создании процесса в операционной системе она добавляет поток для выполнения кода этого процесса, включая все исходные домены приложений. When an operating-system process is created, the operating system injects a thread to execute code in that process, including any original application domain. С этого момента домены приложений могут создаваться и уничтожаться, что не обязательно сопровождается созданием или уничтожением потоков операционной системы. From that point on, application domains can be created and destroyed without any operating system threads necessarily being created or destroyed. Если выполняемый код является управляемым, то вы можете получить объект Thread для потока, выполняющегося в текущем домене приложения. Для этого получите статическое свойство CurrentThread типа Thread. If the code being executed is managed code, then a Thread object for the thread executing in the current application domain can be obtained by retrieving the static CurrentThread property of type Thread. В этой статье описано создание потока и несколько способов передачи данных в процедуру потока. This topic describes thread creation and discusses alternatives for passing data to the thread procedure.
Создание потока Creating a thread
Создание нового объекта Thread приводит к созданию нового управляемого потока. Creating a new Thread object creates a new managed thread. Класс Thread имеет конструкторы, которые принимают делегат ThreadStart или ParameterizedThreadStart. Этот делегат инкапсулирует метод, который вызывается новым потоком при вызове метода Start. The Thread class has constructors that take a ThreadStart delegate or a ParameterizedThreadStart delegate; the delegate wraps the method that is invoked by the new thread when you call the Start method. Повторный вызов Start приводит к созданию исключения ThreadStateException. Calling Start more than once causes a ThreadStateException to be thrown.
Метод Start завершается немедленно, часто даже до запуска нового потока. The Start method returns immediately, often before the new thread has actually started. Вы можете использовать свойства ThreadState и IsAlive, чтобы определить состояние потока в настоящий момент, но эти свойства ни в коем случае нельзя использовать для синхронизации действий потоков. You can use the ThreadState and IsAlive properties to determine the state of the thread at any one moment, but these properties should never be used for synchronizing the activities of threads.
После запуска потока не нужно сохранять ссылку на объект Thread. Once a thread is started, it is not necessary to retain a reference to the Thread object. Поток продолжит выполняться, пока не завершится запущенная в нем процедура. The thread continues to execute until the thread procedure ends.
В следующем примере кода создается два новых потока для вызова метода экземпляра и статического метода из другого объекта. The following code example creates two new threads to call instance and static methods on another object.
Передача данных в потоки Passing data to threads
Делегат ParameterizedThreadStart предоставляет простой способ передать в поток объект с данными при вызове Thread.Start(Object). The ParameterizedThreadStart delegate provides an easy way to pass an object containing data to a thread when you call Thread.Start(Object). См. пример кода в ParameterizedThreadStart. See ParameterizedThreadStart for a code example.
Делегат ParameterizedThreadStart не является типобезопасным способом передачи данных, так как метод Thread.Start(Object) принимает любой объект. Using the ParameterizedThreadStart delegate is not a type-safe way to pass data, because the Thread.Start(Object) method accepts any object. Вместо этого можно инкапсулировать процедуру потока и данные во вспомогательный класс и выполнять процедуру потока при помощи делегата ThreadStart. An alternative is to encapsulate the thread procedure and the data in a helper class and use the ThreadStart delegate to execute the thread procedure. В следующем примере показана эта техника. The following example demonstrates this technique:
Ни один из делегатов ThreadStart или ParameterizedThreadStart не имеет возвращаемого значения, так как им некуда возвращать данные после асинхронного вызова. Neither ThreadStart nor ParameterizedThreadStart delegate has a return value, because there is no place to return the data from an asynchronous call. Чтобы получить результаты из метода потока, вы можете использовать метод обратного вызова, как показано в следующем разделе. To retrieve the results of a thread method, you can use a callback method, as shown in the next section.
Извлечение данных из потоков с помощью методов обратного вызова Retrieving data from threads with callback methods
Приведенный ниже пример демонстрирует метод обратного вызова, который получает данные из потока. The following example demonstrates a callback method that retrieves data from a thread. Конструктор класса, содержащего данные и метод потока, также принимает делегат, который представляет метод обратного вызова. Метод потока перед завершением своей работы вызывает этот делегат обратного вызова. The constructor for the class that contains the data and the thread method also accepts a delegate representing the callback method; before the thread method ends, it invokes the callback delegate.
Создание потока с помощью Visual C#
Вы можете создавать многопоточные приложения в Microsoft Visual C# .NET или Visual C#. В этой статье описывается, как простое приложение Visual C# может создавать и управлять потоками.
Исходная версия продукта: Visual C #
Исходный номер статьи базы знаний: 815804
Требования
В приведенном ниже списке перечислены рекомендуемые оборудование, программное обеспечение, сетевая инфраструктура и необходимые пакеты обновления.
- Windows или Windows Server
- Visual C# .NET или Visual C #
В этой статье предполагается, что вы знакомы со следующими разделами:
- Программирование на Visual C#
- Интегрированная среда разработки (IDE) Visual Studio .NET или ИНТЕГРИРОВАНная среда разработки Visual Studio
Эта статья относится к пространству имен библиотеки классов .NET Framework System.Threading .
Создание приложения Visual C# с помощью потоков
Запустите Visual Studio .NET, Visual Studio или Visual C# Express Edition.
Создайте новый проект приложения Visual C# для Windows с именем среадвинапп.
Добавьте в форму элемент управления Кнопка. По умолчанию кнопке присвоено имя Button1.
Добавьте компонент ProgressBar в форму. По умолчанию индикатор выполнения называется ProgressBar1.
Щелкните форму правой кнопкой мыши и выберите команду Просмотреть код.
Добавьте в начало файла следующую инструкцию:
Добавьте следующий button1_Click обработчик событий для Button1:
Добавьте в класс следующую переменную Form1 :
Добавьте в класс следующий метод Form1 :
Это код, который находится на самом потоке. Этот код является бесконечным циклом, который случайным образом увеличивается или уменьшает значение в ProgressBar1, а затем ожидает 100 миллисекунд, прежде чем продолжить.
Добавьте следующий Form1_Load обработчик событий для Form1. Этот код создает новый поток, создает поток в фоновом потоке, а затем запускает поток.
Проверка работы
Постройте и запустите приложение. Обратите внимание, что значение в ProgressBar1 изменяется случайным образом. Это новый поток в операции.
Чтобы продемонстрировать, что главный поток не зависит от потока, который изменяет значение ProgressBar1, нажмите кнопку в форме. Появится диалоговое окно со следующим сообщением об ошибке:
Дождитесь ввода. Обратите внимание, что значение в ProgressBar1continues для изменения.
Устранение неполадок
В более сложных приложениях необходимо синхронизировать несколько потоков при доступе к общим переменным. Для получения дополнительных сведений ознакомьтесь с разкрывающимся оператором lock и связанными разделами справочной документации по Visual C# .NET Online.
Ссылки
Для получения дополнительных сведений см класс Thread.