Базовые методы работы с потоками: создание потоков, запуск, ожидание завершения
Сейчас мы рассмотрим процесс создания потоков, используя в качестве примера разработку консольной программы. При старте программы будут запускаться на выполнение 4 потока, каждый из которых будет выводить свой номер в окно консоли.
При создании потоков мы установим для них различные приоритеты, таким образом, части потоков будет выделено больше квантов времени процессора, и они будут доминировать при выводе своего номера в окно. Мы сможем это наблюдать в ходе работы программы.
Программа очень проста, так что давайте приступим.
Итак, сначала создайте новый проект, назовем его Thread_Step_1. В качестве шаблона выберите консольное приложение.
Сгенерированный оболочкой код первоначально выглядит следующим образом:
/*http://esate.ru, Anvi*/ using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Thread_Step_1 < class Program < static void Main(string[] args) < >> >
Для работы с потоками нам необходимо подключить пространстве имен System.Threading.
Многопоточность | Потоки | thread | Многопоточное программирование | Уроки | C++ #1
Добавьте строку using System.Threading после строки using System.Text.
Непосредственно перед функцией Main мы добавим функцию WriteString, которая будет отвечать за вывод символов, назначенных данному потоку (мы будем назначать номер потока) на экран. В качестве параметра функция будет получать объект _Data, который впоследствии будет преобразован в сроку в коде функции.
Код функции будет выглядеть следующим образом:
/*http://esate.ru, Anvi*/ static void WriteString(object _Data) < //для получения строки используем преобразование типов: // приводим переменную _Data к типу string и записываем // в переменную str_for_out string str_for_out = (string) _Data; // теперь поток 1 тысячу раз выведит полученную строку (свой номер) for (int i = 0; i
- Сначала мы создадим 4 потока, каждому из которых укажем, что они будут выполнять функцию WriteString.
- Далее мы назначим потокам приоритеты.
- После этого мы запустим все четыре потока, передав в качестве параметра их номера.
- Далее нам останется только дождаться завершения все четырех потоков и ожидать ввода пользователем какого-либо символа, чтобы завершить выполнение программы.
/*http://esate.ru, Anvi*/ static void Main(string[] args) //точка входа в программу < //создаем 4 потока, в качестве параметров передаем имя Выполняемой функции Thread th_1 = new Thread(WriteString); Thread th_2 = new Thread(WriteString); Thread th_3 = new Thread(WriteString); Thread th_4 = new Thread(WriteString); //расставляем приоритеты для потоков th_1.Priority = ThreadPriority.Highest; // самый высокий th_2.Priority = ThreadPriority.BelowNormal; // выше среднего th_3.Priority = ThreadPriority.Normal; // средний th_4.Priority = ThreadPriority.Lowest; // низкий // запускаем каждый поток, в качестве параметра передаем номер потока th_1.Start( «1»); th_2.Start( «2»); th_3.Start( «3»); th_4.Start( «4»); Console.WriteLine( «все потоки запущены «); //Ждем заврешения каждого потока th_1.Join(); th_2.Join(); th_3.Join(); th_4.Join(); Console.ReadKey(); // прочитать символ (пока пользователь не нажмет клавишу программа не завершиться (чтобы можно было успеть посмотреть результат)). >
Откомпилируйте и запустите программу (F5).
Потоки в Python за 5 минут
Пример результата работы программы можно увидеть на рисунке 1.
Также необходимо помнить, что при каждом запуске программы выводимый на экран результат будет отличаться от предыдущего. А если запустить созданное приложение не из среды Visual Studio (без отладки), а просто из операционной системы, то до вывода сообщения «Все потоки запущены» по несколько раз может успеть выполниться назначенная функция каждого потока.
Рисунок 1. Пример выполнения многопоточного консольного приложения.
Как видно из рисунка, к моменту запуска последнего 4-го потока, 1-й поток, имеющий Highest (наивысший) приоритет уже успел полностью выполниться. Последний же 4-й приоритет, запущенный в добавок еще и последним, выполнится уже после всех остальных потоков.
Вот мы и познакомились с самыми примитивными методами, используемыми для работы с потоками. В следующей части главы сможете узнать о других интересных методах для работы с более реальными задачами многопоточного программирования.
Понравилась публикация? Сохраните ее, чтобы вернуться к изучению материала!
Прикрепленные файлы для скачивания:
- Скачать: chapter-3.2-Ishodnie-kodi-rabota-s-potokami.zip
Источник: esate.ru
Образец многопотоковой программы на C
Bounce.c — это пример многопоточной программы, которая создает новый поток при каждом вводе буквы a или A . Каждый поток отображает букву другого цвета на экране. Можно создать до 32 потоков. Обычное завершение программы происходит при q типе или Q .
Компиляция и связывание многопоточной программы
По умолчанию программы компилируются как многопоточные.
Компиляция и связывание многопоточной программы Bounce.c из среды разработки
- В меню Файл последовательно выберите пункты Создать>Проект.
- В диалоговом окне Создание проекта выберите шаблон Консольное приложение с тегами C++, Windows и Консоли . Чтобы продолжить, нажмите кнопку Далее .
- В диалоговом окне Настройка нового проекта введите имя проекта, например Bounce. Нажмите кнопку Создать , чтобы продолжить.
- В окне Обозреватель решений откройте папку Исходные файлы в проекте и измените имя исходного файла на расширение .c.
- В окне редактирования удалите существующий исходный код и замените его примером кода.
- В меню Построение выберите Построить решение.
- Нажмите клавишу F5 , чтобы запустить программу в отладчике.
- В меню Файл последовательно выберите пункты Создать>Проект.
- В диалоговом окне Новый проект выберите Visual C++ на левой панели, а затем в центральной области выберите Пустой проект .
- В поле Имя введите имя проекта, например Bounce. Нажмите кнопку ОК , чтобы создать пустой проект.
- В окне Обозреватель решений откройте папку Исходные файлы в проекте и добавьте в проект файл, содержащий исходный код C.
- В меню Сборка выполните сборку проекта, выбрав команду Сборка решения .
- Нажмите клавишу F5 , чтобы запустить программу в отладчике.
- Нажмите клавишу , чтобы создать новый поток. Каждый поток отображает символ другого цвета на экране.
- Нажмите клавишу q , чтобы завершить работу.
Компиляция и связывание многопоточной программы Bounce.c из командной строки
- Откройте командную строку средств Visual Studio. Это гарантирует, что путь будет включен в компилятор.
- Скомпилируйте и свяжите программу:
cl bounce.c
Пример
Чтобы выполнить сборку из командной строки, скопируйте и сохраните этот пример в исходном файле с расширением C. В интегрированной среде разработки замените любой исходный код, созданный шаблоном, следующим примером:
// sample_multithread_c_program.c // compile with: /c // // Bounce — Creates a new thread each time the letter ‘a’ is typed. // Each thread bounces a character of a different color around // the screen. All threads are terminated when the letter ‘Q’ is // entered. // #include #include #include #include #include #include #define MAX_THREADS 32 // The function getrandom returns a random number between // min and max, which must be in integer range. #define getrandom( min, max ) (SHORT)((rand() % (int)(((max) + 1) — (min))) + (min)) int main(void); // Thread 1: main void KbdFunc(void); // Keyboard input, thread dispatch void BounceProc(void* pMyID); // Threads 2 to n: display void ClearScreen(void); // Screen clear void ShutDown(void); // Program shutdown void WriteTitle(int ThreadNum); // Display title bar information HANDLE hConsoleOut; // Handle to the console HANDLE hRunMutex; // «Keep Running» mutex HANDLE hScreenMutex; // «Screen update» mutex int ThreadNr = 0; // Number of threads started CONSOLE_SCREEN_BUFFER_INFO csbiInfo; // Console information COORD consoleSize; BOOL bTrails = FALSE; HANDLE hThreads[MAX_THREADS] = < NULL >; // Handles for created threads int main(void) // Thread One < // Get display screen information GetConsoleScreenBufferInfo(hConsoleOut, consoleSize.X = csbiInfo.srWindow.Right; consoleSize.Y = csbiInfo.srWindow.Bottom; ClearScreen(); WriteTitle(0); // Create the mutexes and reset thread count. hScreenMutex = CreateMutexW(NULL, FALSE, NULL); // Cleared hRunMutex = CreateMutexW(NULL, TRUE, NULL); // Set // Start waiting for keyboard input to dispatch threads or exit.
KbdFunc(); // All threads done. Clean up handles. if (hScreenMutex) CloseHandle(hScreenMutex); if (hRunMutex) CloseHandle(hRunMutex); if (hConsoleOut) CloseHandle(hConsoleOut); >void ShutDown(void) // Shut down threads < // Tell all threads to die ReleaseMutex(hRunMutex); while (ThreadNr >0) < // Wait for each thread to complete WaitForSingleObject(hThreads[—ThreadNr], INFINITE); >// Clean up display when done WaitForSingleObject(hScreenMutex, INFINITE); ClearScreen(); > void KbdFunc(void) // Dispatch and count threads.
< int KeyInfo; do < KeyInfo = _getch(); if (tolower(KeyInfo) == ‘a’ ThreadNr < MAX_THREADS) < ++ThreadNr; hThreads[ThreadNr] = (HANDLE)_beginthread(BounceProc, 0, (void*)(uintptr_t)ThreadNr); WriteTitle(ThreadNr); >if (tolower(KeyInfo) == ‘t’) < bTrails = !bTrails; >> while (tolower(KeyInfo) != ‘q’); ShutDown(); > void BounceProc(void* pMyID) < wchar_t MyCell, OldCell; WORD MyAttrib, OldAttrib = 0; wchar_t BlankCell = 0x20; COORD Coords, Delta; COORD Old = < 0,0 >; DWORD Dummy; int MyID = (int)(uintptr_t)pMyID; // Generate update increments and initial // display coordinates. srand(MyID * 3); Coords.X = getrandom(0, consoleSize.X — 1); Coords.Y = getrandom(0, consoleSize.Y — 1); Delta.X = getrandom(-3, 3); Delta.Y = getrandom(-3, 3); // Set up character 16) MyCell = (wchar_t)(0x60 + MyID — 16); // lower case else MyCell = (wchar_t)(0x40 + MyID); // upper case MyAttrib = MyID // force black background do < // Wait for display to be available, then lock it. WaitForSingleObject(hScreenMutex, INFINITE); if (!bTrails) < // If we still occupy the old screen position, blank it out. ReadConsoleOutputCharacterW(hConsoleOut, Dummy); ReadConsoleOutputAttribute(hConsoleOut, Dummy); if ((OldCell == MyCell) (OldAttrib == MyAttrib)) WriteConsoleOutputCharacterW(hConsoleOut, Dummy); >// Draw new character, then clear screen lock WriteConsoleOutputCharacterW(hConsoleOut, Dummy); WriteConsoleOutputAttribute(hConsoleOut, Dummy); ReleaseMutex(hScreenMutex); // Increment the coordinates for next placement of the block. Old.X = Coords.X; Old.Y = Coords.Y; Coords.X += Delta.X; Coords.Y += Delta.Y; // If we are about to go off the screen, reverse direction if (Coords.X < 0 || Coords.X >= consoleSize.X) < Delta.X = -Delta.X; Beep(400, 50); >if (Coords.Y < 0 || Coords.Y >consoleSize.Y) < Delta.Y = -Delta.Y; Beep(600, 50); >> // Repeat while RunMutex is still taken. while (WaitForSingleObject(hRunMutex, 75L) == WAIT_TIMEOUT); > void WriteTitle(int ThreadNum) < enum < sizeOfNThreadMsg = 120 >; wchar_t NThreadMsg[sizeOfNThreadMsg] = < L»» >; swprintf_s(NThreadMsg, sizeOfNThreadMsg, L»Threads running: %02d. Press ‘A’ » L»to start a thread, ‘T’ to toggle » L»trails, ‘Q’ to quit.», ThreadNum); SetConsoleTitleW(NThreadMsg); > void ClearScreen(void) < DWORD dummy = 0; COORD Home = < 0, 0 >; FillConsoleOutputCharacterW(hConsoleOut, L’ ‘, consoleSize.X * consoleSize.Y, Home, >
Источник: learn.microsoft.com
Многопоточные приложения в C# для чайников
Что такое многопоточные приложения? Грубо говоря, это приложения с несколькими «рабочими», которые одновременно выполняют разные или однотипные задачи. Зачем это нужно? Ресурсы компьютера используются не всегда эффективно. Например, ваша программа скачивает страницу из интернета, потом анализирует ее, затем — качает следующую.
Во время анализа простаивает интернет соединение, а во время закачки — скучает процессор. Это можно исправить. Уже во время анализа текущей страницы параллельно качать следующую.
Я попытаюсь объяснить, как оптимизировать свои программы, используя многопоточность, и приведу пару примеров с кодом. Прошу под кат.
Для начала, давайте разберемся, когда можно распараллелить программу. Например, у вас есть один поток данных. Его нужно обрабатывать в строго определенном порядке и без результатов предыдущей обработки, следующую операцию выполнять нельзя. В такой программе можно создать дополнительные потоки, но будут ли они нужны? Мой преподаватель по компьютерным системам приводил следующий пример.
Допустим, у нас есть 2 рабочих, которые хорошо копают ямы. Предположим, что один выкопает яму глубиной 2 метра, за 1 час. Тогда два рабочих, выкопают эту яму за полчаса. Это похоже на правду. Давайте возьмем 3600 таких рабочих. Теоретически, они выкопают яму глубиной 2 метра за 1 секунду.
Но на практике они будут друг другу мешать, топтаться на одном месте и нервничать.
Я надеюсь, вы поняли, что многопоточность нужна не всегда, и что утверждение «чем больше потоков — тем лучше» ошибочно. Потоки не должны мешать друг другу и должны использовать эффективно ресурсы системы, в которой они работают.
Далее немного практики. Что бы начать работать с потоками, необходимо подключить пространство имен System.Threading, добавив в начало файла с кодом следующую директиву:
using System.Threading;
Любой поток в C# это функция. Функции не могут быть сами по себе, они обязательно являются методами класса. Поэтому, что бы создать отдельный поток, нам понадобится класс с необходимым методом. Самый простой вариант метода возвращает void и не принимает аргументов:
void MyThreadFunction()
Пример запуска такого потока:
Thread thr = new Thread(MyThreadFunction); thr.Start();
После вызова метода Start() у объекта потока, управление вернется сразу, но в этот момент уже начнет работать ваш новый поток. Новый поток выполнит тело функции MyThreadFunction и завершится. Мой друг спросил меня, а почему функция не возвращает значение? А потому, что его некуда вернуть.
После вызова Start(), управление передается дальше, при этом созданный поток может работать еще длительное время. Что бы обмениваться данными между потоками, можно пользоваться переменными класса. Об этом позже.
Кроме того, существует еще один вариант метода, из которого можно сделать поток. Выглядит он вот так:
void ThreadFunction(Object input)
Его назначение — применение в тех случаях, когда нужно при создании потоков, передавать им какие-то данные. Любой класс в C# наследуется от Object, поэтому можно передавать внутрь потока любой объект. Подробнее позже.
Давайте напишем простой пример, что бы проиллюстрировать работу потоков. Реализуем следующее. Наша программа запустит дополнительный поток, после чего выведет три раза на экран «Это главный поток программы!», в это время созданный поток выведет три раза на экран «Это дочерний поток программы!». Вот, что получилось:
using System; using System.Threading; namespace ThreadsExample < class Program < static void Main(string[] args) < //Создаем объекта потока Thread thread = new Thread(ThreadFunction); //Запускаем поток thread.Start(); //Просто выводим 3 раза на экран заданный текст int count = 3; while (count >0) < Console.WriteLine(«Это главный поток программы!»); —count; >//Ждем ввода от пользователя, что бы окно консоли не закрылось автоматически Console.Read(); > //Функция потока static void ThreadFunction() < //Аналогично главному потоку выводим три раза текст int count = 3; while (count >0) < Console.WriteLine(«Это дочерний поток программы!»); —count; >> > >
Ничего сложно.
Хочу обратить ваше внимание, что у метода потока присутствует модификатор static, это нужно для того, что бы к ней можно было напрямую обратиться из главного потока приложения. Теперь запустите программу несколько раз и сравните результаты вывода в консоль. Обычно, порядок вывода сообщений в консоль при каждом запуске разный. Планировщик задач операционной системы по-разному распределяет процессорное время, поэтому порядок вывода разный. Еще одно полезное свойство потоков заключается в том, что вам не нужно беспокоиться о правильности распараллеливания их на процессоре, планировщик задач все сделает сам.
Идем дальше. Что бы создать несколько потоков, необязательно использовать несколько функций, если потоки одинаковые. Вот пример:
using System; using System.Threading; namespace SomeThreadsExample < class Program < static void Main(string[] args) < //Создаем в цикле 10 потоков for (int i = 0; i < 10; ++i) < Thread thread = new Thread(ThreadFunction); thread.Start(); >Console.WriteLine(«Создание потоков завершено!»); Console.Read(); > static void ThreadFunction() < //Просто выводим что-нибудь в консоль для наглядности Console.WriteLine(«Я поток!»); >> >
Как видите, для создания 10 потоков нам понадобилась всего 1 функция.
Каждый поток имеет свой стек, поэтому локальные переменные метода для каждого потока свои. Что бы это продемонстрировать, мы создадим поток для метода класса, а потом вызовем этот же метод из главного потока. Вот код:
using System; using System.Threading; namespace SomeThreadsExample < class Program < static void Main(string[] args) < //Создаем поток Thread thread = new Thread(ThreadFunction); thread.Start(); //Вызываем этот же метод без создания потока ThreadFunction(); Console.Read(); >static void ThreadFunction() < int count = 5; //Выводим пять раз значение count while (count >0) < Console.WriteLine(count); —count; >> > >
После завершения выполнения потоков, в консоле будет выведено 10 чисел.
Если же мы хотим, что бы каждый поток вел себя по-разному, то есть два решения. Первое — создание дополнительной функции для потока. Например, вот так:
using System; using System.Threading; namespace SomeThreadsExample < class Program < static void Main(string[] args) < //Создаем первый поток Thread thread1 = new Thread(ThreadFunction1); thread1.Start(); //Создаем второй поток Thread thread2 = new Thread(ThreadFunction2); thread2.Start(); Console.Read(); >static void ThreadFunction1() < //Просто выводим что-нибудь в консоль для наглядности Console.WriteLine(«Это первый поток!»); >static void ThreadFunction2() < //Аналогично первому потоку Console.WriteLine(«Это второй поток!»); >> >
Второе решение более запутанное.
Смысл в том, что бы используя один метод выполнять разные действия в разных потоках. Для этого, внутри метода потока нужно определить «кто я?». Как вариант, используем метод потока, который может принимать значения. В поток будем передавать булевый флаг. Если он равен истине — значит это первый поток, если ложь — значит второй.
Метод потока сам будет определять «кто я?» и в зависимости от этого, выполнять разные действия. Вот код:
using System; using System.Threading; namespace SomeThreadsExample < class Program < static void Main(string[] args) < Thread thread1 = new Thread(ThreadFunction); thread1.Start(true); Thread thread2 = new Thread(ThreadFunction); thread2.Start(false); Console.Read(); >static void ThreadFunction(Object input) < //Преобразовываем входящий параметр в bool bool flag = (bool)input; //Если входящий флаг true — значит «я первый поток» if (flag) < Console.WriteLine(«Это первый поток!»); >//Если входящий флаг false — значит «я второй поток» else < Console.WriteLine(«Это второй поток!»); >> > >
Как работает данная программа? Каждый поток использует свой экземпляр метода. Если потоку выдали флаг true, значит он выполняет один код, если false — другой. Этот пример так же наглядно демонстрирует как работать с методами потоков, который принимают параметры.
Давайте разберем, как обмениваться данными между разными потоками. Перед тем, как я приведу пример с кодом, расскажу немного теории. Во всех многопоточных приложениях существует синхронизация данных. Когда два или больше потоков, пытаются взаимодействовать с какими либо данными одновременно — может возникнуть ошибка. Для этого в C# существует несколько способов синхронизации, но мы рассмотрим самый простой с помощью оператора lock.
Теперь давайте пример. Создадим отдельный класс, который будет создавать дополнительные потоки. Каждый поток будет работать с одной переменной (свойство класса). Для обеспечения сохранности данных мы будем использовать оператор lock. Его синтаксис очень простой:
lock (object1)
Как он работает? Когда один из потоков, доходит до оператора lock, он проверяет, не заблокирован ли object1. Если нет — выполняет указанные в скобках операторы. Если заблокирован — ждет, когда object1 будет разблокирован. Таким образом, оператор lock предотвращает одновременное обращение нескольких потоков к одним и тем же данным.
А вот и пример программы:
using System; using System.Threading; namespace ThreadingClass < class Program < static void Main(string[] args) < //Создаем объект нашего класса Worker worker = new Worker(); //Запускаем создание потоков worker.Run(); //Ждем ввода от пользователя, что бы не закрылась консоль Console.Read(); >> //Класс, который создает потоки class Worker < //Переменная для демонстрации работы оператора lock private int value = 0; //Переменная «локер», которая служит для блокировки value private object valueLocker = new object(); //Метод запускающий потоки public void Run() < for (int i = 0; i < 5; ++i) < Thread thread = new Thread(ThreadFunction); thread.Start(); >> private void ThreadFunction() < //Блокируем доступ к локеру lock (valueLocker) < //Выводим значение value Console.WriteLine(value); //Увеличиваем его на единицу ++value; >> > >
Я использовал отдельную переменную, для оператора lock, поскольку он не может заблокировать доступ к int переменной. А вообще, мне как то на хабре посоветовали всегда использовать «локеры» для блокировки других данных.
Теперь о программе. Каждый поток сначала выводит значение свойства value, а потом увеличивает его на единицу. Зачем же тут нужен оператор lock? Запустите программу. Она выведет по порядку «0 1 2 3 4». Потоки по очереди выводят значение value, и все хорошо.
Предположим, что оператора lock нету. Тогда может получиться следующее:
- Первый поток выведет 0 на экран;
- Второй поток выведет 0 на экран и увеличит значение на единицу;
- Третий поток выведет 1 на экран;
- Первый поток увеличит значение на единицу;
- Третий поток увеличит значение на единицу;
- Второй поток выведет 3 на экран;
… и так далее. В результате мы получим, что-то вроде «0 0 1 3 4». Это связано с тем, что процессор и планировщик задач не знают в каком порядке выполняться операции, поэтому они делают это оптимально с точки зрения выполнения программы, при этом логика нарушается.
Думаю, на сегодня хватит, и так много получилось. Основные примеры можно скачать здесь. Пишите комментарии, отзывы и пожелания. Задавайте свои ответы. Удачной компиляции.
Похожие записи:
- Критические секции в С++
- GUI обертка для консольного приложения на C#
- Работа с hex значениями в C#
- Работа с реестром Windows, используя C# (.NET)
Источник: jakeroid.com