Опубликован: 05.03.2013 | Доступ: свободный | Студентов: 2195 / 622 | Длительность: 07:41:00
Лекция 3:

Работа с потоками

Свойства потока

Каждый поток имеет ряд свойств: 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 является разделяемой, поэтому выводится последнее изменение, выполненное третьим потоком.