26-Bob: Thread Asoslari

Windows nima uchun threadlarni qo'llab-quvvatlaydi, thread yuklamasi, CPU tendensiyalari, CLR va Windows threadlari, thread rejalashtirish, foreground va background threadlar

Ushbu bobda men threadlar (oqimlar) haqidagi asosiy tushunchalarni tanishtirib, dasturchilar uchun ular haqida qanday fikrlash kerakligini tavsiya qilaman. Men Windows nima uchun threadlar tushunchasini joriy qilganini, common language runtime (CLR) threadlari va Windows threadlari o'rtasidagi aloqani, threadlardan foydalanish bilan bog'liq yuklamani, Windows threadlarni qanday rejalashtirishi, Microsoft .NET Framework sinflarini va boshqa ko'p narsalarni tushuntirib beraman.

Ushbu kitobning V qismi "Threading" bo'limlari Windows va CLR qanday birgalikda ishlashini tushuntirib, threading arxitekturasini ta'minlaydi. Umid qilamanki, ushbu boblarni o'qib chiqgach, siz threadlardan samarali foydalanib, responsiv, ishonchli va kengaytiradigan ilovalar va komponentlar loyihalashtirishingiz mumkin bo'ladi.

Nima Uchun Windows Threadlarni Qo'llab-quvvatlaydi?

Kompyuterlarning dastlabki davrlarida operatsion tizimlar thread tushunchasini taklif qilmasdi. Amalda, butun tizim bo'ylab ishlaydigan yagona ijro oqimi (thread of execution) mavjud bo'lib, u operatsion tizim kodi va ilova kodini o'z ichiga olardi. Faqat bitta ijro oqimiga ega bo'lishning muammosi shunda ediki, uzoq davom etuvchi vazifa boshqa vazifalarning bajarilishiga to'sqinlik qilardi. Masalan, 16-bitli Windows davrlarida hujjatni chop etayotgan ilova butun mashinani to'xtatib qo'yishi, operatsion tizim va barcha boshqa ilovalarning javob berishini to'xtatishi juda odatiy hol edi. Ba'zan ilovalar ularning ichida xato bo'lib, cheksiz tsiklga kirib qolardi va bu ham butun mashinaning ishlashini to'xtatib qo'yardi.

Bunday paytda oxirgi foydalanuvchi kompyuterni qayta ishga tushirish tugmasi yoki quvvat tugmasini bosishdan boshqa iloji yo'q edi. Albatta, oxirgi foydalanuvchilar buni qilishni yoqtirmasdi (ular hali ham shunday qiladi) chunki barcha ishlaydigan ilovalar tugatilardi; eng muhimi, ushbu ilovalar qayta ishlaydigan har qanday ma'lumotlar xotiradan o'chirilardi va yo'qolardi. Microsoft 16-bitli Windows yetarli emasligini bilardi va kompyuter sanoati rivojlangani sari Microsoft'ni dolzarb saqlash uchun ular korporatsiyalar va shaxslarning ehtiyojlarini qondiruvchi yangi operatsion tizimni yaratishga kirishdilar. Bu yangi operatsion tizim mustahkam, ishonchli, kengaytiriladigan va xavfsiz bo'lishi hamda 16-bitli Windows'ning ko'plab kamchiliklarini tuzatishi kerak edi. Bu operatsion tizim yadrosi dastlab Windows NT da paydo bo'ldi. Yillar davomida bu yadroga ko'plab o'zgartirishlar va yangiliklar kiritildi. Uning so'nggi versiyasi Microsoft mijoz va server Windows operatsion tizimlarining eng so'nggi versiyalarida ishlatilmoqda.

Microsoft ushbu operatsion tizim yadrosini loyihalashtirayotganda, ular ilovaning har bir nusxasini jarayon (process) deb ataladigan narsada ishga tushirishga qaror qildilar. Jarayon - bu ilova tomonidan ishlatiladigan resurslar to'plami. Har bir jarayonga virtual manzil maydoni beriladi, bu kodning va boshqa jarayon tomonidan ishlatiladigan ma'lumotlarning ushbu jarayonga kirish imkoniyati yo'qligini ta'minlaydi. Bu ilova nusxalarini mustahkam qiladi, chunki bitta jarayon boshqa jarayonning kodini yoki ma'lumotlarini buzishi mumkin emas. Bundan tashqari, operatsion tizimning yadro kodi va ma'lumotlari jarayonlarga kirib bo'lmaydi; shuning uchun, ilova kodi operatsion tizim kodini yoki ma'lumotlarini buzishi mumkin emas. Endi ilova kodi operatsion tizimni yoki boshqa ilovalarni buza olmaydi va butun hisoblash tajribasi oxirgi foydalanuvchilar uchun ancha yaxshi bo'ldi. Bundan tashqari, tizim yanada xavfsizroq, chunki ilova kodi foydalanuvchi nomlari, parollar, kredit karta ma'lumotlari yoki boshqa jarayon yoki operatsion tizimning o'zi tomonidan foydalanilayotgan maxfiy ma'lumotlarga kira olmaydi.

Bularning hammasi yaxshi, lekin CPU o'zi haqida nima deyish mumkin? Agar ilova cheksiz tsiklga kirsa nima bo'ladi? Mashinada faqat bitta CPU bo'lsa, u cheksiz tsiklni bajaradi va boshqa hech narsa bajara olmaydi, shuning uchun ma'lumotlar buzilishi mumkin bo'lmasa ham va tizim yanada xavfsiz bo'lsa ham, tizim oxirgi foydalanuvchiga javob berishni to'xtatishi mumkin. Microsoft bu muammoni ham hal qilishi kerak edi va javob threadlar edi. Thread - bu CPU ni virtualizatsiya qilish vazifasiga ega Windows tushunchasi. Windows har bir jarayonga o'zining shaxsiy threadini beradi (funksiyalari CPU ga o'xshash) va agar ilova kodi cheksiz tsiklga kirsa, o'sha kod bilan bog'liq jarayon muzlaydi, lekin boshqa jarayonlar (ularning o'z threadlari bor) muzlamaydi; ular ishlashda davom etadi!

Thread Yuklamasi (Overhead)

Threadlar ajoyib, chunki ular Windows'ga ilovalar uzoq davom etuvchi vazifalarni bajarayotganda ham javob berishini ta'minlaydi. Shuningdek, threadlar foydalanuvchiga uzoq davom etuvchi vazifani bajarayotgan ilova muzlab qolganday ko'ringanda uni (Task Manager orqali) majburiy o'chirish imkonini beradi. Biroq, har qanday virtualizatsiya mexanizmida bo'lgani kabi, threadlar bilan bog'liq joy (xotira iste'moli) va vaqt (ish vaqtidagi ishlash) yuklamasi mavjud.

Muhim

Thread yaratish va yo'q qilish arzon jarayon emas. Joy va vaqt yuklamalarini kamaytirishning eng yaxshi usuli - threadlardan imkon qadar kamroq foydalanish va mavjud bo'lganlardan maksimal foydalanish. Thread pool (27-bob) aynan shu maqsadda xizmat qiladi.

Thread Komponentlari

Keling, ushbu yuklamani batafsil ko'rib chiqaylik. Har bir threadda quyidagilarning har biri mavjud:

  • Thread kernel obyekti (Thread kernel object) — Operatsion tizim tizimda yaratilgan har bir thread uchun ushbu ma'lumotlar strukturalaridan birini ajratadi va ishga tushiradi. Ma'lumotlar strukturasi threadni tavsiflovchi bir qator xususiyatlarni o'z ichiga oladi (bobda keyinroq muhokama qilinadi). Ma'lumotlar strukturasi threadning konteksti deb ataladigan narsani ham o'z ichiga oladi. Kontekst - bu CPU registrlari to'plamini o'z ichiga olgan xotira bloki. x86, x64 va ARM CPU arxitekturaları uchun threadning konteksti mos ravishda taxminan 700, 1,240 yoki 350 bayt xotiradan foydalanadi.
  • Thread environment block (TEB) — TEB - bu foydalanuvchi rejimida (ilova kodi tezda kirishi mumkin bo'lgan manzil maydoni) ajratilgan va ishga tushirilgan xotira bloki. TEB x86, x64 va ARM CPU'larda 1 sahifa xotiradan (4 KB) foydalanadi. TEB threadning exception-handling zanjirining boshini o'z ichiga oladi. Thread kirgan har bir try bloki zanjirning boshiga tugun qo'shadi; thread try blokidan chiqqanda tugun zanjirdan olib tashlanadi. Bundan tashqari, TEB threadning thread-local saqlash ma'lumotlarini va Graphics Device Interface (GDI) va OpenGL grafikalari tomonidan foydalanish uchun ba'zi ma'lumotlar strukturalarini o'z ichiga oladi.
  • Foydalanuvchi rejimi steki (User-mode stack) — Foydalanuvchi rejimi steki lokal o'zgaruvchilar va metodlarga uzatilgan argumentlarni saqlash uchun ishlatiladi. U shuningdek, joriy metod qaytganda thread keyingi nima bajarishini ko'rsatadigan manzilni o'z ichiga oladi. Sukut bo'yicha, Windows har bir threadning foydalanuvchi rejimi steki uchun 1 MB xotira ajratadi. Aniqroq aytganda, Windows 1 MB manzil maydonini zaxira qiladi va threadga stek o'sishi bilan kerak bo'lganda jismoniy xotirani siyrak ajratadi.
  • Yadro rejimi steki (Kernel-mode stack) — Yadro rejimi steki ilova kodi operatsion tizimdagi yadro rejimi funksiyasiga argumentlarni uzatganda ham ishlatiladi. Xavfsizlik maqsadida, Windows foydalanuvchi rejimi kodidan yadro kodiga uzatilgan har qanday argumentlarni threadning foydalanuvchi rejimi stekidan threadning yadro rejimi stekiga nusxalaydi. Nusxalangandan so'ng, yadro argumentlar qiymatlarini tekshirishi mumkin. Yadro rejimi steki 32-bitli Windows tizimida 12 KB va 64-bitli Windows tizimida 24 KB hajmga ega.
  • DLL thread-attach va thread-detach bildirishnomalari — Windows siyosatiga ko'ra, jarayonda thread yaratilganda, o'sha jarayonda yuklangan barcha boshqarilmaydigan (unmanaged) DLL'larning DllMain metodi DLL_THREAD_ATTACH bayrog'i bilan chaqiriladi. Xuddi shunday, thread o'lganda, jarayondagi barcha DLL'larning DllMain metodi DLL_THREAD_DETACH bayrog'i bilan chaqiriladi. Ba'zi DLL'lar jarayonda yaratilgan/yo'q qilingan har bir thread uchun maxsus initsializatsiya yoki tozalash ishlarini bajarish uchun ushbu bildirishnomalarni talab qiladi.

Windows'ning dastlabki kunlarida ko'plab jarayonlarda 5 yoki 6 ta DLL yuklangan bo'lishi mumkin edi, lekin bugun ba'zi jarayonlarda bir necha yuzta DLL yuklangan. Hozir, mening mashinamda, Microsoft Visual Studio jarayon manzil maydoniga taxminan 470 ta DLL yuklangan! Bu shuni anglatadiki, Visual Studio'da yangi thread yaratilganda, threadga nima qilish kerakligini ruxsat berishdan oldin 470 ta funksiya chaqirilishi kerak. Va bu 470 ta funksiya Visual Studio'da thread o'lganda ham qayta chaqirilishi kerak. Voy — bu jarayonda threadlarni yaratish va yo'q qilish ishlashiga jiddiy ta'sir ko'rsatishi mumkin.

Eslatma

C# va boshqa ko'plab boshqariladigan (managed) dasturlash tillari tomonidan ishlab chiqarilgan DLL'larda DllMain funksiyasi umuman bo'lmaydi va shu sababli boshqariladigan DLL'lar DLL_THREAD_ATTACH va DLL_THREAD_DETACH bildirishnomalarini olmaydi, bu esa ishlashni yaxshilaydi. Bundan tashqari, boshqarilmaydigan DLL'lar Win32 DisableThreadLibraryCalls funksiyasini chaqirib ushbu bildirishnomalardan voz kechishlari mumkin.

Kontekst Almashtirish (Context Switching)

Endi siz thread yaratish, tizimda o'tqazish va yo'q qilish bilan bog'liq barcha joy va vaqt yuklamasini ko'rib chiqdingiz. Lekin vaziyat yanada yomonlashadi — endi biz kontekst almashtirish (context switching) haqida gaplashmoqchimiz. Bitta CPU bilan kompyuter bir vaqtning o'zida faqat bitta ishni bajara oladi. Shuning uchun, Windows tizimdagi barcha threadlar (mantiqiy CPU'lar) o'rtasida haqiqiy CPU apparatini bo'lishishi kerak.

Istalgan vaqtda Windows bitta threadni CPU'ga tayinlaydi. O'sha threadga vaqt tilimi (time-slice, ba'zan kvant deb ham ataladi) davomida ishlash ruxsat etiladi. Vaqt tilimi tugagach, Windows boshqa threadga kontekst almashtiradi. Har bir kontekst almashtirish Windows'dan quyidagi harakatlarni bajarishini talab qiladi:

  1. Hozirda ishlayotgan threadning kontekst strukturasidagi CPU registrlari qiymatlarini threadning yadro obyektiga saqlash.
  2. Mavjud threadlar to'plamidan keyingi rejalashtiriladigan threadni tanlash. Agar bu thread boshqa jarayonga tegishli bo'lsa, Windows CPU ko'radigan virtual manzil maydonini ham almashtirishiga to'g'ri keladi.
  3. Tanlangan threadning kontekst strukturasidagi qiymatlarni CPU registrlariga yuklash.

Kontekst almashtirish tugagandan so'ng, CPU tanlangan threadni uning vaqt tilimi tugaguniga qadar bajaradi va keyin yana kontekst almashtirish sodir bo'ladi. Windows taxminan har 30 ms da kontekst almashtirish amalga oshiradi. Kontekst almashtirishlar sof yuklamadir; ya'ni kontekst almashtirishlardan keladigan hech qanday xotira yoki ishlash foydasi yo'q. Windows kontekst almashtirishni oxirgi foydalanuvchilarga mustahkam va javobgar operatsion tizim ta'minlash uchun amalga oshiradi.

Aslida, ishlash zarabri siz o'ylashingiz mumkin bo'lgandan ham yomonroq. Ha, Windows kontekst almashtirishlari vaqtida ishlash zarabri yuzaga keladi. Lekin CPU boshqa threadni bajarar edi va oldingi threadning kodi va ma'lumotlari CPU keshlarida joylashgan edi, shuning uchun CPU RAM xotirasiga murojaat qilishi shart emas, bu esa sezilarli kechikish bilan bog'liq. Windows yangi threadga kontekst almashtirganda, bu yangi thread boshqa kodni bajarayotgan va CPU keshida bo'lmagan boshqa ma'lumotlarga murojaat qilayotgan bo'lishi ehtimoli katta. CPU o'z keshini to'ldirish uchun RAM xotirasiga murojaat qilishi kerak. Lekin keyin, taxminan 30 ms dan so'ng, yana kontekst almashtirish sodir bo'ladi.

Muhim

Vaqt tilimi oxirida, agar Windows bir xil threadni yana rejalashtirishga qaror qilsa (boshqa threadga almashtirish o'rniga), Windows kontekst almashtirish amalga oshirmaydi. Buning o'rniga, threadga ishlashni davom ettirish ruxsat etiladi. Bu ishlashni sezilarli darajada yaxshilaydi va kontekst almashtirishlarning oldini olish siz loyihalashtirganingizda tez-tez erishmoqchi bo'lgan narsadir.

Muhim

Thread o'z vaqt tilimini ixtiyoriy ravishda erta tugatishi mumkin, bu juda tez-tez sodir bo'ladi. Threadlar odatda I/O operatsiyalari (klaviatura, sichqoncha, fayl, tarmoq va h.k.) tugashini kutadi. Masalan, Notepad'ning threadi odatda bo'sh o'tirib, kirish kutadi; bu thread hech narsa qilishi shart emas. Agar foydalanuvchi klaviaturada J tugmasini bossa, Windows Notepad'ning threadini uni qayta ishlash uchun uyg'otadi. Bu faqat 5 ms vaqt olishi mumkin va keyin thread keyingi kiritish hodisasini qayta ishlashga tayyor ekanligini Windows'ga bildiruvchi Win32 funksiyasini chaqiradi. Agar boshqa kiritish hodisalari bo'lmasa, Windows Notepad'ning threadini kutish holatiga o'tkazadi (vaqt tilimining qolgan qismidan voz kechadi), shuning uchun thread hech qanday CPU'da rejalashtirilmaydi. Bu umumiy tizim ishlashini yaxshilaydi, chunki I/O operatsiyalari tugashini kutayotgan threadlar CPU'da rejalashtirilmaydi va CPU vaqtini sarflamaydi; boshqa threadlar esa CPU'da rejalashtirilishi mumkin.

Bundan tashqari, garbage collection amalga oshirilayotganda, CLR barcha threadlarni to'xtatib, ildizlarni topish uchun ularning steklarini ko'rib chiqishi, heap'dagi obyektlarni belgilash, steklarini qayta ko'rib chiqishi (siqish paytida ko'chirilgan obyektlarga ildizlarni yangilash) va keyin barcha threadlarni davom ettirishi kerak. Shuning uchun threadlardan qochish garbage collector ishlashini ham sezilarli darajada yaxshilaydi. Va tuzatuvchi (debugger) ishlatayotganingizda, Windows tuzatilayotgan ilovadagi barcha threadlarni har safar breakpoint ga tushilganda to'xtatadi va siz bitta qadamli yoki ilovani ishga tushirganingizda barcha threadlarni davom ettiradi. Shuning uchun, qancha ko'p thread bo'lsa, tuzatish tajribangiz shuncha sekin bo'ladi.

Ushbu muhokamadan siz threadlardan iloji boricha kamroq foydalanish kerak degan xulosaga kelishingiz kerak, chunki ular ko'p xotira iste'mol qiladi va ularni yaratish, yo'q qilish va boshqarish uchun vaqt talab etiladi. Vaqt ham kontekst almashtirishlar va garbage collection paytida isrof bo'ladi. Biroq, bu muhokama sizga threadlar ba'zan ishlatilishi kerakligini ham anglashga yordam berishi kerak, chunki ular Windows'ni mustahkam va javobgar qiladi.

Shuni ham ta'kidlashim kerakki, ko'p CPU'li kompyuterlarda aslida bir vaqtning o'zida bir nechta threadlar ishlashi mumkin, bu esa kengaytirishni oshiradi (kamroq vaqtda ko'proq ish qilish qobiliyati). Windows har bir CPU yadrosiga bitta thread tayinlaydi va har bir yadro boshqa threadlarga kontekst almashtirishini amalga oshiradi. Windows bitta thread bir vaqtning o'zida bir nechta yadrolarda rejalashtirilmasligini ta'minlaydi, chunki bu jiddiy muammolarga olib keladi.

Jinunlikni To'xtating!

Agar bizni faqat sof ishlash qiziqtirsa, har qanday mashinadagi threadlarning optimal soni o'sha mashinadagi CPU'lar soniga teng bo'lardi. Shunday qilib, bitta CPU bilan mashina faqat bitta threadga ega bo'lardi, ikki CPU bilan mashina ikki threadga ega bo'lardi va hokazo. Sabab aniq: agar CPU'lardan ko'proq thread bo'lsa, kontekst almashtirish kiritiladi va ishlash yomonlashadi. Agar har bir CPU faqat bitta threadga ega bo'lsa, kontekst almashtirish mavjud emas va threadlar to'liq tezlikda ishlaydi.

Biroq, Microsoft Windows'ni ishlash tezligi va samaradorlikdan ko'ra ishonchlilik va javobgarlikni afzal ko'rish uchun loyihalashtirilgan. Va men bu qarorni qo'llab-quvvatlayman: menimcha, agar ilovalar hali ham operatsion tizim va boshqa ilovalarni to'xtata olsalar, hech birimiz bugun Windows yoki .NET Framework'dan foydalanmagan bo'lardik. Shuning uchun, Windows yaxshilangan tizim ishonchliligi va javobgarligi uchun har bir jarayonga o'zining shaxsiy threadini beradi. Mening mashinamda Task Manager'ni ishga tushirib Performance tabini tanlasam, men 26-1-rasmdagi tasvirni ko'raman.

26-1-rasm: Task Manager tizim ishlashini ko'rsatmoqda

Task Manager shuni ko'rsatadiki, mashinamda hozir 55 ta jarayon ishlamoqda va biz kamida 55 ta thread kutamiz, chunki har bir jarayon kamida 1 ta threadga ega. Lekin Task Manager shuningdek mashinamda hozir 864 ta thread borligini ko'rsatmoqda! Bu threadlarning barchasi xotirani ajratmoqda va faqat 4 GB RAM bilan mashinamda megabaytlab xotira sarflamoqda. Bu shuni anglatadiki, har bir jarayonda o'rtacha 15.7 ta thread bor, holbuki mening ikki yadroli mashinamda har bir jarayonda ideal holda faqat 2 ta thread bo'lishi kerak edi!

Vaziyatni yanada yomonlashtirish uchun, CPU foydalanishini ko'rsam, mening CPU'm vaqtning atigi 5 foizida band. Bu vaqtning 95 foizida bu 864 ta thread hech narsa qilmaydi — ular shunchaki xotirani singdirib o'tirgan va threadlar ishlamayotganda ishlatilmayotgan xotirani behuda sarflayapti. Siz o'zingizdan so'rashingiz kerak: bu ilovalar vaqtning 95 foizida hech narsa qilmaslik uchun bu barcha threadlarga muhtojmi? Bu savolning javobi "Yo'q" bo'lishi kerak.

Dasturchilar Windows haqida o'rganganda, ular Windows'da jarayon juda qimmatligini bilishdi. Jarayon yaratish odatda bir necha soniya vaqt oladi, ko'p xotira ajratilishi kerak, EXE va DLL fayllarni diskdan yuklash kerak va hokazo. Taqqoslash uchun, Windows'da thread yaratish juda arzon, shuning uchun dasturchilar jarayonlar yaratishni to'xtatib, threadlar yaratishni boshladilar. Endi bizda ko'p threadlar mavjud. Lekin threadlar jarayonlardan arzon bo'lsa ham, ular ko'pchilik boshqa tizim resurslari bilan taqqoslaganda hali ham juda qimmat, shuning uchun ularni tejamkorlik bilan va maqsadga muvofiq ishlatish kerak.

Shubhasiz, biz muhokama qilgan barcha ilovalar threadlarni samarasiz ishlatmoqda deyishimiz mumkin. Bu threadlarning barchasining tizimda mavjud bo'lishi uchun hech qanday sabab yo'q. Resurslarni ajratish boshqa narsa; ularni ajratib keyin ishlatmaslik butunlay boshqa narsa. Bu shunchaki isrofgarlik va thread steklari uchun barcha xotirani ajratish muhimroq ma'lumotlar, masalan, foydalanuvchi hujjati uchun kamroq xotira mavjudligini anglatadi.

Vaziyatni yanada yomonlashtirish uchun, bu jarayonlar bitta foydalanuvchining Remote Desktop Services sessiyasida ishlayotgan bo'lsa — va bu mashinada haqiqatda 100 foydalanuvchi bo'lsa nima bo'ladi? 100 ta Outlook nusxasi bo'lardi, har biri 24 ta thread yaratib, ular bilan hech narsa qilmaydi. Bu har biri o'z yadro obyekti, TEB, foydalanuvchi rejimi steki, yadro rejimi steki bilan 2,400 ta thread. Bu juda ko'p isrof qilingan resurs. Bu jinunlik to'xtashi kerak. Ushbu kitob qismidagi boblar ilovani juda kam threadlardan samarali foydalanishga qanday loyihalash kerakligini tavsiflaydi.

Ko'p-CPU Texnologiyalari

Bugungi kunda kompyuterlar uchta turdagi ko'p-CPU texnologiyalarini ishlatadi:

  • Ko'p CPU'li (Multiple CPUs) — Ba'zi kompyuterlarda bir nechta CPU mavjud. Ona platada bir nechta soketlar bo'lib, har bir soketda CPU joylashgan. Ona plata kattaroq bo'lishi kerak bo'lganligi sababli, kompyuter korpusi ham kattaroq va ba'zan bu mashinalar qo'shimcha quvvat manbalariga ega. Bu turdagi kompyuterlar bir necha o'n yillardan beri mavjud, lekin ularning kattaligi va narxi sababli bugun unchalik ommaviy emas.
  • Hyperthreaded chiplar — Bu texnologiya (Intel'ga tegishli) bitta chipni ikkita chipga o'xshatadi. Chip ikkita arxitektura holatlar to'plamini (masalan, CPU registrlari) o'z ichiga oladi, lekin faqat bitta ijro resurslari to'plamiga ega. Windows uchun bu mashinada ikkita CPU borga o'xshaydi, shuning uchun Windows ikkita threadni bir vaqtda rejalashtirad. Biroq, chip bir vaqtda faqat bitta threadni bajaradi. Bir thread kesh xatosi, tarmoq noto'g'ri bashorati yoki ma'lumotlarga bog'liqlik sababli pauzaga to'xtalganda, chip boshqa threadga o'tadi. Intel hyperthreaded CPU ishlashni 10 dan 30 foizga yaxshilashi mumkinligini ta'kidlaydi.
  • Ko'p yadroli chiplar (Multi-core chips) — Bir necha yil oldin bir nechta CPU yadrolarini o'z ichiga olgan yagona chiplar paydo bo'ldi. Hozirda ikki, uch, to'rt va undan ortiq yadroli chiplar keng tarqalgan. Hatto mening notebook kompyuterimda ham ikki yadro bor; mobil telefonlarimizda ham bir nechta yadro mavjud. Intel hatto bitta chipda 80 ta yadro ustida ishlagan! Bu juda katta hisoblash quvvati. Va Intel hatto hyperthreaded ko'p yadroli chiplarni ham ishlab chiqaradi.

CLR Threadlari va Windows Threadlari

Bugungi kunda CLR Windows'ning threading imkoniyatlarini ishlatadi, shuning uchun kitobning V qismi haqiqatda Windows'ning threading imkoniyatlari CLR yordamida kod yozuvchi dasturchilarga qanday ta'sir qilishiga qaratilgan. Men Windows'da threadlar qanday ishlashini va CLR ularning xatti-harakatini qanday o'zgartirishini tushuntiraman. Biroq, agar siz threadlar haqida ko'proq ma'lumot olishni istasangiz, men o'zimning avvalgi yozishlarimni, masalan, Windows via C/C++, Fifth Edition (Microsoft Press, 2007) kitobini tavsiya qilaman.

Eslatma

.NET Framework'ning dastlabki kunlarida, CLR jamoasi bir kun kelib CLR mantiqiy threadlarni taklif qilishini o'ylardi, ular Windows threadlariga mos kelmasligi mumkin edi. Biroq, 2005-yil atrofida bu muvaffaqiyatsiz urinildi va CLR jamoasi bu g'oyadan voz kechdi. Shuning uchun, bugungi kunda CLR threadi Windows threadiga identik. Biroq, .NET Framework'da urinishning qoldiqlari bor. Masalan, System.Environment sinfi CLR'ning thread uchun ID'sini qaytaruvchi CurrentManagedThreadId xususiyatini oshkor qiladi, garchi System.Diagnostics.ProcessThread sinfi xuddi shu thread uchun Windows ID'sini qaytaruvchi Id xususiyatini oshkor qilsa ham.

Eslatma

Windows Store ilovalari uchun, Microsoft threading bilan bog'liq ba'zi API'larni olib tashladi, chunki API yomon dasturlash amaliyotlarini rag'batlantirdi ("Jinunlikni to'xtating" bo'limida muhokama qilinganidek) yoki API'lar Microsoft Windows Store ilovalari uchun belgilagan maqsadlarga erishishga yordam bermaydi. Masalan, butun System.Thread sinfi Windows Store ilovalari uchun mavjud emas, chunki unda Start, IsBackground, Sleep, Suspend, Resume, Join, Interrupt, Abort, BeginThreadAffinity va EndThreadAffinity kabi ko'plab yomon API'lar mavjud.

Asinxron Hisoblashga Asoslangan Operatsiya Uchun Maxsus Thread Ishlatish

Ushbu bo'limda men sizga thread yaratish va uni asinxron hisoblashga asoslangan (compute-bound) operatsiyani bajarishga qo'yishni ko'rsataman. Garchi men sizni bu texnika bilan tanishtirsam ham, undan qochishni qat'iy tavsiya qilaman. Aslida, agar siz Windows Store ilovasi yaratayotgan bo'lsangiz, Thread sinfi mavjud bo'lmaganligi sababli, bu texnika mumkin ham emas. Buning o'rniga, asinxron hisoblashga asoslangan operatsiyalarni bajarish uchun thread pool'dan foydalanishingiz kerak. Men 27-bobda, "Hisoblashga Asoslangan Asinxron Operatsiyalar"da bu haqda batafsil to'xtalaman.

Biroq, maxsus thread yaratish istaladigan ba'zi juda kamdan-kam holatlar mavjud. Odatda, threadning ma'lum bir holatda bo'lishini talab qiladigan kodni bajarish uchun maxsus thread yaratishni xohlaysiz, bu thread pool threadi uchun oddiy emas. Masalan, quyidagilardan biri to'g'ri bo'lsa, o'z threadingizni aniq yarating:

  • Threadning oddiy bo'lmagan (non-normal) thread ustuvorligida ishlashini xohlaysiz. Barcha thread pool threadlari oddiy ustuvorlikda ishlaydi; buni o'zgartirish mumkin bo'lsa-da, tavsiya etilmaydi va ustuvorlik o'zgarishi thread pool operatsiyalari orasida saqlanmaydi.
  • Threadning foreground thread sifatida ishlashini xohlaysiz, bu ilova o'z vazifasini tugatmaguncha tugashining oldini oladi. Thread pool threadlari har doim background threadlardir va agar CLR jarayonni tugatmoqchi bo'lsa, ular o'z vazifalarini tugatmasligi mumkin.
  • Hisoblashga asoslangan vazifa juda uzoq davom etadi; bu holda men thread pool mantiqini zo'riqtirmayman, chunki u qo'shimcha thread yaratish kerakligini aniqlashga harakat qiladi.
  • Threadni boshlash va ehtimol uni Thread'ning Abort metodini chaqirib muddatidan oldin to'xtatishni xohlaysiz (22-bobda, "CLR Hosting va AppDomains" da muhokama qilingan).

Maxsus thread yaratish uchun siz System.Threading.Thread sinfining namunasini yaratasiz va konstruktoriga metod nomini uzatasiz. Mana Thread konstruktorining prototipi:

public sealed class Thread : CriticalFinalizerObject, ... {
   public Thread(ParameterizedThreadStart start);
   // Less commonly used constructors are not shown here
}

start parametri maxsus thread bajaradigan metodni aniqlaydi va bu metod ParameterizedThreadStart delegatining imzosiga mos kelishi kerak:

delegate void ParameterizedThreadStart(Object obj);

Thread obyektini yaratish nisbatan engil operatsiya, chunki u haqiqiy jismoniy operatsion tizim threadini yaratmaydi. Haqiqiy operatsion tizim threadini yaratish va uni callback metodini bajarishga boshlash uchun Thread'ning Start metodini chaqirishingiz kerak, unga callback metod argumenti sifatida uzatmoqchi bo'lgan obyektni (holatni) uzating.

Quyidagi kod maxsus thread yaratish va uni asinxron ravishda metod chaqirishga qo'yishni namoyish etadi:

using System;
using System.Threading;

public static class Program {
   public static void Main() {
      Console.WriteLine("Main thread: starting a dedicated thread " +
         "to do an asynchronous operation");
      Thread dedicatedThread = new Thread(ComputeBoundOp);
      dedicatedThread.Start(5);

      Console.WriteLine("Main thread: Doing other work here...");
      Thread.Sleep(10000);     // Simulating other work (10 seconds)

      dedicatedThread.Join();  // Wait for thread to terminate
      Console.WriteLine("Hit <Enter> to end this program...");
      Console.ReadLine();
   }

   // This method's signature must match the ParameterizedThreadStart delegate
   private static void ComputeBoundOp(Object state) {
      // This method is executed by a dedicated thread

      Console.WriteLine("In ComputeBoundOp: state={0}", state);
      Thread.Sleep(1000);   // Simulates other work (1 second)

      // When this method returns, the dedicated thread dies
   }
}

Ushbu kodni kompilyatsiya qilib ishga tushirganimda, quyidagi natijani olaman:

Main thread: starting a dedicated thread to do an asynchronous operation
Main thread: Doing other work here...
In ComputeBoundOp: state=5

Ba'zan kodni ishga tushirganimda, quyidagi natijani olaman, chunki ikkita threadni Windows qanday rejalashtirishini boshqara olmayman:

Main thread: starting a dedicated thread to do an asynchronous operation
In ComputeBoundOp: state=5
Main thread: Doing other work here...

E'tibor bering, Main metodi Join ni chaqiradi. Join metodi chaqiruvchi threadning dedicatedThread tomonidan aniqlangan thread o'zini yo'q qilmaguncha yoki tugatilmaguncha har qanday kodni bajarishni to'xtatishiga olib keladi.

Threadlardan Foydalanish Sabablari

Threadlardan foydalanishning haqiqatda ikkita sababi bor:

  • Javobgarlik (odatda mijoz tomonidagi GUI ilovalari uchun) — Windows har bir jarayonga o'zining shaxsiy threadini beradi, shuning uchun bitta ilova cheksiz tsiklga kirganda, foydalanuvchining boshqa ilovalar bilan ishlashiga to'sqinlik qilmaydi. Xuddi shunday, mijoz tomonidagi GUI ilovangizda siz ishni boshqa threadga topshirishingiz mumkin, shunda GUI threadingiz foydalanuvchi kiritish hodisalariga javobgar bo'lib qoladi. Bu misolda siz mashinadagi mavjud yadrolardan ko'proq threadlar yaratayotgan, tizim resurslarini isrof qilayotgan va ishlashga zarar etkazayotgan bo'lishingiz mumkin. Biroq, foydalanuvchi javobgar foydalanuvchi interfeysiga ega bo'ladi va shuning uchun ilovangiz bilan umumiy tajriba yaxshiroq bo'ladi.
  • Ishlash (mijoz va server tomonidagi ilovalar uchun) — Windows har bir CPU uchun bitta thread rejalashtirishga qodir bo'lganligi va CPU'lar ushbu threadlarni bir vaqtning o'zida bajarishi mumkinligi sababli, ilovangiz bir nechta operatsiyalarni bir vaqtning o'zida parallal bajarish orqali o'z ishlashini yaxshilashi mumkin. Albatta, yaxshilangan ishlashni faqat ilovangiz ko'p CPU'li mashinada ishlayotgan bo'lsagina olasiz. Bugungi kunda ko'p CPU'li mashinalar keng tarqalgan, shuning uchun ilovangizni bir nechta yadrolardan foydalanishga loyihalash mantiqan to'g'ri va 27-bob va 28-bob "I/O-ga Asoslangan Asinxron Operatsiyalar"ning asosiy mavzusidir.

Endi men siz bilan o'z nazariyamni baham ko'rmoqchiman. Har bir kompyuterda aql bovar qilmas darajada kuchli resurs bor: CPU. Agar kimdir kompyuterga pul sarflasa, bu kompyuter ishlashi kerak. Men barcha CPU'lar doimiy ravishda 100 foiz foydalanishda ishlashi kerak deb hisoblayman (ikki ogohlantiruv bilan: birinchidan, agar kompyuter batareya quvvatida ishlayotgan bo'lsa, siz CPU'larning 100 foizda ishlashini xohlamasligingiz mumkin; ikkinchidan, ba'zi ma'lumotlar markazlari 10 ta mashinani 50 foiz CPU foydalanishda ishlashni afzal ko'radi).

Ammo hozir zamonlar o'zgardi. Kompyuterlar ulkan miqdordagi hisoblash quvvati bilan yetkaziladi. Avvalroq men Task Manager mening CPU'm vaqtning atigi 5 foizida band ekanligini ko'rsatayotganini aytdim. 80 yadroli protsessor chiqqanda, mashina deyarli doim hech narsa qilmayotganday ko'rinadi. Kompyuter sotib oluvchilarga ko'proq CPU'lar uchun ko'proq pul to'layotganday va kompyuter kamroq ish qilayotganday ko'rinadi!

Shu sababli apparat ishlab chiqaruvchilari ko'p yadroli kompyuterlarni foydalanuvchilarga sotishda qiynalmoqdalar: dasturiy ta'minot apparatning afzalliklaridan foydalanmayapti va foydalanuvchilar qo'shimcha CPU'li mashinalarni sotib olishdan hech qanday foyda ko'rmayapti. Men aytmoqchi bo'lgan narsa shuki, bizda hisoblash quvvati juda ko'p va yana ko'paymoqda, shuning uchun dasturchilar uni agressiv ravishda iste'mol qilishlari mumkin.

Mana bir misol: Visual Studio muharriridagi yozishni to'xtatsangiz, Visual Studio avtomatik ravishda kompilyatorni ishga tushiradi va kodingizni kompilyatsiya qiladi. Bu dasturchilarga ajoyib samaradorlik beradi, chunki ular yozgan paytda manba kodidagi ogohlantirishlar va xatolarni ko'rib, darhol tuzatishlari mumkin. Haqiqatan ham, bugungi kunda Edit-Build-Debug tsikli shunchaki Edit-Debug tsikliga aylanadi, chunki kompilyatsiya (build) jarayoni doim fonda sodir bo'ladi.

CPU dan agressiv foydalanishning ba'zi boshqa misollari: imlo tekshirish va grammatik tekshirish, jadvallarni qayta hisoblash, diskdagi fayllarni tez qidirish uchun indekslash va qattiq diskni I/O ishlashini yaxshilash uchun defragmentatsiya qilish.

Thread Rejalashtirish va Ustuvorliklar

Preemptiv ko'p vazifali (preemptive multithreaded) operatsion tizim qaysi threadlarni qachon va qancha vaqt rejalashtirish kerakligini aniqlash uchun qandaydir algoritmdan foydalanishi kerak. Ushbu bo'limda biz Windows ishlatadigin algoritmni ko'rib chiqamiz.

Avvalroq men har bir threadning yadro obyektida kontekst strukturasi borligini aytdim. Kontekst strukturasi thread oxirgi marta bajarilgandagi CPU registrlari holatini aks ettiradi. Vaqt tilimidan so'ng, Windows hozirda mavjud bo'lgan barcha thread yadro obyektlarini ko'rib chiqadi. Bu obyektlardan faqat biror narsa kutmayotganlari rejalashtirishga yaroqli hisoblanadi. Windows rejalashtirishga yaroqli thread yadro obyektlaridan birini tanlaydi va unga kontekst almashtirishni amalga oshiradi. Windows aslida har bir threadning qancha marta kontekst almashtirilganini hisoblab turadi. Microsoft Spy++ kabi vositadan foydalanib buni ko'rishingiz mumkin. 26-3-rasmda threadning xususiyatlari ko'rsatilgan. E'tibor bering, ushbu thread 31,768 marta rejalashtirilgan.

Windows preemptiv ko'p vazifali operatsion tizim deb ataladi, chunki thread istalgan vaqtda to'xtatilishi va boshqa thread rejalashtirilishi mumkin. Siz ko'rganingizdek, siz bu ustidan bir oz nazoratga egasiz, lekin ko'p emas. Shuni esda tutingki, siz threadingiz doim ishlab turishini va boshqa hech qanday thread ishga tushirilmasligini kafolatlashingiz mumkin emas.

Eslatma

Dasturchilar tez-tez mendan o'z threadlari biror hodisadan so'ng ma'lum vaqt oralig'ida ishga tushishini kafolatlash mumkinligini so'rashadi — masalan, tarmoqdan 1 ms ichida keladigan ma'lumotlarga javob beradigan ma'lum bir threadni qanday ta'minlash mumkin? Menda oson javob bor: qila olmaysiz. Real-time operatsion tizimlar bunday kafolatlarni berishi mumkin, lekin Windows real-time operatsion tizim emas. Real-time operatsion tizim o'zi ishlaydigan apparatni, uning qattiq disklari, klaviaturalari va boshqa komponentlarning kechikishlarini yaqindan bilishi kerak. Windows'ning maqsadi turli xil apparatlarda ishlash. Qisqasi, Windows real-time operatsion tizim bo'lishga mo'ljallanmagan. CLR boshqariladigan kodni yanada kamroq real-time qilishini ham qo'shib qo'yaman.

Ustuvorlik Sinflari va Darajalari

Har bir threadga 0 (eng past) dan 31 (eng yuqori) gacha ustuvorlik darajasi tayinlanadi. Tizim qaysi threadni CPU'ga tayinlash haqida qaror qilganda, u avval 31-ustuvorlikli threadlarni ko'rib chiqadi va ularni round-robin tarzida rejalashtirad. Agar 31-ustuvorlikli thread rejalashtirishga yaroqli bo'lsa, u CPU'ga tayinlanadi. Bu threadning vaqt tilimi oxirida tizim ishga tayyor boshqa 31-ustuvorlikli thread borligini tekshiradi; agar bo'lsa, o'sha threadni CPU'ga tayinlashga ruxsat beradi.

31-ustuvorlikli threadlar rejalashtirishga yaroqli ekan, tizim hech qachon 0 dan 30 gacha ustuvorlikli threadlarni CPU'ga tayinlamaydi. Bu holat ochlik (starvation) deb ataladi va u yuqori ustuvorlikli threadlar juda ko'p CPU vaqtini sarflab, pastroq ustuvorlikli threadlarning bajarilishini to'xtatganda yuzaga keladi.

Yuqori ustuvorlikli threadlar pastroq ustuvorlikli threadlarni har doim ustunlik qiladi (preempt). Masalan, agar 5-ustuvorlikli thread ishlayotgan bo'lsa va tizim yuqoriroq ustuvorlikli thread ishlashga tayyor ekanligini aniqlasa, tizim darhol pastroq ustuvorlikli threadni to'xtatadi (vaqt tilimining o'rtasida bo'lsa ham) va CPU'ni yuqoriroq ustuvorlikli threadga tayinlaydi.

Aytgancha, tizim yuklanganda, u nol sahifa threadi (zero page thread) deb ataladigan maxsus thread yaratadi. Bu thread 0-ustuvorlik tayinlanadi va butun tizimda 0-ustuvorlikda ishlaydigan yagona thread. Nol sahifa threadi boshqa threadlar ish bajarishi kerak bo'lmaganda tizimdagi RAM'ning bo'sh sahifalarini nolga tenglashtirish uchun javobgardir.

Microsoft dasturchilarga ustuvorlik darajalarini threadlarga tayinlash juda qiyin bo'lishini tushundi. Bu thread 10-ustuvorlik darajasida bo'lishi kerakmi? Bu boshqa thread 23-ustuvorlik darajasida bo'lishi kerakmi? Bu muammoni hal qilish uchun, Windows ustuvorlik darajalari tizimi ustiga abstrakt qatlam oshkor qildi.

Ilovangizni loyihalashtirayotganda, ilovangiz mashinada ishlayotgan boshqa ilovalardan ko'proq yoki kamroq javobgar bo'lishi kerakligini hal qilishingiz kerak. Keyin qaroringizni aks ettiradigan jarayon ustuvorlik sinfini tanlaysiz. Windows oltita jarayon ustuvorlik sinfini qo'llab-quvvatlaydi: Idle, Below Normal, Normal, Above Normal, High va Realtime. Albatta, Normal standart va eng ko'p qo'llanadigan sinfdir.

Idle ustuvorlik sinfi tizim deyarli hech narsa qilmayotganda ishlaydigan ilovalar uchun (masalan, ekran saqlovchilari) mukammal. High ustuvorlik sinfini faqat mutlaqo zarur bo'lganda ishlating. Realtime ustuvorlik sinfidan foydalanishdan saqlaning; u juda yuqori va operatsion tizim vazifalari (masalan, disk I/O va tarmoq trafigi) bilan to'qnashishi mumkin.

Eslatma

Umumiy tizim ishlashini silliq saqlash uchun, jarayon foydalanuvchi Increase Scheduling Priority imtiyoziga ega bo'lmasa, Realtime ustuvorlik sinfida ishlay olmaydi. Administrator yoki kuchli foydalanuvchi sifatida tayinlangan har qanday foydalanuvchi sukut bo'yicha bu imtiyozga ega.

Ustuvorlik sinfini tanlagandan so'ng, ilovangiz boshqa ilovalar bilan qanday munosabatda bo'lishi haqida o'ylashni to'xtatib, shunchaki ilovangiz ichidagi threadlarga e'tibor qaratishingiz kerak. Windows yettita nisbiy thread ustuvorligini qo'llab-quvvatlaydi: Idle, Lowest, Below Normal, Normal, Above Normal, Highest va Time-Critical. Bu ustuvorliklar jarayonning ustuvorlik sinfiga nisbiy. Normal nisbiy thread ustuvorligi standart va eng ko'p qo'llanadigan.

Xulosa qilib aytganda, jarayoningiz ustuvorlik sinfining a'zosi va o'sha jarayon ichida siz threadlarga bir-biriga nisbiy thread ustuvorliklarini tayinlaysiz. E'tibor bering, men 0 dan 31 gacha ustuvorlik darajalari haqida hech narsa aytmadim. Ilova dasturchilari ustuvorlik darajalari bilan hech qachon to'g'ridan-to'g'ri ishlamaydi. Buning o'rniga, tizim jarayonning ustuvorlik sinfi va threadning nisbiy ustuvorligini ustuvorlik darajasiga moslashtiradi.

Ustuvorlik Jadvali (Table 26-1)

Quyidagi jadval jarayon ustuvorlik sinfi va nisbiy thread ustuvorliklari qanday ustuvorlik darajalariga moslanishini ko'rsatadi:

Nisbiy Thread Ustuvorligi Idle Below Normal Normal Above Normal High Realtime
Time-Critical151515151531
Highest6810121526
Above Normal579111425
Normal468101324
Below Normal35791223
Lowest24681122
Idle1111116

Masalan, Normal jarayondagi Normal thread 8-ustuvorlik darajasiga ega. Ko'pchilik jarayonlar Normal ustuvorlik sinfida va ko'pchilik threadlar Normal thread ustuvorligida bo'lganligi sababli, tizimdagi ko'pchilik threadlar 8-ustuvorlik darajasiga ega.

Agar Normal threadingiz High-priority jarayonda bo'lsa, thread 13-ustuvorlik darajasiga ega bo'ladi. Agar jarayonning ustuvorlik sinfini Idle ga o'zgartirsangiz, threadning ustuvorlik darajasi 4 ga tushadi. Esda tutingki, thread ustuvorliklari jarayonning ustuvorlik sinfiga nisbiy. Agar jarayonning ustuvorlik sinfini o'zgartirsangiz, threadning nisbiy ustuvorligi o'zgarmaydi, lekin uning ustuvorlik raqami o'zgaradi.

E'tibor bering, jadvalda thread uchun 0-ustuvorlik darajasiga erishishning hech qanday usuli ko'rsatilmagan. Buning sababi shuki, 0-ustuvorlik nol sahifa threadi uchun zaxiralangan va tizim boshqa hech qanday threadga 0-ustuvorlik berishga ruxsat bermaydi. Shuningdek, quyidagi ustuvorlik darajalariga erishib bo'lmaydi: 17, 18, 19, 20, 21, 27, 28, 29 va 30.

Eslatma

Jarayon ustuvorlik sinfi kontseptsiyasi ba'zi odamlarni chalkashtirib yuboradi. Ular bu Windows jarayonlarni rejalashtiradi degan ma'noni anglatadi deb o'ylashadi. Biroq, Windows hech qachon jarayonlarni rejalashtirmaydi; Windows faqat threadlarni rejalashtiradi. Jarayon ustuvorlik sinfi Microsoft ilovangizning boshqa ishlaydigan ilovalar bilan qanday taqqoslanishini aqlan anglashingizga yordam berish uchun yaratgan abstrakt tushuncha; boshqa hech qanday maqsadga xizmat qilmaydi.

Muhim

Threadning ustuvorligini boshqa threadning ustuvorligini oshirish o'rniga pasaytirish yaxshiroq. Odatda threadning ustuvorligini uzoq davom etuvchi hisoblashga asoslangan vazifani (kompilyatsiya, imlo tekshirish, jadval qayta hisoblash va h.k.) bajaradigan bo'lsa pasaytirasiz. Threadning ustuvorligini oshirish faqat thread biror narsaga juda tez javob berib, keyin kutish holatiga qaytishi kerak bo'lgandagina maqsadga muvofiq. Yuqori ustuvorlikli threadlar ko'pincha kutish holatida bo'lishi kerak, shunda ular butun tizim javobgarligiga ta'sir qilmasin.

Desktop ilovalar (Windows Store ilovalari emas) uchun, System.Diagnostics nom maydoni Process va ProcessThread sinflarini o'z ichiga oladi. Bu sinflar mos ravishda jarayon va threadning Windows ko'rinishini ta'minlaydi.

Boshqa tomondan, ilovangiz o'z threadlarining nisbiy thread ustuvorligini Thread'ning Priority xususiyatini o'rnatish orqali o'zgartirishi mumkin, unga ThreadPriority sanab o'tilgan turida aniqlangan beshta qiymatdan birini (Lowest, BelowNormal, Normal, AboveNormal yoki Highest) uzatadi. Biroq, xuddi Windows 0 va real-time diapazonini o'zi uchun zaxiralagani kabi, CLR Idle va Time-Critical ustuvorlik darajalarini o'zi uchun zaxiralaydi. Shuning uchun, boshqariladigan dasturchi sifatida siz haqiqatan ham faqat 26-1-jadvalda ta'kidlangan beshta nisbiy thread ustuvorligidan foydalanasiz.

Foreground Threadlar va Background Threadlar

Thread Holatlari (Lifecycle)
Unstarted
Running
WaitSleepJoin
Running
Stopped
Thread yaratilgandan to'xtaguncha turli holatlardan o'tadi

CLR har bir threadni foreground (oldingi) thread yoki background (fon) thread deb hisoblaydi. Jarayondagi barcha foreground threadlar ishlashni to'xtatganda, CLR istalgan background threadlarni majburiy tugatadi. Bu background threadlar darhol tugaydi; hech qanday istisno (exception) tashlanmaydi.

Shuning uchun, siz haqiqatan ham tugashini xohlagan vazifalarni bajarish uchun foreground threadlardan foydalanishingiz kerak, masalan, xotira buferidan diskka ma'lumotlarni yozish. Va siz muhim bo'lmagan vazifalar uchun background threadlardan foydalanishingiz kerak, masalan, jadval kataklarini qayta hisoblash yoki yozuvlarni indekslash, chunki bu ish ilova qayta ishga tushirilganda davom ettirishi mumkin va ilova faol qolishi uchun hech qanday sabab yo'q.

CLR foreground va background threadlar tushunchasini AppDomain'larni yaxshiroq qo'llab-quvvatlash uchun taqdim etishi kerak edi. Har bir AppDomain o'z foreground threadiga ega bo'lgan alohida ilovani ishga tushirishi mumkin edi. Agar bitta ilova chiqsa va uning foreground threadi tugasa, CLR hali ham ishlashda davom etishi kerak, shunda boshqa ilovalar ishlashda davom etsin. Barcha ilovalar chiqib, barcha foreground threadlari tugagandan so'ng, butun jarayon yo'q qilinishi mumkin.

Quyidagi kod foreground va background threadlar orasidagi farqni namoyish etadi:

using System;
using System.Threading;

public static class Program {
   public static void Main() {
      // Create a new thread (defaults to foreground)
      Thread t = new Thread(Worker);

      // Make the thread a background thread
      t.IsBackground = true;

      t.Start(); // Start the thread
      // If t is a foreground thread, the application won't die for about 10 seconds
      // If t is a background thread, the application dies immediately
      Console.WriteLine("Returning from Main");
   }

   private static void Worker() {
      Thread.Sleep(10000);   // Simulate doing 10 seconds of work

      // The following line only gets displayed if this code is executed by a foreground thread
      Console.WriteLine("Returning from Worker");
   }
}

Threadni istalgan vaqtda foreground'dan background'ga va aksincha o'zgartirish mumkin. Ilova asosiy threadi va Thread obyektini yaratish orqali aniq yaratilgan har qanday threadlar sukut bo'yicha foreground threadlaridir. Boshqa tomondan, thread pool threadlari sukut bo'yicha background threadlaridir. Shuningdek, boshqariladigan ijro muhitiga kiradigan native kod tomonidan yaratilgan har qanday threadlar background threadlar deb belgilanadi.

Muhim

Foreground threadlardan iloji boricha foydalanmang. Men bir marta konsalting ishiga taklif qilingandim, ilova tugatilmayotgan edi. Bir necha soat muammoni tekshirgandan so'ng, ma'lum bo'lishicha, UI komponenti foreground thread (standart) yaratayotgan ekan va shu sababli jarayon tugatilmayotgan edi. Biz komponentni thread pool'dan foydalanishga o'zgartirdik va muammo hal bo'ldi, samaradorlik ham yaxshilandi.

Endi Nima?

Ushbu bobda men threadlar haqidagi asosiy ma'lumotlarni tushuntirdim va umid qilamanki, sizga threadlar juda qimmat resurslar ekanligini va ulardan tejamkorlik bilan foydalanish kerakligini aniq ko'rsatdim. Bunga erishishning eng yaxshi usuli thread pool'dan foydalanishdir. Thread pool sizning o'rniga threadlarni yaratish va yo'q qilishni boshqaradi. Thread pool turli vazifalar uchun qayta ishlatiladigan threadlar to'plamini yaratadi, shuning uchun ilovangiz barcha ishlarini bajarish uchun faqat bir nechta threadlarni talab qiladi.

27-bobda men hisoblashga asoslangan (compute-bound) operatsiyalarni bajarish uchun thread pool'dan qanday foydalanishga e'tibor qarataman. Keyin, 28-bobda, men I/O-ga asoslangan (I/O-bound) operatsiyalarni bajarish uchun thread pool'dan qanday foydalanishni muhokama qilaman. Ko'p stsenariylarda siz asinxron hisoblashga asoslangan va I/O-ga asoslangan operatsiyalarni thread sinxronizatsiyasi umuman talab etilmaydigan tarzda bajarishingiz mumkin. Biroq, thread sinxronizatsiyasi talab etiladigan ba'zi stsenariylar ham mavjud va turli sinxronizatsiya konstruksiyalari orasidagi farq 29-bobda, "Primitiv Thread Sinxronizatsiya Konstruksiyalari" va 30-bobda, "Gibrid Thread Sinxronizatsiya Konstruksiyalari" da muhokama qilinadi.

Ushbu muhokamani tugatishdan oldin, men threadlar bilan keng ishlashimni ta'kidlamoqchiman, chunki Windows NT 3.1 ning birinchi beta versiyasi 1992-yil atrofida mavjud edi. Va .NET beta holatida bo'lganda, men asinxron dasturlash va thread sinxronizatsiyasini soddalashtira oladigan sinflar kutubxonasini ishlab chiqarishni boshladim. Bu kutubxona Wintellect Power Threading Library deb ataladi va u bepul yuklab olinishi va foydalanishi mumkin.

Xulosa

Ushbu bobda siz threadlarning asosiy tushunchalarini, Windows nima uchun threadlarni qo'llab-quvvatlashini, thread yuklamasini (xotira va vaqt), kontekst almashtirishni, CPU tendensiyalarini, CLR va Windows threadlari aloqasini, thread rejalashtirish va ustuvorliklarni, hamda foreground va background threadlar orasidagi farqni o'rgandingiz. Eng muhimi, threadlar qimmat resurslar ekanligini va ularni tejamkorlik bilan ishlatish kerakligini tushundingiz. Keyingi boblarda thread pool yordamida samarali ko'p oqimli dasturlashni o'rganasiz.