0cfdde6894c0ee05b83eaf3ed824b39b.ppt
- Количество слайдов: 186
Програмиране за. NET Framework http: //www. nakov. com/dotnet-project/ Многонишково програмиране и синхронизация Михаил Стойнов Софтуерен инженер, Sciant Inc. www. sciant. com Microsoft Student Consultant www. fmi. uni-sofia. bg/microsoft/
Необходими знания u u u Базови познания за. NET Framework и CLR Базови познания за общата система от типове в. NET (Common Type System) Базови познания за езика C# Базови познания по операционни системи Атрибути
Съдържание u u u Многозадачност Нишки Синхронизация Асинхронни извиквания Забележка: Под някои слайдове в полето забележки има подробно описание на съдържанието на слайда
Многозадачност – съдържание u Многозадачност v v v v v u u u Проблемът – защо многозадачност? Ползите от многозадачността Решението – процеси и нишки Прилики и разлики Какво предлагат? Кога са удобни нишките? Многозадачност – видове Имплементации на многозадачност Application Domains Нишки Синхронизация Асинхронни извиквания
Проблемът u u u Има случаи, в които вашето приложение трябва да изпълни времеотнемащи операции или да чака за освобождаването на ресурс Има случаи, в които вашето приложение трябва да извършва операция на заден план Нужен е механизъм, който да позволява няколко операции да се извършват едновременно
Ползите от многозадачността u u u Performance – на машината с няколко процесора работи по-бързо Responsiveness – системата отговаря максимално бързо при интерактивна работа Throughput – подобряване на производителността v u Пример със супермаркета – обслужване на няколко каси едновременно Обслужване на много потребители едновременно
Демонстрация #1 u Защо е нужна многозадачност
Решението – процеси и нишки u Процесът представлява съвкупността от памет, стек и кода на приложението u OS използват процеси (process), за да разграничават различните приложения u Нишката (thread) е основната единица, на която OS може да заделя процесорно време u В един процес различните задачи се изпълняват от отделни нишки u В един процес има поне една нишка
Процес vs. Нишка u Прилики v v v u собствен стек (stack) приоритет exception handlers Разлики v v v Процесите са изолирани един от друг по отношение на памет и данни Нишките в един процес споделят паметта (променливите и данните) на този процес Процесите съдържат изпълнимия код, а нишките го изпълняват
Какво предлагат нишките? u Използването на няколко нишки v v u Създава впечатление за извършване на няколко задачи едновременно Причината: Времето, през което една нишка държи процесора е много кратко Например v v Потребителят може да въвежда данни в текстообработваща програма докато се печатат данни на принтер Би било недопустимо ако потребителят трябваше да изчака принтерът да свърши работата си
Кога са удобни нишките? u Случаи, в които е удобно да се използват нишки: v v v Обслужване на много потребители едновременно, например Web сървър Комуникация през мрежа (sockets) Извършване на времеотнемаща операция Различаване на задачите по приоритет За да може потребителският интерфейс да продължи да “откликва” на потребителски заявки, докато на заден план се извършва друга задача
Многозадачност – Изпреварваща и кооперативна u Кооперативна многозадачност (cooperative multitasking) v v u Нишката сама решава колко процесорно време й е необходимо Сваля се от процесора само ако е свършила или чака за някакъв ресурс Изпреварваща многозадачност (preemptive multitasking) v v Планировчикът (task scheduler) заделя предварително някакво процесорно време Без значение дали е приключила, нишката се снема от процесора
Многозадачност – Изпреварваща и кооперативна u При кооперативната многозадачност: v v u Една нишка може дълго време да държи процесора Заради нея останалите нишки могат да чакат недопустимо дълго Някои системи използват комбиниран вариант v Нишки с висок приоритет се държат кооперативно спрямо нишките с понисък приоритет
Имплементации на многозадачност Apartment Process Global Data Thread Instruction Instruction Apartment Threading Free Threading
Application Domain u u . NET Framework добавя още едно ниво на абстракция между нишка и процес – Application Domain App. Domain-ът дава логическа изолация v u Предимства v v u При процесите изолацията е физическа, тъй като е имплементиран на ниво OS Подобрена производителност Ниво на изолация – като при процеса Връзка м/у нишки без proxy Извършва type checking System. App. Domain
System. App. Domain #1 App. Domain #2 App. Domain #3 Global Data Thread Instruction Thread App. Domain #4 Thread Instruction Global Data Instruction
Нишки – съдържание u u Многозадачност Нишки v v v v v u u Как работят нишките Живот на нишките По-важни членове Приоритет Състояния Thread Local Storage Thread-Relative Static Fields Прекратяване Неудобства Повреждане на данни Синхронизация Асинхронни извиквания
Как работи многонишковостта Pull From Queue Thread Instruction Thread Interrupt Thread Return to Queue Running Queue Time Slice Instruction
Малък пример class First. Thread { public void Do. Task 1() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 1: job({0})", i); } public void Do. Task 2() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 2: job({0})", i); } } class Starter { static void Main(string[] args) { First. Thread ft = new First. Thread(); Thread t 1 = new Thread( new Thread. Start(ft. Do. Task 1)); Thread t 2 = new Thread( new Thread. Start(ft. Do. Task 2)); t 1. Start(); t 2. Start(); } }
Малък пример class First. Thread { public void Do. Task 1() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 1: job({0})", i); } public void Do. Task 2() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 2: job({0})", i); } } class Starter { static void Main(string[] args) { First. Thread ft = new First. Thread(); Thread t 1 = new Thread( new Thread. Start(ft. Do. Task 1)); Thread t 2 = new Thread( new Thread. Start(ft. Do. Task 2)); t 1. Start(); t 2. Start(); } }
Малък пример class First. Thread { public void Do. Task 1() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 1: job({0})", i); } public void Do. Task 2() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 2: job({0})", i); } } class Starter { static void Main(string[] args) { First. Thread ft = new First. Thread(); Thread t 1 = new Thread( new Thread. Start(ft. Do. Task 1)); Thread t 2 = new Thread( new Thread. Start(ft. Do. Task 2)); t 1. Start(); t 2. Start(); } }
Малък пример class First. Thread { public void Do. Task 1() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 1: job({0})", i); } public void Do. Task 2() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 2: job({0})", i); } } class Starter { static void Main(string[] args) { First. Thread ft = new First. Thread(); Thread t 1 = new Thread( new Thread. Start(ft. Do. Task 1)); Thread t 2 = new Thread( new Thread. Start(ft. Do. Task 2)); t 1. Start(); t 2. Start(); } }
Малък пример class First. Thread { public void Do. Task 1() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 1: job({0})", i); } public void Do. Task 2() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 2: job({0})", i); } } class Starter { static void Main(string[] args) { First. Thread ft = new First. Thread(); Thread t 1 = new Thread( new Thread. Start(ft. Do. Task 1)); Thread t 2 = new Thread( new Thread. Start(ft. Do. Task 2)); t 1. Start(); t 2. Start(); } }
Малък пример class First. Thread { public void Do. Task 1() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 1: job({0})", i); } public void Do. Task 2() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 2: job({0})", i); } } class Starter { static void Main(string[] args) { First. Thread ft = new First. Thread(); Thread t 1 = new Thread( new Thread. Start(ft. Do. Task 1)); Thread t 2 = new Thread( new Thread. Start(ft. Do. Task 2)); t 1. Start(); t 2. Start(); } }
Малък пример class First. Thread { public void Do. Task 1() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 1: job({0})", i); } public void Do. Task 2() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 2: job({0})", i); } } class Starter { static void Main(string[] args) { First. Thread ft = new First. Thread(); Thread t 1 = new Thread( new Thread. Start(ft. Do. Task 1)); Thread t 2 = new Thread( new Thread. Start(ft. Do. Task 2)); t 1. Start(); t 2. Start(); } }
Малък пример //Резултат: Thread 1: job(1) Thread 1: job(2) Thread 2: job(1) Thread 1: job(3). . . Thread 1: job(53) Thread 2: job(46) Thread 2: job(47) Thread 1: job(54). . . Thread 1: job(97) Thread 2: job(99) Thread 1: job(98) Thread 1: job(99)
Демонстрация #2 u Нишки – малък пример
Жизнен цикъл на нишките Start() Is. Alive Suspend() Sleep() Wait. X() All Done Join() Suspended Wait. Sleep. Join Interrupt() Resume() Expire time Interrupt() Notified Aimed Thread Destroyed Abort() Stopped Thread Stopped
По-важните членове на Thread u public Thread( Thread. Start ); v v u u u u Създава инстанция Подава се делегат с метод, който да се изпълни при стартиране Sleep() Suspend() Resume() Is. Alive Is. Background Is. Thread. Pool. Thread Name Priority Thread. State Abort() Interrupt() Join() Start()
По-важните членове на Thread u public Thread( Thread. Start start); u Sleep() v v u u u “Приспива” текущата нишка за указания брой милисекунди (и наносекунди) Извиква се само от самата нишка Suspend() Resume() Is. Alive Is. Background Is. Thread. Pool. Thread Name Priority Thread. State Abort() Interrupt() Join() Start()
Малко повече за Sleep() T T Instruction Thread Sleep() Thread Clock Interrupt Running Queue T To Sleep Queue T Thread from Q ueue Instruction T
Приспиване на нишка using System; using System. Threading; public class Thread. Sleep { public static Thread worker; public static Thread worker 2; public static void Main() { Console. Write. Line("Entering void Main!"); worker = new Thread(new Thread. Start(Counter)); worker 2 = new Thread(new Thread. Start(Counter 2)); worker. Start(); worker 2. Start(); Console. Write. Line("Exiting void Main!"); } (примерът продължава)
Приспиване на нишка } public static void Counter() { Console. Write. Line("Entering Counter"); for(int i = 1; i < 50; i++) { Console. Write(i + " "); if(i == 10) Thread. Sleep(1000); } Console. Write. Line("n. Exiting Counter"); } public static void Counter 2() { Console. Write. Line("Entering Counter 2"); for(int i = 51; i < 100; i++) { Console. Write(i + " "); if( i == 70 ) Thread. Sleep(5000); } Console. Write. Line(“n. Exiting Counter 2"); }
Демонстрация #3 u Използване на Thread. Sleep()
По-важните членове на Thread u public Thread( Thread. Start start); Sleep() u Suspend() u v v u u u Ако нишката е в състояние Running, я преустановява временно (suspend) ако е преустановена, не се случва нищо Resume() Is. Alive Is. Background Is. Thread. Pool. Thread Name Priority Thread. State Abort() Interrupt() Join() Start()
По-важните членове на Thread u public Thread( Thread. Start start); Sleep() Suspend() u Resume() u u v v u u u u u Подновява нишка, която е била преустановена (suspended) Ако нишката работи, не прави нищо Is. Alive Is. Background Is. Thread. Pool. Thread Name Priority Thread. State Abort() Interrupt() Join() Start()
Демонстрация #4 u Използване на Suspend() и Resume()
По-важните членове на Thread u public Thread( Thread. Start start); Sleep() Suspend() Resume() u Is. Alive u u u v v u u u u u true, ако е стартирана и не е спряна прекъсната или прекратена Повече информация дава Thread. State Is. Background Is. Thread. Pool. Thread Name Priority Thread. State Abort() Interrupt() Join() Start()
По-важните членове на Thread u public Thread( Thread. Start start); Sleep() Suspend() Resume() Is. Alive u Is. Background u u v v u u u u Преден план (foreground) и фонов режим (background) Свойство за смяна/извличане Is. Thread. Pool. Thread Name Priority Thread. State Abort() Interrupt() Join() Start()
По-важните членове на Thread u public Thread( Thread. Start start); Sleep() Suspend() Resume() Is. Alive Is. Background u Is. Thread. Pool. Thread u u u v v u u u u Свойство за смяна/извличане true, ако нишката принадлежи на managed thread pool, иначе false Name Priority Thread. State Abort() Interrupt() Join() Start()
По-важните членове на Thread u public Thread( Thread. Start start); Sleep() Suspend() Resume() Is. Alive Is. Background Is. Thread. Pool. Thread u Name u u u v v u u u Всяка нишка в. NET Framework може да име Свойство за смяна/извличане на името Priority Thread. State Abort() Interrupt() Join() Start()
По-важните членове на Thread u public Thread( Thread. Start start); Sleep() Suspend() Resume() Is. Alive Is. Background Is. Thread. Pool. Thread Name u Priority u u u u v v u u u Lowest, Below. Normal, Normal (по подразбиране), Above. Normal, Highest Свойство за промяна/извличане Thread. State Abort() Interrupt() Join() Start()
По-важните членове на Thread u public Thread( Thread. Start start); Sleep() Suspend() Resume() Is. Alive Is. Background Is. Thread. Pool. Thread Name Priority u Thread. State u u u u v v u u Съдържа състоянието на нишката – зависи от това дали нишката работи Свойство само за извличане Abort() Interrupt() Join() Start()
По-важните членове на Thread u public Thread( Thread. Start start); Sleep() Suspend() Resume() Is. Alive Is. Background Is. Thread. Pool. Thread Name Priority Thread. State u Abort() u u u u u v v u u u Обикновено “убива” нишката Хвърля Thread. Abort. Exception в извиканата нишка Interrupt() Join() Start()
По-важните членове на Thread u public Thread( Thread. Start start); Sleep() Suspend() Resume() Is. Alive Is. Background Is. Thread. Pool. Thread Name Priority Thread. State Abort() u Interrupt() u u u u u v v u u Събужда нишка ако е в състояние Wait. Sleep. Join, иначе не прави нищо Хвърля Thread. Interrupted. Exception Join() Start()
По-важните членове на Thread u public Thread( Thread. Start start); Sleep() Suspend() Resume() Is. Alive Is. Background Is. Thread. Pool. Thread Name Priority Thread. State Abort() Interrupt() u Join() u u u v v u Извикващата нишка изчаква, докато извиканата приключи Може да се укаже таймаут (timeout) Start()
Демонстрация #5 u Използване на Thread. Join()
По-важните членове на Thread u public Thread( Thread. Start start); Sleep() Suspend() Resume() Is. Alive Is. Background Is. Thread. Pool. Thread Name Priority Thread. State Abort() Interrupt() Join() u Start() u u u v v Стартира посочената нишка Операцията не е блокираща (връща управлението веднага)
Приоритет на нишка u Повечето имплементации на многонишковост включват отличителното качество "приоритет" v v u Приоритетът указва колко важна е нишката (колко време да й се отделя) Важен е за планировчика (task scheduler) Приоритетът в. NET Framework v Възможни стойности v v v Lowest, Below. Normal, Normal (по подразбиране), Above. Normal, Highest В Win 32 има и приоритет Real. Time OS не е длъжна да се съобразява с приоритета на нишките, но обикновено го прави
Приоритет – пример class First. Thread { public void Do. Task 1() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 1: job({0})", i); } public void Do. Task 2() { for( int i=0; i<100; i++ ) Console. Write. Line("Thread 2: job({0})", i); } } class Starter static void Main(string[] args) { First. Thread ft = new First. Thread(); Thread t 1 = new Thread( new Thread. Start(ft. Do. Task 1)); Thread t 2 = new Thread( new Thread. Start(ft. Do. Task 2)); t 1. Priority = Thread. Priority. Highest; t 1. Start(); t 2. Start(); } }
Приоритет Резултат: Thread 1: job(1) Thread 1: job(2) Thread 2: job(1) Thread 1: job(3). . . Thread 1: job(97) Thread 1: job(98) Thread 2: job(85) Thread 1: job(99) Thread 2: job(86). . . Thread 2: job(97) Thread 2: job(98) Thread 2: job(99)
Thread. State u u u Всяка нишка във всеки един момент е в някое от състоянията на енумерацията Thread. State Допуска се да бъде в няколко състояния едновременно Енумерацията има атрибут Flags. Attribute v Това позволява побитово комбиниране на стойностите й: if ((state & // implies u (Unstarted | Stopped)) == 0) Running Когато се създаде нишка е в състояние Unstarted v Остава така докато не се извика Start()
Състоянията в Thread. State u Aborted(256) v v u u u u u Извикан е метода Abort() Нишката е в състояние Stopped, едновременно с Aborted Abort. Requested(128) Background(4) Running(0) Stopped(16) Stop. Requested(1) Suspended(64) Suspend. Requested(2) Unstarted(8) Wait. Sleep. Join(32)
Състоянията в Thread. State u Aborted(256) u Abort. Requested(128) v v u u u u Методът Abort() е бил извикан Още не е получила Thread. Abort. Exception, което ще се опита да я прекрати Background(4) Running(0) Stopped(16) Stop. Requested(1) Suspended(64) Suspend. Requested(2) Unstarted(8) Wait. Sleep. Join(32)
Състоянията в Thread. State u Aborted(256) Abort. Requested(128) u Background(4) u v v u u u u Нишката е във фонов режим Променя се със свойството Thread. Is. Background Running(0) Stopped(16) Stop. Requested(1) Suspended(64) Suspend. Requested(2) Unstarted(8) Wait. Sleep. Join(32)
Състоянията в Thread. State u Aborted(256) Abort. Requested(128) Background(4) u Running(0) u u v v u u u Нишката е стартирана и не е блокирана Няма чакащо изключение Thread. Aborted. Exception Stopped(16) Stop. Requested(1) Suspended(64) Suspend. Requested(2) Unstarted(8) Wait. Sleep. Join(32)
Състоянията в Thread. State u Aborted(256) Abort. Requested(128) Background(4) Running(0) u Stopped(16) u u u v v u u u Нишката е отговорила на заявка от Abort() или Прекратила е работата си доброволно Stop. Requested(1) Suspended(64) Suspend. Requested(2) Unstarted(8) Wait. Sleep. Join(32)
Състоянията в Thread. State u Aborted(256) Abort. Requested(128) Background(4) Running(0) Stopped(16) u Stop. Requested(1) u u v v u u Поискано е било от нишката да спре работа Само за вътрешна употреба Suspended(64) Suspend. Requested(2) Unstarted(8) Wait. Sleep. Join(32)
Състоянията в Thread. State u Aborted(256) Abort. Requested(128) Background(4) Running(0) Stopped(16) Stop. Requested(1) u Suspended(64) u u u v v u u u Нишката е била преустановена Независимо колко пъти е извикан Suspend(), един Resume() е достатъчен Suspend. Requested(2) Unstarted(8) Wait. Sleep. Join(32)
Състоянията в Thread. State u Aborted(256) Abort. Requested(128) Background(4) Running(0) Stopped(16) Stop. Requested(1) Suspended(64) u Suspend. Requested(2) u u u v v u u Извикан е метода Suspend() Изчаква се да стигне до стабилно състояние, за да се преустанови Unstarted(8) Wait. Sleep. Join(32)
Състоянията в Thread. State u Aborted(256) Abort. Requested(128) Background(4) Running(0) Stopped(16) Stop. Requested(1) Suspended(64) Suspend. Requested(2) u Unstarted(8) u u u u v v u Не е стартирана, стартира се със Start() След като се стартира нишката никога повече не може пак да е в това състояние Wait. Sleep. Join(32)
Състоянията в Thread. State u Aborted(256) Abort. Requested(128) Background(4) Running(0) Stopped(16) Stop. Requested(1) Suspended(64) Suspend. Requested(2) Unstarted(8) u Wait. Sleep. Join(32) u u u u v v Нишката е блокирана Става с един от методите Thread. Wait(), Thread. Sleep(), Thread. Join()
Прекратяване на нишки u u За да се “убие” една нишка, се използва методът Thread. Abort() хвърля специалното изключение Thread. Aborted. Exception v u u u Хвърля Thread. State. Exception, ако нишката вече е прекратена Изпълняват се catch и finally на прекратената нишка Thread. Reset. Abort() не позволява на CLR да хвърли изключението отново Unmanaged code – изчакване
Прекратяване на нишки – пример public class Background. Thread { public void Do. Background. Job() { try { // Нишката извършва някаква работа } } } catch(Thread. Interrupted. Exception) { Message. Box. Show("Thread interrupted. "); } catch(Thread. Abort. Exception) { Message. Box. Show("Thread aborted. "); } finally { Message. Box. Show("Finally block. "); } Message. Box. Show("After finally block. "); (примерът продължава)
Прекратяване на нишки – пример public class Interrupt. Abort. Demo { private Thread m. Bg. Thread; public Interrupt. Abort. Demo() { bg = new Background. Thread(); m. Bg. Thread = new Thread(new Thread. Start(bg. Do. Background. Job)); m. Bg. Thread. Is. Background = true; m. Bg. Thread. Start(); } private void btn. Suspend_Click(. . . ) { m. Bg. Thread. Suspend(); } (примерът продължава)
Прекратяване на нишки – пример private void btn. Resume_Click(. . . ) { m. Bg. Thread. Resume(); } private void btn. Interrupt_Click(. . . ) { m. Bg. Thread. Interrupt(); } } private void btn. Abort_Click(. . . ) { m. Bg. Thread. Abort(); }
Демонстрация #6 u Използване на Priority, State, Suspend, Resume, Interrupt и Abort
Thread Local Storage u u u Контейнер, в който нишките могат да съхраняват данни Нишка не може да достъпи данните на друга нишка Thread. Allocate. Named. Data. Slot v v u Thread. Allocate. Data. Slot v u Именуван контейнер Проблеми – уникални имена Няма име, само reference Проблеми: две парчета код от една и съща нишка използват общ слот
Thread Local Storage – пример class Threads { public void Create. Data. Thread() { Local. Data. Store. Slot slot = Thread. Allocate. Named. Data. Slot("my. Slot"); //Записваме важни данни в Named. Slot Thread. Set. Data(slot , "IMPORTANT DATA"); // Suspend-ваме Thread. Current. Thread. Suspend(); // Прочитаме отново данните object my. Data = Thread. Get. Data("my. Slot"); } public void Read. Data. Thread() { Local. Data. Store. Slot slot = Thread. Get. Named. Data. Slot("my. Slot"); // Опитваме се да променим информацията } } Thread. Set. Data(slot, "BAD DATA"); (примерът продължава)
Thread Local Storage – пример class TLSDemo { static void Main(string[] args) { Threads threads = new Threads(); Thread create. Data = new Thread( new Thread. Start(threads. create. Data. Thread) ); create. Data. Start(); Thread. Data = new Thread( new Thread. Start(threads. read. Data. Thread) ); read. Data. Start(); read. Data. Join(); create. Data. Resume(); } }
Демонстрация #7 u Thread. Local. Storage
Thread-Relative Static Fields u u Като статична променлива, но има по една инстанция за всяка нишка на програмата Декларира се като статична променлива, но с атрибута [Thread. Static] Всяка нишка, която я използва трябва да я инициализира Не разчитайте на конструктора да инициализира такива променливи, защото той се изпълнява в родителската нишка!
Thread-Relative Static Fields – пример class Thread. Static { static void Main(string[] args) { for( int i=0; i<10; i++ ) { new Thread(new Thread. Start(new My. Thread(). Do. Task)). Start(); } } } class My. Thread { [Thread. Static] public static int abc; public My. Thread() { abc=42; // This runs in the main app. thread } public void Do. Task() { abc++; Console. Write. Line("abc={0}", abc); } }
Демонстрация #8 u Използване на Thread Static поле
Неудобства на нишките u u Самата имплементация на многонишковост изисква ресурси Добра практика е да не прекалявате с използването на нишки v u u Възможно е процесорът да използва повече ресурси за управлението на нишките отколкото за изпълнението им Препоръчва се да използвате Thread. Pool когато е възможно Следенето на много нишки е трудно
Споделени данни – проблеми u Race condition v v v u Две нишки едновременно достъпват едни и същи данни Непредвидими резултати Например две нишки едновременно изпълняват i++ на обща променлива i Deadlock v v Състояние, в което две нишки се чакат взаимно за освобождаване на взаимно заети ресурси A заема ресурса X, B заема ресурса Y и чака за X, а A започва да чака за Y
Race Condition – пример using System; using System. Threading; class Account { public int m. Balance; public void Withdraw 100() { int old. Balance = m. Balance; Console. Write. Line("Withdrawing 100. . . "); Thread. Sleep(100); int new. Balance = old. Balance - 100; m. Balance = new. Balance; } } (примерът продължава)
Race Condition – пример static void Main(string[] args) { Account acc = new Account(); acc. m. Balance = 500; Console. Write. Line("Account balance = acc. m. Balance); Thread user 1 = new Thread( new Thread. Start(acc. Withdraw 100) Thread user 2 = new Thread( new Thread. Start(acc. Withdraw 100) user 1. Start(); user 2. Start(); user 1. Join(); user 2. Join(); Console. Write. Line("Account balance = acc. m. Balance); } {0}", ); ); {0}",
Демонстрация #9 u Повреждане на данни
Синхронизация – съдържание u u u Многозадачност Нишки Синхронизация v v v v Най-доброто решение “Стратегии” за синхронизация Synchronized Contexts Synchronized code regions Method. Impl. Attribute Unmanaged Synchronization – Wait. Handle Специални класове v v u Класически синхронизационни проблеми Thread. Pool Асинхронни извиквания
Най-доброто решение u u Най-добра сигурност при споделени данни в многонишкова среда е липсата на споделени данни Препоръчва се v v u Да се капсулират данните и ресурсите в инстанции на обекти Тези обекти да не се споделят между нишки Когато това е невъзможно, . NET Framework, предоставя механизми за синхронизация на достъпа до данни
“Стратегии” за синхронизация u u Синхронизирани контексти (Synchronized Contexts) Синхронизирани “пасажи” код (Synchronized code regions) [Method. Impl. Attribute (Method. Impl. Options. Synchronized)] над имплементацията на метод Unmanaged синхронизация v Класове за фина, ръчно настроена (custom) синхронизация с обекти на OS
Synchronized Contexts u Използва се Synchronization. Attribute за обекти, наследяващи Context. Bound. Object v u u u обекти, които оперират в един контекст Всички нишки в този контекст достъпват методите на инстанции на такива обекти последователно Статичните членове не са предпазени Не се поддържа синхронизиране на специфични “отрязъци” от код – синхронизира цял клас
Synchronized Contexts – пример [Synchronization. Attribute] class CBO : Context. Bound. Object { public void Do. Some. Task 1() { Console. Write. Line("Job 1 started. "); Thread. Sleep(2000); Console. Write. Line("Job 1 finished. n"); } } public void Do. Some. Task 2() { Console. Write. Line("Job 2 started. "); Thread. Sleep(1500); Console. Write. Line("Job 2 finished. n"); } (примерът продължава)
Synchronized Contexts – пример static void Main() { CBO sync. Class = new CBO(); Console. Write. Line("Started 6 threads: n" + "3 doing Job 1 and 3 doing Job 2. nn"); for (int i=0; i<6; i++) { Thread t; if (i%2==0) t = new Thread( new Thread. Start( sync. Class. Do. Some. Task 1) ); else t = new Thread( new Thread. Start( sync. Class. Do. Some. Task 2) ); t. Start(); } }
Демонстрация #10 u Синхронизация чрез Context. Bound. Object
Критични секции lock (obj) { //. . code. . } u u Monitor. Enter(obj); try { /*. . code. . */ } finally { Monitor. Exit(obj); } Управлявана (managed) реализация Може да се ползва и за статични членове на класовете Синхронизира се изпълнението само частта, която е застрашена u Нарича се критична секция Monitor. Try. Enter();
Критични секции – пример public class Monitor. Enter. Exit { private int m. Counter; public void Critical. Section() { Monitor. Enter(this); m. Counter = 0; try { for(int i = 1; i <= 5; i++) { Console. Write(++m. Counter); Thread. Sleep(1000); } } finally { Monitor. Exit(this); } } (примерът продължава)
Критични секции – пример static void Main() { Monitor. Enter. Exit mee = new Monitor. Enter. Exit(); Thread thread 1 = new Thread(new Thread. Start(mee. Critical. Section)); thread 1. Start( ); Thread thread 2 = new Thread(new Thread. Start(mee. Critical. Section)); thread 2. Start( ); } } // Резултат: 12345 // Резултат без синхронизация: 1123456789
Демонстрация #11 u Работа с критични секции
Няколко важни метода u Monitor. Wait(object) v v u Monitor. Pulse(object) v v u Освобождава монитора на обекта и блокира нишката докато не го получи Може да се задава Timeout Нишката се нарежда на waiting queue Чака Pulse(object), Pulse. All(object) Нишката преминава в ready queue (има право да вземе монитора на обект) Вика се само от текущия собственик на монитора на обекта (от критична секция) Monitor. Pulse. All(object)
Wait() и Pulse() – пример u u Имаме две нишки, които съвместно изпълняват някаква задача Когато едната прави нещо, другата я изчаква и обратното public class Wait. Pulse { private object m. Sync; private string m. Name; public Wait. Pulse(string a. Name, object a. Sync) { m. Name = a. Name; m. Sync = a. Sync; } (примерът продължава)
Wait() и Pulse() – пример public void Do. Job() { lock (m. Sync) { Monitor. Pulse(m. Sync); for(int i = 1; i <= 3; i++) { Console. Write. Line("{0}: Pulsе", m. Name); Monitor. Pulse(m. Sync); Console. Write. Line("{0}: Wait", m. Name); Monitor. Wait(m. Sync); } } Console. Write. Line("{0}: Woke. Up", m. Name); Thread. Sleep(1000); (примерът продължава)
Wait() и Pulse() – пример public class Wait. Pulse. Demo { public static void Main(String[] args) { object sync = new object(); Wait. Pulse wp 1 = new Wait. Pulse( "Wait. Pulse 1", sync); Thread thread 1 = new Thread( new Thread. Start(wp 1. Do. Job)); thread 1. Start(); } } Wait. Pulse wp 2 = new Wait. Pulse( "Wait. Pulse 2", sync); Thread thread 2 = new Thread( new Thread. Start(wp 2. Do. Job)); thread 2. Start();
Демонстрация #12 u Работа с Wait() и Pulse()
[Method. Impl. Attribute (Method. Impl. Options. Synchronized)] u u Прилича на lock върху цял метод Може да синхронизира и static членове [Method. Impl(Method. Impl. Options. Synchronized)] public void Do. Some. Task 1() { Console. Write. Line("job 1 started"); for (int i=0; i<100000; i++) Math. Sqrt(i); Console. Write. Line("Job 1 done. n"); }
Unmanaged Synchronization u Wait. Handle v v u Unmanaged механизъм за синхронизация Използва обекти на операционната система – за изчакване на ресурси Предоставя възможности отвъд тези на CLR – Wait. All(), Wait. Any() Не се препоръчва – не е managed Има няколко наследника: v v v Mutex Auto. Reset. Event Manual. Reset. Event
Класът Wait. Handle u Неговите методи се използват за изчакване на събития u Състояния: signaled, nonsignaled Wait. All() (static) u v u Wait. Any() (static) v u Изчаква първото получило сигнал Wait. One() v u Изчаква сигнал от всички събития от масив Изчаква текущото събитие да приключи Прилики и разлики с класа Monitor
Mutex u u u Наследник на Wait. Handle Примитив за синхронизация на OS Енкапсулира Win 32 synchronization handles Mutex. Wait. One(); Mutex. Release. Mutex(); v u Трябва да се извика, същия брой пъти като Mutex. Wait. One(); Wait. Handle. Wait. All() – изчаква два или няколко handle-a
Mutex – пример class Mutex. Demo { Mutex m. Mutex; public Mutex. Demo(Mutex a. Mutex) { m. Mutex = a. Mutex; } public void Perform. Some. Task() { m. Wait. One(); Console. Write. Line("n. Job started. . . "); for( int i=0; i<10; i++) { Thread. Sleep(100); Console. Write("|"); } Console. Write. Line("n. Job finished. "); m. Release. Mutex(); } }
Демонстрация #13 u Синхронизация с Mutex
Auto. Reset. Event, Manual. Reset. Event u u Наследници на Wait. Handle Примитиви за синхронизация Енкапсулират Win 32 synchronization handles Сигнализиране със Set() v Auto. Reset. Event сигнализира само първия манипулатор(handle) v v Минава автоматично в nonsignaled state Manual. Reset. Event() сигнализира всички чакащи манипулатори v Остава в signaled state, докато някой не го промени
Auto. Reset. Event/Manual. Reset. Event using System; using System. Threading; class One. Who. Waits { Wait. Handle m. Wait. Handle; int m. Wait. Time; public One. Who. Waits(Wait. Handle a. Wait. Handle, int wait. Time ) { m. Wait. Handle = a. Wait. Handle; m. Wait. Time = m. Wait. Time; } public void perform. Some. Task() { Thread. Sleep(m. Wait. Time); Console. Write. Line("Thread {0} waiting", Thread. Current. Thread. Get. Hash. Code()); m. Wait. Handle. Wait. One(); } } (примерът продължава)
Auto. Reset. Event/Manual. Reset. Event class Main. Class { static void Main() { Manual. Reset. Event evnt = new Manual. Reset. Event(false); for (int i=0; i<10; i++ ) { One. Who. Waits oww = new One. Who. Waits(evnt, (i+1)*500); Thread thread = new Thread( new Thread. Start(oww. perform. Some. Task)); thread. Start(); } for (int i=0; i<10; i++) { Console. Read. Line(); evnt. Set(); } } }
Демонстрация #14 u Синхронизация с Auto. Reset. Event и Manual. Reset. Event
Специални класове u System. Threading. Interlocked v u System. Threading. Thread. Pool v v v u Бърз достъп до нишки Преизползваемост на нишките Програмистът не се грижи за живота на нишките (създаване / унищожаване) System. Threading. Reader. Writer. Lock v u Атомарни операции Класически синхронизационни проблеми – Reader/Writer Problem Synchronized Wrappers
System. Threading. Interlocked u Осигурява изпълнение на атомарни операции – увеличение с 1, намаление с 1, размяна, сравнение и др. u Няма нужда синхронизация на споделените данни u В по-простите случаи синхронизацията може да се избегне с тези методи u Методите не хвърлят изключения
System. Threading. Interlocked u Increment/Decrement v u Exchange v u Атомарна операция за разлика от i++/i -Записва стойността на вторият параметър в първия, връща оригинала Compare. Exchange v v v Има три параметъра Проверява дали първият и третият са равни, ако да, записва втория в първия Използва се при работа с временни променливи
Interlocked – пример class Test. Interlocked. Increment { static long m. Unsafe. Counter = 0; static long m. Safe. Counter = 0; private static void Do. Task() { while (true) { m. Unsafe. Counter++; Interlocked. Increment(ref m. Safe. Counter); } } if (m. Safe. Counter % 10000000 == 0) { Console. Write. Line("Safe={0}, Unsafe={1}", m. Safe. Counter, m. Unsafe. Counter); }
Interlocked – пример static void Main(string[] args) { for (int i=0; i<5; i++) { Thread thread = new Thread( new Thread. Start(Do. Task)); thread. Start(); } } } // // Резултат: Safe=10000000, Safe=20000000, Safe=30000000, Safe=40000000, Safe=50000000, . . . Unsafe=5846325 Unsafe=15846326 Unsafe=25846326 Unsafe=35846325 Unsafe=41356463
Thread Pooling u u u Много нишки прекарват по-голямата част от живота си спейки (Thread. State. Wait. Sleep. Join) или чакайки някакво събитие Други се “събуждат” само за малки периоди, за да проверят истинността на някакво условие или за да свършат нещо дребно Поддържането на много неактивни нишки е излишно, неефективно и консумира ресурси
Thread Pooling u u u TP е подход за намаляване на товара при създаване и унищожаване на нишки При TP се създават група нишки в началото на многонишково приложение Наричат се “работни нишки” (worker threads) Всяка нишка “живее” в т. нар. Thread Pool При нова задача, се използва worker thread, след това се връща обратно в пула TP се грижи за живота и разпределението на задачите върху нишките v Използващият нишки се освобождава от този товар
System. Threading. Thread. Pool u u u . NET Framework имплементира механизма Thread Pooling в класа Thread. Pool Използва се, когато трябва да се свършат много на брой кратки задачи, които могат да работят паралелно Задачите се нареждат на опашка и се изпълняват по няколко едновременно (според броя worker threads) По подразбиране в Thread Pool-ът има лимит от 25 нишки на процесор Един процес може да има само един Thread Pool – той е общ за всички App Domains
System. Threading. Thread. Pool u Thread Pool-ът за даден процес се създава се при първото: v v v u извикване на Queue. User. Work. Item регистриране на таймер регистриране на изчакваща операция с callback метод . NET Framework използва Thread Pooling за: v v v асинхронни извиквания асинхронен вход/изход работа с таймери работа със сокети изчакващи операции
System. Threading. Thread. Pool u u u Ако дадена задача е в Thread Pool, не може да се премахне от него Thread Pooling позволява на OS да оптимизира използването на нишки, тъй като броят им е постоянен Случаи, в които НЕ се препоръчва използването на Thread Pool v v v Ако е нужна контролираща нишка При задачи, отнемащи много време – може да се блокират другите задачи Ако има нужда от синхронизация
Thread. Pool. Queue. User. Work. Item class Thread. Pool. Demo { public static void Long. Task(object a. Param) { Console. Write. Line("Started: {0}. ", a. Param); Thread. Sleep(500); Console. Write. Line("Finished: {0}. ", a. Param); } static void Main() { for (int i=1; i<=100; i++) { string task. Name = "Task" + i; Thread. Pool. Queue. User. Work. Item(new Wait. Callback(Long. Task), task. Name); } } } Console. Read. Line();
Демонстрация #15 u Изпълнение на задачи с Thread Pool
Thread. Pool. Register. Wait For. Single. Object u u Регистрира делегат, който чака за събитие (да бъде сигнализиран) Активира се при: v v u u сигнализиране при настъпване на зададен timeout Процесът по изчакване и извикване се управлява се от Thread Pool-а Може да настрои да се изпълнява делегатът многократно
Thread. Pool. Register. Wait. For. Single. Object using. . . public class Example { public static void Main(string[] args) { Auto. Reset. Event ev =new Auto. Reset. Event(false); object param = "some param"; Registered. Wait. Handle r = Thread. Pool. Register. Wait. For. Single. Object( ev, new Wait. Or. Timer. Callback(Wait. Proc), param, 1000, false ); Console. Read. Line(); Console. Write. Line("signaling. "); ev. Set(); Console. Read. Line(); Console. Write. Line("unregister wait"); r. Unregister(ev); Console. Read. Line(); } public static void Wait. Proc(object param, bool timed. Out) { string cause = "SIGNALED"; if (timed. Out) cause = "TIMED OUT"; Console. Write. Line("Wait. Proc executes; cause = {0}", cause); }}
Thread. Pool. Register. Wait. For. Single. Object using. . . public class Example { public static void Main(string[] args) { Auto. Reset. Event ev =new Auto. Reset. Event(false); object param = "some param"; Registered. Wait. Handle r = Thread. Pool. Register. Wait. For. Single. Object( ev, new Wait. Or. Timer. Callback(Wait. Proc), param, 1000, false ); Console. Read. Line(); Console. Write. Line("signaling. "); ev. Set(); Console. Read. Line(); Console. Write. Line("unregister wait"); r. Unregister(ev); Console. Read. Line(); } public static void Wait. Proc(object param, bool timed. Out) { string cause = "SIGNALED"; if (timed. Out) cause = "TIMED OUT"; Console. Write. Line("Wait. Proc executes; cause = {0}", cause); }}
Thread. Pool. Register. Wait. For. Single. Object using. . . public class Example { public static void Main(string[] args) { Auto. Reset. Event ev =new Auto. Reset. Event(false); object param = "some param"; Registered. Wait. Handle r = Thread. Pool. Register. Wait. For. Single. Object( ev, new Wait. Or. Timer. Callback(Wait. Proc), param, 1000, false ); Console. Read. Line(); Console. Write. Line("signaling. "); ev. Set(); Console. Read. Line(); Console. Write. Line("unregister wait"); r. Unregister(ev); Console. Read. Line(); } public static void Wait. Proc(object param, bool timed. Out) { string cause = "SIGNALED"; if (timed. Out) cause = "TIMED OUT"; Console. Write. Line("Wait. Proc executes; cause = {0}", cause); }}
Thread. Pool. Register. Wait. For. Single. Object using. . . public class Example { public static void Main(string[] args) { Auto. Reset. Event ev =new Auto. Reset. Event(false); object param = "some param"; Registered. Wait. Handle r = Thread. Pool. Register. Wait. For. Single. Object( ev, new Wait. Or. Timer. Callback(Wait. Proc), param, 1000, false ); Console. Read. Line(); Console. Write. Line("signaling. "); ev. Set(); Console. Read. Line(); Console. Write. Line("unregister wait"); r. Unregister(ev); Console. Read. Line(); } public static void Wait. Proc(object param, bool timed. Out) { string cause = "SIGNALED"; if (timed. Out) cause = "TIMED OUT"; Console. Write. Line("Wait. Proc executes; cause = {0}", cause); }}
Thread. Pool. Register. Wait. For. Single. Object using. . . public class Example { public static void Main(string[] args) { Auto. Reset. Event ev =new Auto. Reset. Event(false); object param = "some param"; Registered. Wait. Handle r = Thread. Pool. Register. Wait. For. Single. Object( ev, new Wait. Or. Timer. Callback(Wait. Proc), param, 1000, false ); Console. Read. Line(); Console. Write. Line("signaling. "); ev. Set(); Console. Read. Line(); Console. Write. Line("unregister wait"); r. Unregister(ev); Console. Read. Line(); } public static void Wait. Proc(object param, bool timed. Out) { string cause = "SIGNALED"; if (timed. Out) cause = "TIMED OUT"; Console. Write. Line("Wait. Proc executes; cause = {0}", cause); }}
Thread. Pool. Register. Wait. For. Single. Object using. . . public class Example { public static void Main(string[] args) { Auto. Reset. Event ev =new Auto. Reset. Event(false); object param = "some param"; Registered. Wait. Handle r = Thread. Pool. Register. Wait. For. Single. Object( ev, new Wait. Or. Timer. Callback(Wait. Proc), param, 1000, false ); Console. Read. Line(); Console. Write. Line("signaling. "); ev. Set(); Console. Read. Line(); Console. Write. Line("unregister wait"); r. Unregister(ev); Console. Read. Line(); } public static void Wait. Proc(object param, bool timed. Out) { string cause = "SIGNALED"; if (timed. Out) cause = "TIMED OUT"; Console. Write. Line("Wait. Proc executes; cause = {0}", cause); }}
Thread. Pool. Register. Wait. For. Single. Object using. . . public class Example { public static void Main(string[] args) { Auto. Reset. Event ev =new Auto. Reset. Event(false); object param = "some param"; Registered. Wait. Handle r = Thread. Pool. Register. Wait. For. Single. Object( ev, new Wait. Or. Timer. Callback(Wait. Proc), param, 1000, false ); Console. Read. Line(); Console. Write. Line("signaling. "); ev. Set(); Console. Read. Line(); Console. Write. Line("unregister wait"); r. Unregister(ev); Console. Read. Line(); } public static void Wait. Proc(object param, bool timed. Out) { string cause = "SIGNALED"; if (timed. Out) cause = "TIMED OUT"; Console. Write. Line("Wait. Proc executes; cause = {0}", cause); }}
Thread. Pool. Register. Wait. For. Single. Object using. . . public class Example { public static void Main(string[] args) { Auto. Reset. Event ev =new Auto. Reset. Event(false); object param = "some param"; Registered. Wait. Handle r = Thread. Pool. Register. Wait. For. Single. Object( ev, new Wait. Or. Timer. Callback(Wait. Proc), param, 1000, false ); Console. Read. Line(); Console. Write. Line("signaling. "); ev. Set(); Console. Read. Line(); Console. Write. Line("unregister wait"); r. Unregister(ev); Console. Read. Line(); } public static void Wait. Proc(object param, bool timed. Out) { string cause = "SIGNALED"; if (timed. Out) cause = "TIMED OUT"; Console. Write. Line("Wait. Proc executes; cause = {0}", cause); }}
Thread. Pool. Register. Wait. For. Single. Object using. . . public class Example { public static void Main(string[] args) { Auto. Reset. Event ev =new Auto. Reset. Event(false); object param = "some param"; Registered. Wait. Handle r = Thread. Pool. Register. Wait. For. Single. Object( ev, new Wait. Or. Timer. Callback(Wait. Proc), param, 1000, false ); Console. Read. Line(); Console. Write. Line("signaling. "); ev. Set(); Console. Read. Line(); Console. Write. Line("unregister wait"); r. Unregister(ev); Console. Read. Line(); } public static void Wait. Proc(object param, bool timed. Out) { string cause = "SIGNALED"; if (timed. Out) cause = "TIMED OUT"; Console. Write. Line("Wait. Proc executes; cause = {0}", cause); }}
Thread. Pool. Register. Wait. For. Single. Object using. . . public class Example { public static void Main(string[] args) { Auto. Reset. Event ev =new Auto. Reset. Event(false); object param = "some param"; Registered. Wait. Handle r = Thread. Pool. Register. Wait. For. Single. Object( ev, new Wait. Or. Timer. Callback(Wait. Proc), param, 1000, false ); Console. Read. Line(); Console. Write. Line("signaling. "); ev. Set(); Console. Read. Line(); Console. Write. Line("unregister wait"); r. Unregister(ev); Console. Read. Line(); } public static void Wait. Proc(object param, bool timed. Out) { string cause = "SIGNALED"; if (timed. Out) cause = "TIMED OUT"; Console. Write. Line("Wait. Proc executes; cause = {0}", cause); }}
Thread. Pool. Register. Wait. For. Single. Object using. . . public class Example { public static void Main(string[] args) { Auto. Reset. Event ev =new Auto. Reset. Event(false); object param = "some param"; Registered. Wait. Handle r = Thread. Pool. Register. Wait. For. Single. Object( ev, new Wait. Or. Timer. Callback(Wait. Proc), param, 1000, false ); Console. Read. Line(); Console. Write. Line("signaling. "); ev. Set(); Console. Read. Line(); Console. Write. Line("unregister wait"); r. Unregister(ev); Console. Read. Line(); } public static void Wait. Proc(object param, bool timed. Out) { string cause = "SIGNALED"; if (timed. Out) cause = "TIMED OUT"; Console. Write. Line("Wait. Proc executes; cause = {0}", cause); }}
Демонстрация #16 u Register. Wait. For. Single. Object
Класически синхр. проблеми u The Producer-Consumer Problem v v Един консуматор и един производител споделят общa опашка (shared queue) Изисквания: v v v u Ако опашката е празна, консуматорът блокира докато се появи нещо в нея Ако опашката е пълна, производителят блокира докато не се опразни място Производителят и консуматорът не могат да достъпват опашката едновременно Пример: пощенска кутия Нарича се още bounded buffer problem Няма стандартен клас в. NET
Класически синхр. проблеми u The Readers-Writers Problem v v v Един или повече четеца и един или повече писеца достъпват общ ресурс Пример: достъп до общ файл в OS Условия на Бернщайн: v v v u Няколко четци могат да достъпват общия ресурс Когато писец достъпва ресурса, нито четец нито друг писец може да го достъпва Очаква се нито един писец/четец да не чака безкрайно дълго за споделения ресурс В. NET – Reader. Writer. Lock
System. Threading. Reader. Writer. Lock u u u u Имплементира най-популярния синхронизационен проблем – Reader/Writer Problem Is. Reader. Lock. Held Is. Writer. Lock. Held Acquire. Reader. Lock Acquire. Writer. Lock Release. Reader. Lock Release. Writer. Lock
System. Threading. Reader. Writer. Lock class Resource { Reader. Writer. Lock rwl = new Reader. Writer. Lock(); public void Read() { rwl. Acquire. Reader. Lock(Timeout. Infinite); try { // Many can read, writers are blocked } finally { rwl. Release. Reader. Lock(); } } public void Write() { rwl. Acquire. Writer. Lock(Timeout. Infinite); try { // One can write, readers blocked } finally { rwl. Release. Writer. Lock(); } } }
Класически синхр. проблеми u The Dining Philosophers Problem v v v Няколко философа стоят около кръгла маса и се хранят или мислят Когато се хранят, се нуждаят и от двете клечки от двете им страни Условия: v v Не трябва никой философ да умре от глад Не трябва да се позволява deadlock – когато всеки философ има по една клечка и чака за другата
Custom. Reader. Writer class Custom. Reader. Writer { private int m. Readers = 0; private bool m. Is. Writing = false; public void Read() { lock( this ) { while (m. Is. Writing) Monitor. Wait( this ); m. Readers++; } //. . . READING TAKES PLACE HERE. . . } lock( this ) { m. Readers--; if( m. Readers == 0 ) Monitor. Pulse(this); } (примерът продължава)
Custom. Reader. Writer class Custom. Reader. Writer { private int m. Readers = 0; private bool m. Is. Writing = false; public void Read() { lock( this ) { while (m. Is. Writing) Monitor. Wait( this ); m. Readers++; } //. . . READING TAKES PLACE HERE. . . } lock( this ) { m. Readers--; if( m. Readers == 0 ) Monitor. Pulse(this); } (примерът продължава)
Custom. Reader. Writer class Custom. Reader. Writer { private int m. Readers = 0; private bool m. Is. Writing = false; public void Read() { lock( this ) { while (m. Is. Writing) Monitor. Wait( this ); m. Readers++; } //. . . READING TAKES PLACE HERE. . . } lock( this ) { m. Readers--; if( m. Readers == 0 ) Monitor. Pulse(this); } (примерът продължава)
Custom. Reader. Writer class Custom. Reader. Writer { private int m. Readers = 0; private bool m. Is. Writing = false; public void Read() { lock( this ) { while (m. Is. Writing) Monitor. Wait( this ); m. Readers++; } //. . . READING TAKES PLACE HERE. . . } lock( this ) { m. Readers--; if( m. Readers == 0 ) Monitor. Pulse(this); } (примерът продължава)
Custom. Reader. Writer public void Write() { lock( this ) { while( m. Readers != 0 ) Monitor. Wait( this ); m. Is. Writing = true; } //. . . WRITING TAKES PLACE HERE. . . } } lock( this ) { m. Is. Writing = false; Monitor. Pulse. All( this ); }
Custom. Reader. Writer public void Write() { lock( this ) { while( m. Readers != 0 ) Monitor. Wait( this ); m. Is. Writing = true; } //. . . WRITING TAKES PLACE HERE. . . } } lock( this ) { m. Is. Writing = false; Monitor. Pulse. All( this ); }
Custom. Reader. Writer public void Write() { lock( this ) { while( m. Readers != 0 ) Monitor. Wait( this ); m. Is. Writing = true; } //. . . WRITING TAKES PLACE HERE. . . } } lock( this ) { m. Is. Writing = false; Monitor. Pulse. All( this ); }
Custom. Reader. Writer public void Write() { lock( this ) { while( m. Readers != 0 ) Monitor. Wait( this ); m. Is. Writing = true; } //. . . WRITING TAKES PLACE HERE. . . } } lock( this ) { m. Is. Writing = false; Monitor. Pulse. All( this ); }
Демонстрация #17 u Custom. Reader. Writer
Synchronized Wrappers u u u По подразбиране стандартните колекции (Array. List, Queue, Stack) не са thread safe Hashtable е thread safe (1 писач, N четеца) Synchronized Wrapper е синхронизирана обвивка около колекцията v Връща синхронизирана версия на колекцията Stack safe. Stack = Stack. Synchronized(stack); v u При Hashtable – threadsafe (N писача, N четеца) Свойството ICollection. Sync. Root lock (bit. Array. Sync. Root) { // Perform thread unsafe operations here }
Нишки – препоръчвани практики u u u u Избягвайте нуждата от синхронизация, когато е възможно Използвайте Interlocked вместо синхронизация, където е възможно Пазете се от deadlocks Избягвайте lock(. . ){}, ако е възможно Правете критичните секции възможно найкратки (за да намалите изчакването) Член-променливите не е нужно да са thread safe, ако не се достъпват от други нишки Статичните членове е нужно да са thread safe Използвайте статични данни само за четене (read-only)
Асинхронни извиквания – съдържание u u Многозадачност Нишки Синхронизация Асинхронни извиквания v v v Асинхронни извиквания на методи Къде се поддържа? Асинхронно извикване чрез делегат Design Pattern за асинхр. извиквания Интерфейсът IAsync. Result Резултат от асинхронен метод
Асинхронни извиквания на методи u u По подразбиране един метод се дефинира като синхронен (synchronous) Синхронно извикване на метод (synchronous method call) v u Асинхронно извикване на метод (asynchronous method call) v u Изчаква се неговото приключване и след това се преминава към следващия оператор Без да се чака приключването на метода, се минава на следващия оператор Механизмът на асинхронното извикване използва нишки (Thread. Pool)
Къде се поддържа? u u u u Вход/изход: File IO, Stream IO, Socket IO Мрежови класове: HTTP, TCP Remoting канали (HTTP, TCP) и проксита ASP. NET XML Web-услуги ASP. NET Web-приложения При работа с MSMQ (Microsoft Message Queue) Асинхронни делегати Потребителски класове, дефиниращи асинхронен интерфейс за извикване
Асинхронно извикване чрез делегат u Делегатите предоставят функционалност за асинхронно извикване на синхронен метод v v u Автоматично се генерират Begin. Invoke() и End. Invoke() с правилния брой параметри Генерирането става при създаването на делегат със съответната сигнатура Добавя се функционалност без да се изисква нито ред допълнителен код
Асинхронно извикване чрез делегат using System; using System. Threading; class Async. Call { public delegate int My. Delegate(int a, int b); public int Sum(int a, int b) { Thread. Sleep(3000); return a+b; } static void Main(string[] args) { My. Delegate async. Call = new My. Delegate(new Async. Call(). Sum); Console. Write. Line("Starting method async. ");
Асинхронно извикване чрез делегат IAsync. Result status = async. Call. Begin. Invoke(5, 6, null); Console. Write. Line("Async method is working"); } } Console. Write. Line("Calling End. Invoke()"); int result = async. Call. End. Invoke(status); Console. Write. Line("End. Invoke() returned"); Console. Write. Line("Result={0}", result); // Резултат: Starting method asynchronously Asynchronous method is working now Calling End. Invoke() // тук има пауза, заради Sleep() End. Invoke() returned Result=11
Демонстрация #18 u Асинхронно извикване чрез делегат
Design Pattern за асинхронно програмиране u Случаи, в които се налага изричното имплементиране на асинхронно извикване на метод v v u Ако изричното имплементиране е побързо от извикване с делегат Ако методът трябва да се извиква само асинхронно В такива случаи строго се препоръчва следването на design pattern-a
Асинхронно програмиране u Нека имаме синхронен метод int Sum(int a, int b); u u u Искаме да предоставим асинхронна версия В. NET Framework асинхронното извикване се характеризира с използването на методите Begin. XXXXX и End. XXXXX, където XXXXX е синхронната версия на метода Асинхронната версия на метода Sum е: IAsync. Result Begin. Sum(int a, int b, Async. Callback request. Callback, object state. Object); int End. Sum(IASync. Result ar);
Асинхронно програмиране u Нека имаме синхронен метод int Sum(int a, int b); u u u Искаме да предоставим асинхронна версия В. NET Framework асинхронното извикване се характеризира с използването на методите Begin. XXXXX и End. XXXXX, където XXXXX е синхронната версия на метода Асинхронната версия на метода Sum е: IAsync. Result Begin. Sum(int a, int b, Async. Callback request. Callback, object state. Object); int End. Sum(IASync. Result ar);
Асинхронно програмиране u Нека имаме синхронен метод int Sum(int a, int b); u u u Искаме да предоставим асинхронна версия В. NET Framework асинхронното извикване се характеризира с използването на методите Begin. XXXXX и End. XXXXX, където XXXXX е синхронната версия на метода Асинхронната версия на метода Sum е: IAsync. Result Begin. Sum(int a, int b, Async. Callback request. Callback, object state. Object); int End. Sum(IASync. Result ar);
Асинхронно програмиране u Нека имаме синхронен метод int Sum(int a, int b); u u u Искаме да предоставим асинхронна версия В. NET Framework асинхронното извикване се характеризира с използването на методите Begin. XXXXX и End. XXXXX, където XXXXX е синхронната версия на метода Асинхронната версия на метода Sum е: IAsync. Result Begin. Sum(int a, int b, Async. Callback request. Callback, object state. Object); int End. Sum(IASync. Result ar);
Асинхронно програмиране u Нека имаме синхронен метод int Sum(int a, int b); u u u Искаме да предоставим асинхронна версия В. NET Framework асинхронното извикване се характеризира с използването на методите Begin. XXXXX и End. XXXXX, където XXXXX е синхронната версия на метода Асинхронната версия на метода Sum е: IAsync. Result Begin. Sum(int a, int b, Async. Callback request. Callback, object state. Object); int End. Sum(IASync. Result ar); delegate void Async. Callback(IASync. Result ar);
Асинхронно програмиране u Нека имаме синхронен метод int Sum(int a, int b); u u u Искаме да предоставим асинхронна версия В. NET Framework асинхронното извикване се характеризира с използването на методите Begin. XXXXX и End. XXXXX, където XXXXX е синхронната версия на метода Асинхронната версия на метода Sum е: IAsync. Result Begin. Sum(int a, int b, Async. Callback request. Callback, object state. Object); int End. Sum(IASync. Result ar);
Асинхронно програмиране u Нека имаме синхронен метод int Sum(int a, int b); u u u Искаме да предоставим асинхронна версия В. NET Framework асинхронното извикване се характеризира с използването на методите Begin. XXXXX и End. XXXXX, където XXXXX е синхронната версия на метода Асинхронната версия на метода Sum е: IAsync. Result Begin. Sum(int a, int b, Async. Callback request. Callback, object state. Object); int End. Sum(IASync. Result ar);
IAsync. Result public interface IAsync. Result { object Async. State {get; } Wait. Handle Async. Wait. Handle {get; } bool Completed. Synchronously {get; } bool Is. Completed {get; } } v v v Свойството Async. State, връща същия обект подаден като state. Object на Begin. Invoke() Предоставя се, за да се използва от потребителя, като начин за следене на статуса Не се използва или променя по какъвто и да било начин от извиквания метод
IAsync. Result public interface IAsync. Result { object Async. State {get; } Wait. Handle Async. Wait. Handle {get; } bool Completed. Synchronously {get; } bool Is. Completed {get; } } v Свойството Async. Wait. Handle се използва като параметър на методите Wait. All(), Wait. One() или Wait. Any() на класа Wait. Handle за изчакване приключването на асинхронния метод v Гореописаният механизъм е един от начините за приключване на асинхронен метод
IAsync. Result public interface IAsync. Result { object Async. State {get; } Wait. Handle Async. Wait. Handle {get; } bool Completed. Synchronously {get; } bool Is. Completed {get; } } v Completed. Synchronously връща true ако асинхронният метод е приключил работа преди края на извикването на Begin. Invoke метода (т. е. ако работи прекалено бързо)
IAsync. Result public interface IAsync. Result { object Async. State {get; } Wait. Handle Async. Wait. Handle {get; } bool Completed. Synchronously {get; } bool Is. Completed {get; } } v v Is. Completed връща true ако асинхронният метод е приключил работа Може да се използва чрез механизма "polling" – през определено време да се проверява истинността на Is. Completed докато върне true
Изчакване на асинхронен метод u Има 4 начина да се провери дали е приключил един асинхронен метод 1. Механизмът "polling" – проверява се IAsync. Result. Is. Completed през определено време 2. Чрез Wait. Handle. Wait. One() / Wait. All() / Wait. Any() върху IAsync. Result. Async. Wait. Handle v 3. Може да се задава таймаут, за да не се чака безкрайно дълго Извикване на End. Invoke(), който блокира докато асинхронният метод не свърши работата си
Изчакване на асинхронен метод u Има 4 начина да се провери дали е приключил един асинхронен метод 4. Да се подаде callback метод на Begin. Invoke() чрез делегата Async. Callback delegate void Async. Callback(IASync. Result ar) v v Подаденият метод се извиква, когато асинхронният метод приключи работа От него може да се извика End. Invoke() за да се извлече резултата
Демонстрация #19 u 4 начина да изчакаме приключването на асинхронно извикване
Многонишково програмиране и синхронизация Въпроси?
Задачи 1. 2. 3. Напишете програма, която стартира предварително зададен брой нишки (константа). Всяка нишка изписва “Thread X started”, спи (Thread. Sleep()) случаен брой милисекунди и изписва “Thread X stopped”. X трябва да се задава в конструктора на класа, който съдържа метода, използван в Thread. Start делегата. Да се промени програмата, така че името на нишката да се задава от главната нишка и да се взима след това с Thread. Current. Thread. Name. Променете горната програма, така че след като стартира всичките нишки, да изписва “Main Thread Exits”. Стартирайте я и наблюдавайте резултата. Пазете в масив референция към всяка стартирана нишка. Добавете изчакване на всички стартирани нишки с Thread. Join().
Задачи 4. 5. 6. Напишете Windows Forms приложение, което стартира едновременно 10 нишки и от всяка от тях в безкраен цикъл добавя по една буква към текста, съдържащ се в дадена Text. Box контрола. Наслаждавайте се на забележителния начин по който приложението "зависва". Обяснете защо се получава този ефект? Обещавате ли никога да не правите подобна глупост? Защо при конзолни приложения може много нишки да пишат по конзолата едновременно? Преработете предходното приложение, като се съобразите с препоръката, че потребителският интерфейс на Windows Forms приложенията трябва да се обновява само и единствено от главната им нишка. Използвайте метода Form. Invoke(), както е показано в примерите.
Задачи 7. 8. 9. Напишете Windows Forms приложение, което стартира нишка при натискане на бутон. Нишката итерира числата от 1 до 1000 и ги изписва в текстово поле през 200 ms (използвайте Thread. Sleep()). Сложете 2 бутона – единият да извиква Suspend() за нишката, другият – Resume(). Тествайте приложението. Замръзва ли потребителският интерфейс докато работи нишката? Погрижихте ли се достъпът до потребителския интерфейс да става само през главната нишка на приложението? Добавете към горната програма още една нишка, която през 100 ms да изписва състоянието на другата нишка в Status. Bar контрола. Направете нишките на приложението background. Какво става ако не са такива?
Задачи 10. 11. Добавете към предната програма бутони "Interrupt" и "Abort", които извикват Interrupt() и Abort() на нишката, която печата числата. Тествайте приложението. Напишете програма, която стартира 100 нишки. Всяка итерира 3 вложени цикъла от 1 до 1 000, като пресмята Sqrt(i+j+k), където i, j, k са водещите променливи в трите вложени цикъла. Циклите са за симулиране на работа. Нагласете циклите така, че да не отнемат нито твърде много нито твърде малко време. Изписвайте “Thread X Started” и “Thread X Stopped” съответно преди и след като започне работа. Пуснете програмата. Сменяйте приоритетите на някои от нишките и наблюдавайте разликите във времето за изпълнение на нишките с различни приоритети.
Задачи 12. 13. 14. Напишете Windows Forms приложение, което търси текст във всички файлове от дадена директория (подобно на търсенето от Windows Explorer). Използвайте нишки. Реализирайте по правилен начин прекратяване на търсенето. Напишете програма, която стартира 2 нишки, всяка от нишките заделя Named. Slot с уникално име (използвайки Thread Local Storage) и се опитайте да достъпите от всяка от нишките данните на другата нишка. Какво се случва? Напишете програма, в която има клас с членпроменлива (целочислена), отбелязана с атрибута [Thread. Static]. Напишете метод, който променя стойността й и я отпечатва на екрана. Пуснете няколко нишки с този метод. Наблюдавайте резултата. Защо се получава така?
Задачи 15. 16. (Race condition) Напишете клас, който има статична член-променлива (целочислена). Напишете метод, който прави локално копие на тази променлива, спи 100 ms. , увеличава стойността на променливата, и я записва на мястото на статичната член-променлива. Пуснете 100 нишки с този метод. Накрая отпечатайте резултата (изчакайте всичките нишки да приключат) от главната нишка. Верен ли е? (Deadlock) Напишете клас, който има два обекта – А и В, и два метода. Метод 1 заключва ресурс А (Monitor. Enter(А)) и спи 2000 ms. След това чака за ресурс В (Monitor. Enter(B)). Метод 2 спи 1000 ms, заключва ресурс В, и се опитва да заключи ресурс А. Какво се случва?
Задачи 17. 18. 19. 20. 21. Напишете програма, която има целочислена член-променлива (брояч). Добавете метод, който увеличава тази брояч с 1. Изписвайте на екрана текст преди и след увеличаването му. Пуснете 100 нишки с този метод. Има ли нещо нередно при работата с брояча? Синхронизирайте достъпът до брояча с lock. Синхронизирайте достъпа до променливата от предната задача с Monitor. Enter() / Exit(). Синхронизирайте достъпа до променливата от предната задача чрез Context. Bound. Object и [Synchronization. Attribute]. Синхронизирайте достъпа до променливата от предната задача с [Method. Impl. Attribute (Method. Impl. Options. Synchronized)]
Задачи 22. 23. 24. Синхронизирайте достъпа до променливата от предната задача с Mutex. Синхронизирайте достъпа до променливата от предната задача чрез класа Interlocked. Модифицирайте Demo-13 -Mutex-Example така, че едновременно да работят по най-много 2, а не най-много 1 нишка. Подсказка: добавете статична член-променлива, която да брои работещите нишки. Ако броят им е 2 блокирайте нишката (Mutex. Wait. One()). Внимавайте колко пъти освобождавате заключването (Mutex. Release. Mutex()). Променете изписването на екрана, така че да е адекватно на новата ситуация. Използвайте lock при достъпа до статичната променлива.
Задачи 25. 26. 27. Напишете клас, който има метод, извършващ времеотнемащи изчисления. Пуснете 10 нишки да изпълняват този метод. Синхронизирайте изпълнението на изчисленията, така че само една нишка да извършва изчисления в даден момент. Използвайте Auto. Reset. Event – всяка нишка като се стартира чака (без първата) и след като свърши работа сигнализира на следващата. Променете програмата от предната задача, така че стартирането на първата нишка да се сигнализира от клавиатурата. Променете предната задача така, че всички нишки да се стартират при натискане на [Enter]. Използвайте Manual. Reset. Event вместо Auto. Reset. Event. Каква е разликата?
Задачи 28. Реализирайте Windows Forms приложение, което показва коя директория от C: колко място заема (заедно с всички файлове и поддиректории). Потребителският интерфейс трябва да представлява дърво, което съответства на дървото на директориите. Всеки елемент в това дърво трябва да е име на директория и да показва в скоби обемът й (в KB, MB или GB). За да не чакат прекалено дълго потребителите, трябва след визуализация на всички поддиректории размерите им да се пресмятат във фонов режим (с отделна нишка). За директориите, за които размерите се пресмятат в момента и не са все още известни, вместо размер трябва да се показва в скобите "working". Първоначално активна е коренната директория. При избиране на друга директория от дървото трябва да се показват нейните поддиректории (заедно с размерите им).
Задачи 29. 30. 31. 32. Реализирайте програма, която през 5 секунди проверява какъв е размерът на даден файл (името е зададено като константа) и ако нарасне над определен праг (примерно 500 КB), отпечатва предупредително съобщение. Използвайте Thread. Pool. Register. Wait. For. Single. Object(…). Напишете програма, която реализира функционалността на Demo-17 Custom. Reader. Writer използвайки класа Reader. Writer. Lock. Решете проблема "производител/консуматор" чрез Monitor. Wait() и Monitor. Pulse(). Направете си примерно приложение, с което да тествате дали работи правилно. Напишете програма, която пуска няколко нишки, които четат и пишат в общ стек. Синхронизирайте достъпа да стека чрез Stack. Synchronized();
Задачи 33. 34. 35. Синхронизирайте достъпа до стека от предходната задача като използвате свойството му Sync. Root. Реализирайте Windows Forms приложение, което може да търси символни низове във всички файлове от зададена директория. Приложението трябва да поддържа едновременно търсене на няколко низа в няколко директории. Използвайте Thread Pool-а на. NET Framework. При всяка заявка за търсене добавяйте в него задача. Визуализирайте резултатите в List. Box контрола. Реализирайте предходната задача, като вместо Thread Pool използвате асинхронно извикване на метод чрез делегат.
Използвана литература u u MSDN Training, Programming with the Microsoft. NET Framework (C#. NET) (MOC 2349 C), Module 14: Threading and Asynchronous Programming – http: //www. microsoft. com/learning/syllabi/enus/2349 bfinal. mspx MSDN Library, Using Threads and Threading. NET – http: //msdn. microsoft. com/library/default. asp? url=/lib rary/en-us/cpguide/html/cpcon. Using. Threads Threading. asp MSDN Library, Threads and Threading – http: //msdn. microsoft. com/library/default. asp? url=/lib rary/en-us/cpguide/html/cpcon. Threads. Threading. asp MSDN Library, Synchronizing Data for Multithreading http: //msdn. microsoft. com/library/default. asp? url=/lib rary/en-us/cpguide/html/cpconmanagedthreading support. asp
Използвана литература u u u MSDN Library, Thread Pooling – http: //msdn. microsoft. com/library/default. asp? url=/li brary/en-us/cpguide/html/cpconthreadpooling. asp MSDN, Application Domains Overview – http: //msdn. microsoft. com/library/en-us/cpguide/ html/cpconapplicationdomainsoverview. asp MSDN, Visual Studio. NET – Product Overview – http: //msdn. microsoft. com/vstudio/productinfo/over view/ Classical synchronization problems – http: //nob. cs. ucdavis. edu/classes/ecs 150 -199902/sync-problems. html Multithreading Part 1: Multitasking and Multithreading by Manish Mehta – http: //www. csharpcorner. com/Code/2002/April/Mt. P 1 Mt. Vs. Mt. asp
Използвана литература u u Multithreading Part 2: Understanding the Thread Class by Manish Mehta – http: //www. c-sharpcorner. com/Code/ 2002/April/System. Threading. Thread. MT 2. asp Multithreading Part 3: Thread Synchronization by Manish Mehta – http: //www. c-sharpcorner. com/Code/ 2002/April/Multithreading. P 3. asp Multithreading Part 4: The Thread. Pool, Timer Class and Asynchronous Programming Discussed by Manish Mehta – http: //www. c-sharpcorner. com/Code/2002/ April/Mt. P 4 Mt. Vs. Mt. asp Using asynchronous method calls in C# by Shawn Dillon – http: //uk. builder. com/programming/ java/0, 39026606, 20264369, 00. htm
Използвана литература u u u Multithreading In C# by Mubbsher – http: //www. codeproject. com/csharp/Multithreading_In_C_. asp Multithreading with C# by Raffi Krikorian – http: //www. ondotnet. com/pub/a/dotnet/2001/08/06/csharp. html Multithreading In C# by Arun GG http: //www. csharphelp. com/archives/archive 128. html


