Работа с потоками
Свойства потока
Каждый поток имеет ряд свойств: Name - имя потока, ManagedThreadId – номер потока, IsAlive - признак существования потока, IsBackground – признак фонового потока, ThreadState – состояние потока. Эти свойства доступны и для внешнего вызова.
// Объявляем массив потоков Thread[] arThr = new Thread[N]; for(int i=0; i<arThr.Length; i++) { arThr[i] = new Thread(SomeFunc); arThr[i].Start(); } for(int i=0; i<arThr.Length; i++) { // Выводим информацию о потоках Console.WriteLine("Thread Id: {0}, name: {1}, IsAlive: {2}", arThr[i].ManagedThreadId, arThr[i].Name, arThr[i].IsAlive); }
Свойства текущего потока можно получить с помощью объекта Thread.CurrentThread. Следующий фрагмент с помощью механизма рефлексии выводит все свойства текущего потока.
using System; using System.Reflection; using System.Threading; class ThreadInfo { static void Main() { Thread t = Thread.CurrentThread; t.Name = "MAIN THREAD"; foreach(PropertyInfo p in t.GetType().GetProperties()) { Console.WriteLine("{0}:{1}", p.Name,p.GetValue(t, null)); } }
}
Вывод всех свойств текущего потока:
ManagedThreadId: 10 ExecutionContext: System.Threading.ExecutionContext Priority: Normal IsAlive: True IsThreadPoolThread: False CurrentThread: System.Threading.Thread IsBackground: False ThreadState: Running ApartmentState: MTA CurrentUICulture: ru-RU CurrentCulture: ru-RU CurrentContext: ContextID: 0 CurrentPrincipal: System.Security.Principal.GenericPrincipal Name: MAIN THREAD
Приоритеты потоков
Приоритеты потоков определяют очередность выделения доступа к ЦП. Высокоприоритетные потоки имеют преимущество и чаще получают доступ к ЦП, чем низкоприоритетные. Приоритеты потоков задаются перечислением ThreadPriority, которое имеет пять значений: Highest - наивысший, AboveNormal – выше среднего, Normal - средний, BelowNormal – ниже среднего, Lowest - низший. По умолчанию поток имеет средний приоритет. Для изменения приоритета потока или чтения текущего используется свойство Priority. Влияние приоритетов сказывается только в случае конкуренции множества потоков за мощности ЦП.
В следующем фрагменте пять потоков с разными приоритетами конкурируют за доступ к ЦП с двумя ядрами. Каждый поток увеличивает свой счетчик.
class PriorityTesting { static long[] counts; static bool finish; static void ThreadFunc(object iThread) { while(true) { if(finish) break; counts[(int)iThread]++; } } static void Main() { counts = new long[5]; Thread[] t = new Thread[5]; for(int i=0; i<t.Length; i++) { t[i] = new Thread(ThreadFunc); t[i].Priority = (ThreadPriority)i; } // Запускаем потоки for(int i=0; i<t.Length; i++) t[i].Start(i); // Даём потокам возможность поработать 10 c Thread.Sleep(10000); // Сигнал о завершении finish = true; // Ожидаем завершения всех потоков for(int i=0; i<t.Length; i++) t[i].Join(); // Вывод результатов for(int i=0; i<t.Length; i++) Console.WriteLine("Thread with priority {0, 15}, Counts: {0}", (ThreadPriority)i, counts[i]); } }
Thread with priority Lowest, Counts: 7608195 Thread with priority BelowNormal, Counts: 10457706 Thread with priority Normal, Counts: 17852629 Thread with priority AboveNormal, Counts: 297729812 Thread with priority Highest, Counts: 302506232
Локальное хранилище потока
Типы, объявленные внутри рабочей функции потока, являются локальными – у каждого потока, выполняющего функцию, свои копии (приватные данные). Типы, объявленные вне рабочей функции потока, являются общими для всех потоков переменными. Изменения общих данных в одном потоке отражаются в другом потоке.
Существует возможность оперировать с локальными данными потока, объявленными вне рабочей функции, например, в общедоступном классе. Первый способ заключается в объявлении статического поля, локального для каждого потока.
В следующем фрагменте рабочая функция потока использует объект пользовательского типа Data. В этом классе
public class Data { public static int sharedVar; [ThreadStatic] static int localVar; } class Program { static void threadFunc(object i) { Console.WriteLine("Thread {0}: Before changing.. Shared: {1}, local: {2}", i, Data.sharedVar, Data.localVar); Data.sharedVar = (int)i; Data.localVar = (int)i; Console.WriteLine("Thread {0}: After changing.. Shared: {1}, local: {2}", i, Data.sharedVar, Data.localVar); } static void Main() { Thread t1 = new Thread(threadFunc); Thread t2 = new Thread(threadFunc); Data.sharedVar = 3; Data.localVar = 3; t1.Start(1); t2.Start(2); t1.Join(); t2.Join(); } }
Thread 1: Before changing.. Shared: 3, local: 0 Thread 1: After changing.. Shared: 1, local: 1 Thread 2: Before changing.. Shared: 1, local: 0 Thread 2: After changing.. Shared: 2, local: 2
Ограничения этого способа связаны с тем, что атрибут используется только со статическими полями и инициализация поля осуществляется только одним потоком.
Второй способ объявления локальных данных заключается в использовании объекта ThreadLocal<T>:
static void Main() { ThreadLocal<int> localSum = new ThreadLocal<int>(() => 0); t1 = new Thread(() => { for(int i=0; i<10; i++) localSum.Value++; Console.WriteLine(localSum.Value); }); t2 = new Thread(() => { for(int i=0; i<10; i++) localSum.Value--; Console.WriteLine(localSum.Value); }); t1.Start(); t2.Start(); t1.Join(); t2.Join(); Console.WriteLine(localSum.Value); }
Получаем:
10 -10 0
Первый поток увеличивал свой счетчик, второй уменьшал, а третий (главный поток) ничего не делал со своим локальным счетчиком, поэтому получаем 0.
Третий способ заключается в использовании локальных слотов потока. Доступ к слотам обеспечивается с помощью методов GetData, SetData. Объект, идентифицирующий слот, можно получить с помощью строковой константы. Применение слотов является менее производительным по сравнению с локальными статическими полями. Но может быть полезным, если работа потока структурирована в нескольких методах. В каждом методе можно получить доступ к слоту потока по его имени.
public class ThreadWork { private string sharedWord; public void Run(string secretWord) { sharedWord = secretWord; Save(secretWord); Thread.Sleep(500); Show(); } private void Save(string s) { // Получаем идентификатор слота по имени LocalDataStoreSlot slot = Thread.GetNamedDataSlot("Secret"); // Сохраняем данные Thread.SetData(slot, s); } private void Show() { LocalDataStoreSlot slot = Thread.GetNamedDataSlot("Secret"); string secretWord = Thread.GetData(slot); Console.WriteLine("Thread {0}, secret word: {1}, shared word: {2}", Thread.CurrentThread.ManagedThreadId, secretWord, sharedWord); } } class Program { static void Main() { ThreadWork thr = new ThreadWork(); new Thread(() => thr.Run("one")).Start(); new Thread(() => thr.Run("two")).Start(); new Thread(() => thr.Run("three")).Start(); Thread.Sleep(1000); } }
Thread: 15, secret word: one, shared word: three Thread: 16, secret word: two, shared word: three Thread: 17, secret word: three, shared word: three
Переменная sharedWord является разделяемой, поэтому выводится последнее изменение, выполненное третьим потоком.