Mavzu holatlari. Kritik bo'limlar Windows operatsion tizimida sinxronlashni yakunlash

Ushbu sinxronizatsiya ob'ekti faqat uni yaratgan jarayonda mahalliy sifatida ishlatilishi mumkin. Qolgan ob'ektlar turli jarayonlarning iplarini sinxronlashtirish uchun ishlatilishi mumkin. "Muhim bo'lim" ob'ektining nomi dastur kodining (bo'limning) ba'zi operatsiyalarni bajaradigan qismini mavhum tanlash bilan bog'liq bo'lib, ularning tartibini buzish mumkin emas. Ya'ni, ushbu bo'limning kodini bir vaqtning o'zida bajarishga ikki xil ipning urinishi xatolikka olib keladi.

Masalan, bunday bo'lim bilan yozuvchi funktsiyalarini himoya qilish qulay bo'lishi mumkin, chunki bir vaqtning o'zida bir nechta yozuvchilarga kirishni istisno qilish kerak.

Kritik bo'lim uchun ikkita operatsiya kiritiladi:

bo'limga kiring Har qanday mavzu muhim bo'limda bo'lsa, boshqa barcha mavzular unga kirishga harakat qilganda avtomatik ravishda kutishni to'xtatadi. Ushbu bo'limga allaqachon kirgan mavzu uni bo'shatishini kutmasdan bir necha marta kirishi mumkin.

bo'limni tark eting Ip bo'limni tark etganda, ushbu ipning bo'limga kirishlari sonining hisoblagichi kamayadi, shuning uchun bo'lim boshqa iplar uchun bo'shatiladi, agar ip qismdan qancha marta kirsa, bo'shatiladi. Muhim bo'lim chiqarilganda, ushbu bo'limga kirish uchun ruxsatni kutib, faqat bitta mavzu uyg'onadi.

Umuman olganda, boshqa Win32 bo'lmagan API'larda (masalan, OS/2) muhim bo'lim sinxronizatsiya ob'ekti sifatida emas, balki faqat bitta dastur chizig'i tomonidan bajarilishi mumkin bo'lgan dastur kodining bir qismi sifatida ko'rib chiqiladi. Ya'ni, kritik bo'limga kirish, ushbu bo'limdan chiqishgacha ipni almashtirish mexanizmining vaqtinchalik to'xtatilishi sifatida qabul qilinadi. Win32 API muhim bo'limlarni ob'ektlar sifatida ko'rib chiqadi, bu ba'zi chalkashliklarga olib keladi - ular o'z xususiyatlarida nomsiz eksklyuziv ob'ektlarga juda yaqin ( mutex, pastga qarang).

Kritik bo'limlardan foydalanganda, bo'limda juda katta kod bo'laklarini ajratmaslikka ehtiyot bo'lish kerak, chunki bu boshqa iplarning bajarilishida sezilarli kechikishlarga olib kelishi mumkin.

Misol uchun, allaqachon ko'rib chiqilgan uyumlarga nisbatan, barcha yig'ish funktsiyalarini tanqidiy qism bilan himoya qilish mantiqiy emas, chunki o'quvchi funktsiyalari parallel ravishda bajarilishi mumkin. Bundan tashqari, hatto yozuvchilarni sinxronlashtirish uchun ham tanqidiy bo'limdan foydalanish noqulay bo'lib tuyuladi - yozuvchini o'quvchilar bilan sinxronlashtirish uchun, ikkinchisi hali ham ushbu bo'limga kirishi kerak bo'ladi, bu esa amalda barcha funktsiyalarni yagona himoya bilan himoya qilishga olib keladi. Bo'lim.

Muhim bo'limlardan samarali foydalanishning bir nechta holatlari mavjud:

kitobxonlar yozuvchilar bilan ziddiyatga tushmaydi (faqat yozuvchilarni himoya qilish kerak);

barcha mavzular taxminan teng huquqlarga ega (aytaylik, siz sof yozuvchilar va o'quvchilarni ajratib bo'lmaydi);

Murakkab ob'ektda ketma-ket operatsiyalarni himoya qilish uchun bir nechta standart ob'ektlardan iborat birikma sinxronlash ob'ektlarini qurishda.

Maqolaning oldingi qismlarida men gaplashdim umumiy tamoyillar va ko'p bosqichli ilovalarni yaratish uchun maxsus usullar. Turli xil iplar deyarli har doim vaqti-vaqti bilan bir-biri bilan o'zaro ta'sir qilishlari kerak va sinxronizatsiya zarurati muqarrar ravishda paydo bo'ladi. Bugun biz Windows-ning eng muhim, eng kuchli va ko'p qirrali sinxronlash vositasini ko'rib chiqamiz: yadro sinxronlash ob'ektlari.

WaitForMultipleObjects va boshqa kutish funktsiyalari

Esingizda bo'lsa, iplarni sinxronlashtirish uchun siz odatda iplardan birining bajarilishini vaqtincha to'xtatib qo'yishingiz kerak. Biroq, uni vositalar orqali tarjima qilish kerak operatsion tizim CPU vaqtini talab qilmaydigan kutish holatiga. Biz buni amalga oshiradigan ikkita funktsiyani bilamiz: SuspendThread va ResumeThread . Ammo maqolaning oldingi qismida aytganimdek, ba'zi xususiyatlar tufayli bu funktsiyalar sinxronizatsiya uchun mos emas.

Bugun biz boshqa funktsiyani ko'rib chiqamiz, u ham ipni kutish holatiga keltiradi, lekin SuspendThread/ResumeThread dan farqli o'laroq, u sinxronizatsiyani tashkil qilish uchun maxsus mo'ljallangan. Bu WaitForMultipleObjects. Bu xususiyat juda muhim bo'lgani uchun, men API tafsilotlariga kirmaslik qoidasidan biroz chetga chiqaman va bu haqda batafsilroq gaplashaman, hatto uning prototipini ham keltiraman:

DWORD WaitForMultipleObjects (

DWORD nCount , // lpHandles massividagi ob'ektlar soni

CONST TUTAQ * lpHandles , // yadro obyekti deskriptorlari massiviga ko'rsatgich

BOOL bWaitAll , // barcha ob'ektlarni kutish kerakmi yoki bittasi kifoya qiladimi yoki yo'qligini bildiruvchi bayroq

DWORD dwMillisoniyalar // taym-aut; turib qolish; tanaffus

Ushbu funktsiyaning asosiy parametri yadro ob'ekti tutqichlari massiviga ko'rsatgichdir. Quyida ushbu ob'ektlar nima ekanligi haqida gaplashamiz. Hozircha, bu ob'ektlarning har biri ikkita holatda bo'lishi mumkinligini bilish biz uchun muhim: neytral yoki "signal" (signalli holat). Agar bWaitAll bayrog'i FALSE bo'lsa, ob'ektlardan kamida bittasi signal berishi bilan funksiya qaytadi. Va agar bayroq TRUE bo'lsa, bu barcha ob'ektlar bir vaqtning o'zida signal berishni boshlaganda sodir bo'ladi (ko'rib turganimizdek, bu funktsiyaning eng muhim xususiyatidir). Birinchi holda, qaytarilgan qiymat bo'yicha siz ob'ektlardan qaysi biri signal berganligini bilib olishingiz mumkin. Undan WAIT_OBJECT_0 konstantasini ayirish kerak va siz lpHandles massivida indeks olasiz. Agar kutish vaqti oxirgi parametrda ko'rsatilgan vaqtdan oshsa, funktsiya kutishni to'xtatadi va WAIT_TIMEOUT qiymatini qaytaradi. Vaqt tugashi sifatida siz doimiy INFINITE ni belgilashingiz mumkin va keyin funktsiya "to'xtaguncha" kutadi yoki siz aksincha 0 ga teng bo'lishi mumkin, keyin esa ip umuman to'xtatilmaydi. Ikkinchi holda, funksiya darhol qaytadi, lekin uning natijasi sizga ob'ektlarning holatini aytib beradi. Oxirgi texnika juda tez-tez ishlatiladi. Ko'rib turganingizdek, bu funktsiya boy imkoniyatlarga ega. Yana bir nechta WaitForXXX funksiyalari mavjud, ammo ularning barchasi asosiy mavzudagi o'zgarishlardir. Xususan, WaitForSingleObject uning soddalashtirilgan versiyasidir. Qolganlarning har biri o'ziga xos qo'shimcha funktsiyalarga ega, lekin umuman olganda, kamroq ishlatiladi. Masalan, ular nafaqat yadro obyektlarining signallariga, balki ip navbatiga yangi oyna xabarlarining kelishiga ham javob berish imkonini beradi. Ularning tavsifi, shuningdek, haqida batafsil ma'lumot WaitForMultipleObjects , siz odatdagidek MSDN-da topasiz.

Endi bu sirli "yadro ob'ektlari" nima haqida. Boshlash uchun ular iplar va jarayonlarning o'zlarini o'z ichiga oladi. Ular tugagandan so'ng darhol signalizatsiya holatiga kiradilar. Bu juda muhim xususiyat, chunki ko'pincha mavzu yoki jarayon qachon tugashini kuzatish kerak bo'ladi. Masalan, ishchi iplar to'plamiga ega bo'lgan server dasturimiz to'ldirilishi kerak. Shu bilan birga, boshqaruv chizig'i ishchi iplarni qandaydir tarzda ishni tugatish vaqti kelganligi to'g'risida xabardor qilishi kerak (masalan, global bayroqni o'rnatish orqali) va keyin to'g'ri bajarish uchun zarur bo'lgan hamma narsani bajarib, barcha iplar tugashini kuting. harakat: resurslarni bo'shatish, mijozlarni o'chirish haqida xabardor qilish, tarmoq ulanishlarini yopish va hk.

Ish oxirida iplar signalni yoqishi, ipni tugatish bilan sinxronizatsiya muammosini hal qilishni juda osonlashtiradi:

// Oddiylik uchun faqat bitta ishchi ipga ega bo'laylik. Keling, uni ishga tushiramiz:

HANDLE hWorkerThread = :: CreateThread (...);

// Ish tugashidan oldin biz qandaydir tarzda ishchi mavzuga yuklash vaqti kelganligini aytishimiz kerak.

// Mavzu tugashini kuting:

DWORD dwWaitResult = :: WaitForSingleObject ( hWorkerThread , CHEKSIZ );

agar( dwWaitResult != WAIT_OBJECT_0 ) { /* xato bilan ishlash */ }

// Oqimning "dastasi" yopilishi mumkin:

TASHIRISH (:: CloseHandle ( hWorkerThread );

/* Agar CloseHandle bajarilmasa va FALSE qiymatini qaytarsa, men istisno qilmayman. Birinchidan, agar bu tizim xatosi tufayli sodir bo'lgan bo'lsa ham, bu bizning dasturimiz uchun bevosita oqibatlarga olib kelmaydi, chunki biz tutqichni yopganimiz sababli, kelajakda u bilan hech qanday ish kutilmaydi. Aslida, CloseHandle-ning ishdan chiqishi faqat dasturingizdagi xatolikni anglatishi mumkin. Shuning uchun, dasturni disk raskadrovka bosqichida o'tkazib yubormaslik uchun biz VERIFY makrosini bu erga kiritamiz. */

Jarayon tugashini kutayotgan kod o'xshash ko'rinadi.

Agar bunday o'rnatilgan qobiliyat bo'lmasa, ishchi ip qandaydir tarzda uning tugallanganligi haqidagi ma'lumotni asosiy ipning o'ziga etkazishi kerak edi. Agar bu oxirgi marta bajarilgan bo'lsa ham, asosiy ish zarrachasi ishchining bajarish uchun kamida bir nechta assembler ko'rsatmalari qolmaganligiga ishonch hosil qila olmaydi. DA individual vaziyatlar(masalan, agar ip kodi DLL-da bo'lsa, u tugashi bilan olib tashlanishi kerak) bu halokatli bo'lishi mumkin.

Sizga shuni eslatmoqchimanki, ip (yoki jarayon) tugatilgandan keyin ham uning tutqichlari CloseHandle funksiyasi tomonidan aniq yopilmaguncha o'z kuchida qoladi. (Aytgancha, buni qilishni unutmang!) Bu istalgan vaqtda oqim holatini tekshirishingiz uchun amalga oshiriladi.

Shunday qilib, WaitForMultipleObjects funktsiyasi (va uning analoglari) sinxronizatsiya ob'ektlarining holati, xususan, boshqa oqimlar va jarayonlar bilan ipning bajarilishini sinxronlashtirishga imkon beradi.

Maxsus yadro ob'ektlari

Sinxronizatsiya uchun maxsus mo'ljallangan yadro ob'ektlarini ko'rib chiqishga o'tamiz. Bu hodisalar, semaforlar va mutekslar. Keling, ularning har birini qisqacha ko'rib chiqaylik:

voqea

Ehtimol, eng oddiy va eng asosiy sinxronizatsiya ob'ekti. Bu faqat SetEvent / ResetEvent funktsiyalari bilan o'rnatilishi mumkin bo'lgan bayroq: signalizatsiya yoki neytral. Voqea kutayotgan ipga qandaydir voqea sodir bo'lganligi haqida signal berishning eng qulay usulidir (shuning uchun u shunday nomlangan) va siz ishlashni davom ettirishingiz mumkin. Hodisadan foydalanib, biz ishchi ipni ishga tushirishda sinxronizatsiya muammosini osongina hal qilishimiz mumkin:

// Oddiylik uchun hodisa dastagini global o'zgaruvchida saqlaylik:

HANDLE g_hEventInitComplete = NULL ; // hech qachon o'zgaruvchini ishga tushirilmagan holda qoldirmang!

{ // asosiy mavzudagi kod

// hodisa yaratish

g_hEventInitComplete = :: Event yaratish ( NULL,

FALSE , // bu parametr haqida keyinroq gaplashamiz

FALSE , // boshlang'ich holati - neytral

agar(! g_hEventInitComplete ) { /* Xatolarni qayta ishlash haqida unutmang */ }

// ishchi ipni yarating

DWORD idWorkerThread = 0 ;

HANDLE hWorkerThread = :: CreateThread ( NULL , 0 , & WorkerThreadProc , NULL , 0 , & idWorkerThread );

agar(! hWorkerThread ) { /* xato bilan ishlash */ }

// ishchi ipdan signalni kuting

DWORD dwWaitResult = :: WaitForSingleObject ( g_hEventInitComplete , CHEKSIZ );

agar( dwWaitResult != WAIT_OBJECT_0 ) { /* xato */ }

// endi ishchi ish zarrachasi ishga tushirilganiga ishonch hosil qilishingiz mumkin.

TASHIRISH (:: CloseHandle ( g_hEventInitComplete )); // keraksiz narsalarni yopishni unutmang

g_hEventInitComplete = NULL ;

// ish jarayoni funktsiyasi

DWORD WINAPI WorkerThreadProc ( LPVOID_parametri )

InitializeWorker (); // ishga tushirish

// ishga tushirish tugallanganligi haqida signal

BOOL yaxshi = :: SetEvent ( g_hEventInitComplete );

agar(! yaxshi ) { /* xato */ }

Shuni ta'kidlash kerakki, hodisalarning ikki xil turi mavjud. CreateEvent funksiyasining ikkinchi parametri yordamida ulardan birini tanlashimiz mumkin. Agar u TRUE bo'lsa, holati faqat qo'lda, ya'ni SetEvent/ResetEvent funktsiyalari orqali boshqariladigan hodisa yaratiladi. Agar u FALSE bo'lsa, avtomatik tiklash hodisasi yaratiladi. Bu shuni anglatadiki, ma'lum bir hodisani kutayotgan ba'zi bir mavzu ushbu voqea signali bilan chiqarilishi bilanoq, u avtomatik ravishda neytral holatga qayta tiklanadi. Ularning farqi bir vaqtning o'zida bir nechta mavzular bir hodisani kutayotgan vaziyatda eng aniq namoyon bo'ladi. Qo'lda boshqariladigan hodisa boshlang'ich to'pponchaga o'xshaydi. Signalli holatga o'rnatilishi bilanoq, barcha iplar bir vaqtning o'zida chiqariladi. Boshqa tomondan, avtomatik qayta o'rnatish hodisasi metro turniketiga o'xshaydi: u faqat bitta oqimni chiqaradi va neytral holatga qaytadi.

Muteks

Hodisa bilan solishtirganda, bu ko'proq ixtisoslashgan ob'ekt. U odatda bir nechta ish zarralari tomonidan ulashilgan manbaga kirish kabi umumiy sinxronizatsiya muammosini hal qilish uchun ishlatiladi. Ko'p jihatdan, u avtomatik tiklash hodisasiga o'xshaydi. Asosiy farq shundaki, u ma'lum bir ipga maxsus bog'lanishga ega. Muteks signalli holatda bo'lsa, bu uning erkin ekanligini va hech qanday ipga tegishli emasligini bildiradi. Ma'lum bir ip ushbu mutexni kutishi bilanoq, ikkinchisi neytral holatga qayta o'rnatiladi (bu erda xuddi avtomatik tiklash hodisasi kabi) va ip ReleaseMutex funktsiyasi bilan mutexni aniq chiqarmaguncha uning egasi bo'ladi, yoki tugatadi. Shunday qilib, bir vaqtning o'zida faqat bitta ip umumiy ma'lumotlar bilan ishlashiga ishonch hosil qilish uchun bunday ish amalga oshiriladigan barcha joylar juftlik bilan o'ralgan bo'lishi kerak: WaitFor - ReleaseMutex :

HANDLE g_hMutex ;

// Muteks tutqichi global o'zgaruvchida saqlansin. Albatta, u ishchi iplar boshlanishidan oldin, oldindan yaratilishi kerak. Bu allaqachon qilingan deb faraz qilaylik.

int iWait = :: WaitForSingleObject ( g_hMutex , CHEKSIZ );

almashtirish( iWait ) {

hol WAIT_OBJECT_0 : // Hammasi yaxshi

tanaffus;

hol KUTILGAN : /* ReleaseMutex-ga qo'ng'iroq qilishni unutib, ba'zi mavzu tugadi. Ehtimol, bu sizning dasturingizdagi xatolikni anglatadi! Shuning uchun, har qanday holatda, biz bu erga ASSERT ni kiritamiz, ammo oxirgi versiyada (chiqarish) biz ushbu kodni muvaffaqiyatli deb hisoblaymiz. */

ASSERT ( yolg'on );

tanaffus;

standart:

// Xatolarni hal qilish bu erda bo'lishi kerak.

// Muteks bilan himoyalangan kod qismi.

ProcessCommonData ();

TASHIRISH (:: ReleaseMutex ( g_hMutex ));

Nima uchun mutex avtomatik tiklash hodisasidan yaxshiroq? Yuqoridagi misolda u ham ishlatilishi mumkin, faqat ReleaseMutex ni SetEvent bilan almashtirish kerak bo'ladi. Biroq, quyidagi qiyinchilik paydo bo'lishi mumkin. Ko'pincha siz bir nechta joylarda umumiy ma'lumotlar bilan ishlashingiz kerak. Bizning misolimizdagi ProcessCommonData bir xil ma'lumotlar bilan ishlaydigan va o'zining WaitFor - ReleaseMutex juftligiga ega funktsiyani chaqirsa nima bo'ladi (amalda bu juda keng tarqalgan)? Agar biz hodisadan foydalansak, dastur aniq osilib qoladi, chunki himoyalangan blok ichida hodisa neytral holatda. Muteks yanada qiyinroq. U har doim asosiy ip uchun signal holatida qoladi, garchi u boshqa barcha iplar uchun neytral holatda bo'lsa ham. Shuning uchun, agar ip mutexni olgan bo'lsa, WaitFor funksiyasini qayta chaqirish bloklanmaydi. Bundan tashqari, hisoblagich ham mutexga o'rnatilgan, shuning uchun ReleaseMutex WaitFor ga qo'ng'iroqlar bo'lgani kabi bir xil miqdorda chaqirilishi kerak. Shunday qilib, biz umumiy ma'lumotlar bilan ishlaydigan har bir kod qismini WaitFor - ReleaseMute x juftligi bilan xavfsiz himoya qila olamiz, bu kodni rekursiv chaqirish mumkinligidan xavotirlanmasdan. Bu mutexni ishlatish uchun juda qulay vositaga aylantiradi.

Semafor

Bundan ham aniqroq sinxronizatsiya ob'ekti. Tan olishim kerakki, mening amaliyotimda hali foydali bo'ladigan holat bo'lmagan. Semafor bir vaqtning o'zida resursda ishlashi mumkin bo'lgan iplarning maksimal sonini cheklash uchun mo'ljallangan. Aslini olganda, sema bu hisoblagichga ega hodisadir. Bu hisoblagich noldan katta bo'lsa, semafor signalizatsiya holatida bo'ladi. Biroq, WaitFor ga har bir qo'ng'iroq bu hisoblagichni nolga aylantirguncha va semafor neytral holatga o'tguncha bir marta kamaytiradi. Muteks kabi semaforda hisoblagichni oshiradigan ReleaseSemaphor funksiyasi mavjud. Biroq, mutexdan farqli o'laroq, semafor ip bilan bog'lanmagan va WaitFor/ReleaseSemaphor ni qayta chaqirish hisoblagichni kamaytiradi/ko'paytiradi.

Semafordan qanday foydalanish mumkin? Masalan, u ko'p ish zarralarini sun'iy ravishda cheklash uchun ishlatilishi mumkin. Yuqorida aytib o'tganimdek, bir vaqtning o'zida juda ko'p faol ish zarralari tez-tez kontekst o'zgartirilishi tufayli butun tizimning ish faoliyatini sezilarli darajada yomonlashtirishi mumkin. Va agar biz juda ko'p ishchi iplarni yaratishimiz kerak bo'lsa, biz bir vaqtning o'zida faol iplar sonini protsessorlar soni bo'yicha raqam bilan cheklashimiz mumkin.

Yadro sinxronlash ob'ektlari haqida yana nima deyish mumkin? Ularga nom berish juda qulay. Sinxronizatsiya ob'ektlarini yaratadigan barcha funktsiyalar mos keladigan parametrga ega: CreateEvent , CreateMutex , CreateSemaphore . Agar siz, masalan, CreateEvent-ga ikki marta qo'ng'iroq qilsangiz, ikkala safar ham bir xil bo'sh bo'lmagan nomni ko'rsatsangiz, ikkinchi marta funksiya yangi ob'ekt yaratish o'rniga mavjud ob'ektning dastagini qaytaradi. Bu ikkinchi qo'ng'iroq boshqa jarayondan qilingan bo'lsa ham sodir bo'ladi. Ikkinchisi, turli jarayonlarga tegishli iplarni sinxronlashtirishni xohlagan hollarda juda qulaydir.

Sinxronizatsiya ob'ekti endi kerak bo'lmaganda, men yuqorida mavzular haqida gapirganimda aytib o'tgan CloseHandle funksiyasini chaqirishni unutmang. Aslida, u ob'ektni darhol o'chirib tashlamaydi. Gap shundaki, ob'ekt bir nechta tutqichga ega bo'lishi mumkin va keyin u faqat oxirgisi yopilganda o'chiriladi.

Shuni eslatmoqchiman Eng yaxshi yo'l CloseHandle yoki shunga o'xshash "tozalash" funktsiyasi, hatto g'ayritabiiy vaziyatda ham chaqirilishini ta'minlash uchun uni destruktorga qo'yish kerak. Aytgancha, bu Kirill Pleshivtsevning "Smart Destructor" maqolasida bir marta yaxshi va batafsil tasvirlangan. Yuqoridagi misollarda men ushbu texnikani faqat ta'lim maqsadlarida ishlatmadim, shuning uchun API funktsiyalari ishi ko'proq ingl. Haqiqiy kodda siz doimo tozalash uchun aqlli destruktorlar bilan o'rash sinflaridan foydalanishingiz kerak.

Aytgancha, ReleaseMutex funktsiyasi va shunga o'xshashlar bilan doimo CloseHandle bilan bir xil muammo yuzaga keladi. Bu ish qanchalik muvaffaqiyatli yakunlanganidan qat'i nazar, umumiy ma'lumotlar bilan ish oxirida chaqirilishi kerak (oxir-oqibat, istisno bo'lishi mumkin). Bu yerda “unutish”ning oqibatlari jiddiyroq. Agar CloseHandle deb nomlanmagan bo'lsa, faqat resurslarni oqizadi (bu ham yomon!), U holda chiqarilmagan mutex boshqa ish zarrachalarining umumiy resurs bilan ishlamay qolgan tarmoq tugaguniga qadar ishlashiga to'sqinlik qiladi, bu esa dasturning normal ishlashiga imkon bermaydi. Bunga yo'l qo'ymaslik uchun aqlli destruktorga ega maxsus o'qitilgan sinf bizga yana yordam beradi.

Sinxronizatsiya ob'ektlarini ko'rib chiqishni tugatib, Win32 API-da bo'lmagan ob'ektni eslatib o'tmoqchiman. Ko'pgina hamkasblarim nima uchun Win32-da ixtisoslashtirilgan "bir yozadi, ko'p o'qiydi" ob'ekti yo'qligi qiziqtiradi. "Kengaytirilgan mutex" turi, bu bir vaqtning o'zida faqat bitta ip yozish uchun umumiy ma'lumotlarga kirishiga ishonch hosil qiladi va bir nechta iplar bir vaqtning o'zida o'qiy oladi. Shunga o'xshash ob'ektni UNIX "ah" da topish mumkin. Ba'zi kutubxonalar, masalan, Borlanddan, uni standart sinxronizatsiya ob'ektlari asosida taqlid qilishni taklif qiladi. Biroq, bunday emulyatsiyalarning haqiqiy foydasi juda shubhali. Bunday ob'ektni faqat quyidagi manzilda samarali amalga oshirish mumkin. operatsion tizim yadrosining darajasi.Lekin Windows yadrosida bunday ob'ekt mavjud emas.

Nima uchun Windows NT yadrosini ishlab chiquvchilar bunga e'tibor bermadilar? Nima uchun biz UNIX dan yomonroqmiz? Menimcha, javob shundaki, Windows uchun bunday ob'ektga hali haqiqiy ehtiyoj yo'q. Oddiy bir protsessorli mashinada, iplar hali ham jismonan bir vaqtning o'zida ishlay olmaydi, u amalda mutexga teng bo'ladi. Ko'p protsessorli mashinada u o'quvchi iplarining parallel ishlashiga ruxsat berish orqali foyda keltirishi mumkin. Shu bilan birga, bu daromad o'qish iplarining "to'qnashuvi" ehtimoli yuqori bo'lgandagina sezilarli bo'ladi. Shubhasiz, masalan, 1024 protsessorli mashinada bunday yadro ob'ekti hayotiy ahamiyatga ega bo'ladi. Shunga o'xshash mashinalar mavjud, ammo ular maxsus operatsion tizimlar bilan ishlaydigan maxsus tizimlardir. Ko'pincha bunday operatsion tizimlar UNIX asosida quriladi, ehtimol u erdan "bir yozadi, ko'p o'qiydi" kabi ob'ekt ushbu tizimning ko'proq ishlatiladigan versiyalariga kiradi. Ammo biz x86 mashinalarida, qoida tariqasida, faqat bitta va faqat ikkita protsessor o'rnatiladi. Va faqat Intel Xeon kabi protsessorlarning eng ilg'or modellari 4 yoki undan ko'p protsessor konfiguratsiyasini qo'llab-quvvatlaydi, ammo bunday tizimlar hali ham ekzotik bo'lib qolmoqda. Ammo bunday "ilg'or" tizimda ham "ilg'or mutex" faqat juda aniq vaziyatlarda sezilarli samaradorlikni berishi mumkin.

Shunday qilib, "ilg'or" mutexni amalga oshirish shunchaki muammoga arzimaydi. "Past protsessorli" mashinada u hatto standart mutex bilan solishtirganda ob'ekt mantig'ining murakkabligi tufayli unchalik samarali bo'lmasligi mumkin. E'tibor bering, bunday ob'ektni amalga oshirish birinchi qarashda ko'rinadigan darajada oddiy emas. Muvaffaqiyatsiz amalga oshirilganda, agar o'qish iplari juda ko'p bo'lsa, yozish ipi shunchaki ma'lumotlarga "o'tmaydi". Shu sabablarga ko'ra, men sizga bunday ob'ektni taqlid qilishni tavsiya etmayman. Haqiqiy mashinalardagi haqiqiy ilovalarda oddiy mutex yoki tanqidiy bo'lim (bu maqolaning keyingi qismida muhokama qilinadi) umumiy ma'lumotlarga kirishni sinxronlashtirish vazifasini mukammal darajada bajaradi. Garchi, menimcha, Windows OS ning rivojlanishi bilan "bir yozadi, ko'p o'qiydi" yadro ob'ekti ertami-kechmi paydo bo'ladi.

Eslatma. Haqiqatan ham, Windows NT da "bir yozadi - ko'p o'qiydi" ob'ekti hali ham mavjud. Men ushbu maqolani yozganimda bu haqda bilmasdim. Ushbu ob'ekt "yadro resurslari" deb ataladi va foydalanuvchi rejimi dasturlari uchun mavjud emas, ehtimol shuning uchun u yaxshi ma'lum emas. Bu boradagi o'xshashliklarni DDKda topish mumkin. Menga buni ko'rsatgani uchun Konstantin Manuringa rahmat.

O'lik qulf

Endi WaitForMultipleObjects funksiyasiga, aniqrog‘i uning uchinchi parametri bWaitAllga qaytaylik. Men bir vaqtning o'zida bir nechta ob'ektlarni kutish qobiliyati nima uchun juda muhimligini aytib berishga va'da berdim.

Nima uchun bir nechta ob'ektlardan birini kutish uchun funktsiya kerakligi tushunarli. Maxsus funktsiya bo'lmasa, buni amalga oshirish mumkin, faqat bo'sh tsikldagi ob'ektlarning holatini ketma-ket tekshirishdan tashqari, bu, albatta, qabul qilinishi mumkin emas. Ammo bir vaqtning o'zida bir nechta ob'ektlar signal holatiga o'tish vaqtini kutishga imkon beradigan maxsus funktsiyaga ehtiyoj unchalik aniq emas. Haqiqatan ham, quyidagi odatiy vaziyatni tasavvur qiling: ma'lum bir lahzada bizning tarmoq bir vaqtning o'zida ikkita umumiy ma'lumotlar to'plamiga kirishga muhtoj, ularning har biri o'z mutexlari uchun javob beradi, keling, ularni A va B deb ataymiz. avval mutex A chiqarilguncha kuting, uni qo'lga oling, so'ngra mutex B chiqarilishini kuting... WaitForSingleObject ga bir nechta qo'ng'iroqlar bilan ham qila olamiz shekilli. Haqiqatan ham, bu ishlaydi, lekin faqat boshqa barcha iplar bir xil tartibda mutekslarni o'zlashtirsagina: avval A, keyin B. Agar ma'lum bir ip teskarisini qilishga harakat qilsa nima bo'ladi: avval B, keyin A ga ega bo'ladimi? Ertami-kechmi, bir ip mutex A ni, ikkinchisi B ni tutib olganida, birinchisi B chiqishini kutayotganda, ikkinchisi A. Buni hech qachon kutmasliklari va dastur osib qo'yilishi aniq.

Bunday nosozlik juda keng tarqalgan xatodir. Sinxronizatsiya bilan bog'liq barcha xatolar singari, u faqat vaqti-vaqti bilan paydo bo'ladi va dasturchi uchun juda ko'p nervlarni buzishi mumkin. Shu bilan birga, bir nechta sinxronizatsiya ob'ektlarini o'z ichiga olgan deyarli har qanday sxema to'xtab qolishi mumkin. Shuning uchun bunday sxemani loyihalash bosqichida ushbu muammoga alohida e'tibor berilishi kerak.

Berilgan oddiy misolda blokirovkadan qochish juda oson. Barcha iplarning ma'lum bir tartibda mutekslarni olishini talab qilish kerak: birinchi navbatda A, keyin B. Biroq, bir-biriga turli yo'llar bilan bog'liq bo'lgan ko'plab ob'ektlar mavjud bo'lgan murakkab dasturda bunga erishish odatda unchalik oson emas. Qulfda ikkita emas, balki ko'plab ob'ektlar va iplar ishtirok etishi mumkin. Shuning uchun, eng ko'p ishonchli yo'l Tarmoq bir vaqtning o'zida bir nechta sinxronizatsiya ob'ektlariga muhtoj bo'lgan vaziyatda blokirovkaga yo'l qo'ymaslik uchun ularning barchasini bWaitAll=TRUE parametri bilan WaitForMultipleObjects funksiyasiga bitta qo'ng'iroq bilan olish kerak. To'g'risini aytsam, bu holda, biz faqat blokirovkalar muammosini operatsion tizim yadrosiga o'tkazamiz, lekin asosiysi, bu endi bizning tashvishimiz bo'lmaydi. Biroq, ko'p ob'ektlarga ega bo'lgan murakkab dasturda, ularning qaysi biri ma'lum bir operatsiyani bajarishi kerakligini darhol aytish har doim ham imkoni bo'lmasa, barcha WaitFor qo'ng'iroqlarini bir joyga to'plash va ularni birlashtirish oson emas.

Shunday qilib, o'likdan qochishning ikki yo'li mavjud. Sinxronizatsiya ob'ektlari har doim bir xil tartibda iplar tomonidan yozib olinishini yoki ular WaitForMultipleObjects ga bitta qo'ng'iroq orqali olinishini ta'minlashingiz kerak. Oxirgi usul oddiyroq va afzalroqdir. Biroq, amalda ikkala talabning bajarilishi bilan doimo qiyinchiliklar yuzaga keladi, bu ikkala yondashuvni birlashtirish kerak. Murakkab vaqt sxemalarini loyihalash ko'pincha juda ahamiyatsiz vazifadir.

Sinxronizatsiya misoli

Ko'pgina tipik vaziyatlarda, masalan, yuqorida aytib o'tganimdek, sinxronizatsiyani tashkil qilish qiyin emas, hodisa yoki mutex etarli. Ammo vaqti-vaqti bilan muammoning echimi unchalik aniq bo'lmagan murakkab holatlar mavjud. Buni o‘z amaliyotimdan aniq bir misol bilan ko‘rsatmoqchiman. Ko'rib turganingizdek, yechim hayratlanarli darajada sodda bo'lib chiqdi, lekin men uni topishdan oldin bir nechta muvaffaqiyatsiz variantlarni sinab ko'rishim kerak edi.

Shunday qilib, vazifa. Deyarli barcha zamonaviy yuklab olish menejerlari yoki oddiygina "silkituvchi stullar" fonda ishlaydigan "silkituvchi stul" foydalanuvchining Internetda kezishiga katta xalaqit bermasligi uchun trafikni cheklash imkoniyatiga ega. Men shunga o'xshash dasturni ishlab chiqayotgan edim va menga aynan shunday "xususiyatni" amalga oshirish vazifasi berildi. Mening tebranadigan stul klassik ko'p qirrali sxema bo'yicha ishladi, bu holda har bir vazifa, bu holda, ma'lum bir faylni yuklab olish, alohida ip tomonidan bajariladi. Trafik chegarasi barcha oqimlar uchun jamlangan bo'lishi kerak edi. Ya'ni, ma'lum vaqt oralig'ida barcha oqimlar o'z rozetkalaridan ma'lum miqdordagi baytdan ko'p bo'lmasligini ta'minlash kerak edi. Ushbu chegarani oqimlar o'rtasida teng taqsimlash, shubhasiz, samarasiz bo'ladi, chunki fayllarni yuklab olish juda notekis bo'lishi mumkin, biri tez, ikkinchisi sekin yuklab olinadi. Shuning uchun bizga barcha mavzular uchun umumiy hisoblagich kerak, qancha bayt o'qilgan va yana qancha o'qilishi mumkin. Bu erda sinxronizatsiya yordam beradi. Vazifaning qo'shimcha murakkabligi har qanday vaqtda ishchi iplarning istalgan vaqtda to'xtatilishi mumkinligi talabi bilan berildi.

Keling, muammoni batafsilroq shakllantiraylik. Sinxronizatsiya tizimini maxsus sinfga qo'shishga qaror qildim. Mana uning interfeysi:

sinf CQuota {

ommaviy: // usullari

bekor o'rnatish ( imzosiz int _nKvota );

imzosiz int So'rov ( imzosiz int _nBytesToRead , HANDLE_hStopEvent );

bekor Chiqarish ( imzosiz int _nBytesRevert , HANDLE_hStopEvent );

Vaqti-vaqti bilan, sekundiga bir marta ayting, boshqaruv chizig'i yuklab olish kvotasi o'rnatgan holda Set usulini chaqiradi. Ishchi ip tarmoqdan olingan ma'lumotlarni o'qishdan oldin, u joriy kvotaning nolga teng emasligini tekshiradigan Request usulini chaqiradi va agar shunday bo'lsa, joriy kvotadan kamroq o'qilishi mumkin bo'lgan baytlar sonini qaytaradi. Kvota mos ravishda bu raqamga kamayadi. Agar so'rov chaqirilganda kvota nolga teng bo'lsa, qo'ng'iroq qiluvchi ip kvota mavjud bo'lguncha kutishi kerak. Ba'zan shunday bo'ladiki, aslida so'ralgandan kamroq bayt olinadi, bu holda ip Release usuli bilan unga ajratilgan kvotaning bir qismini qaytaradi. Va aytganimdek, foydalanuvchi istalgan vaqtda yuklab olishni to'xtatish buyrug'ini berishi mumkin. Bunday holda, kvotaning mavjudligidan qat'i nazar, kutish to'xtatilishi kerak. Buning uchun maxsus hodisa ishlatiladi: _hStopEvent. Vazifalarni mustaqil ravishda boshlash va to'xtatish mumkin bo'lganligi sababli, har bir ishchi ipning o'z to'xtash hodisasi mavjud. Uning dastagi Request va Release usullariga o'tkaziladi.

Muvaffaqiyatsiz variantlardan birida men CQuota sinfiga kirishni sinxronlashtiradigan mutex va kvotaning mavjudligini ko'rsatadigan hodisaning kombinatsiyasidan foydalanishga harakat qildim. Biroq, to'xtash hodisasi ushbu sxemaga mos kelmaydi. Agar ip kvotani olishni istasa, uning kutish holati murakkab mantiqiy ifoda bilan boshqarilishi kerak: ((mutex VA kvota hodisasi) OR stop hodisasi). Ammo WaitForMultipleObjects bunga ruxsat bermaydi, siz bir nechta yadro obyektlarini AND yoki OR operatsiyasi bilan birlashtira olasiz, lekin aralashtirilmaydi. WaitForMultipleObjects-ga ikkita ketma-ket qo'ng'iroqlar bilan kutish vaqtini ajratishga urinish muqarrar ravishda boshi berk ko'chaga olib keladi. Umuman olganda, bu yo'l boshi berk ko'chaga aylandi.

Men endi tumanga yo'l qo'ymayman va sizga yechimni aytaman. Aytganimdek, mutex avtomatik tiklash hodisasiga juda o'xshaydi. Va bu erda bizda kamdan-kam holatlar mavjud bo'lib, uni ishlatish qulayroq bo'ladi, lekin bir emas, balki ikkitasi:

sinf CQuota {

shaxsiy: // ma'lumotlar

imzosiz int m_nQuota ;

CEvent m_eventHasQuota ;

CEvent m_eventNoQuota ;

Bir vaqtning o'zida ushbu hodisalardan faqat bittasini o'rnatish mumkin. Kvotani boshqaradigan har qanday mavzu, agar qolgan kvota nolga teng bo'lmasa, birinchi hodisani, agar kvota tugagan bo'lsa, ikkinchisini o'rnatishi kerak. Kvota olishni istagan mavzu birinchi voqeani kutishi kerak. Kvotani oshiruvchi ip faqat ushbu hodisalardan birini kutishi kerak, chunki agar ularning ikkalasi ham asl holatini tiklash holatida bo'lsa, demak, boshqa ish zarrachasi hozirda kvota bilan ishlamoqda. Shunday qilib, ikkita hodisa bir vaqtning o'zida ikkita funktsiyani bajaradi: ma'lumotlarga kirishni sinxronlashtirish va kutish. Nihoyat, ip ikkita hodisadan birini kutayotganligi sababli, to'xtash uchun signal beruvchi hodisa osongina kiritiladi.

So'rov usulini amalga oshirishga misol keltiraman. Qolganlari xuddi shunday tarzda amalga oshiriladi. Haqiqiy loyihada ishlatiladigan kodni biroz soddalashtirdim:

imzosiz int CQuota :: So'rov ( imzosiz int _nSo'rov , HANDLE_hStopEvent )

agar(! _nSo'rov ) qaytish 0 ;

imzosiz int nTa'minlash = 0 ;

HANDLE HIVOQELAR [ 2 ];

voqealar [ 0 ] = _hStopEvent ; // Stop hodisasi ustunlikka ega. Biz buni birinchi o'ringa qo'yamiz.

voqealar [ 1 ] = m_eventHasQuota ;

int iWaitResult = :: WaitForMultipleObjects ( 2 , voqealar , FALSE , CHEKSIZ );

almashtirish( iWaitResult ) {

hol WAIT_FAILED :

// XATO

yangisini tashlang CWin32 istisnosi ;

hol WAIT_OBJECT_0 :

// Hodisani to'xtatish. Men buni odatiy istisno bilan hal qildim, lekin uni boshqa yo'l bilan amalga oshirishimga hech narsa to'sqinlik qilmaydi.

yangisini tashlang CStopException ;

hol WAIT_OBJECT_0 + 1 :

// Tadbir "kvota mavjud"

ASSERT ( m_nQuota ); // Agar signal ushbu hodisa tomonidan berilgan bo'lsa-yu, lekin aslida kvota yo'q bo'lsa, unda biz biror joyda xatoga yo'l qo'ydik. Xatoni qidirish kerak!

agar( _nSo'rov >= m_nQuota ) {

nTa'minlash = m_nQuota ;

m_nQuota = 0 ;

m_eventNoQuota . o'rnatish ();

boshqa {

nTa'minlash = _nSo'rov ;

m_nQuota -= _nSo'rov ;

m_eventHasQuota . o'rnatish ();

tanaffus;

qaytish nTa'minlash ;

Kichik eslatma. Ushbu loyihada MFC kutubxonasidan foydalanilmagan, lekin siz taxmin qilganingizdek, men o'zimning CEvent sinfimni yaratdim, bu MFC "schnoy" ga o'xshash "voqea" yadrosi ob'ekti atrofida o'ralgan. Aytganimdek, bunday oddiy o'rash sinflari ish oxirida bo'shatish uchun eslab qolishi kerak bo'lgan ba'zi resurs (bu holda, yadro ob'ekti) mavjud bo'lganda juda foydali bo'ladi. Qolganlarida SetEvent(m_hEvent) yoki m_event.Set( ni yozishingiz muhim emas. ).

Umid qilamanki, bu misol, agar siz ahamiyatsiz bo'lmagan vaziyatga duch kelsangiz, o'zingizning vaqt sxemangizni yaratishga yordam beradi. Asosiysi, sxemangizni iloji boricha diqqat bilan tahlil qilish. To'g'ri ishlamaydigan vaziyat bo'lishi mumkinmi, xususan, blokirovka bo'lishi mumkinmi? Nosozliklarni tuzatuvchida bunday xatolarni ushlash odatda umidsiz biznesdir, bu erda faqat batafsil tahlil yordam beradi.

Shunday qilib, biz ko'rib chiqdik muhim vosita ip sinxronizatsiyasi: yadro sinxronlash ob'ektlari. Bu kuchli va ko'p qirrali vositadir. Uning yordamida siz hatto juda murakkab sinxronizatsiya sxemalarini ham qurishingiz mumkin. Yaxshiyamki, bunday ahamiyatsiz holatlar kamdan-kam uchraydi. Bundan tashqari, ko'p qirralilik har doim ishlash narxiga bog'liq. Shuning uchun, ko'p hollarda, muhim bo'limlar va atom operatsiyalari kabi Windows-da mavjud bo'lgan boshqa ip sinxronlash xususiyatlaridan foydalanishga arziydi. Ular juda universal emas, lekin ular oddiy va samarali. Ular haqida keyingi qismda gaplashamiz.

Jarayon xotiraga yuklangan dasturning namunasidir. Ushbu misol bajarilishi kerak bo'lgan ko'rsatmalar ketma-ketligi bo'lgan iplarni yaratishi mumkin. Ishlayotgan jarayonlar emas, balki iplar ekanligini tushunish muhimdir.

Bundan tashqari, har qanday jarayonda kamida bitta ip mavjud. Bu ip ilovaning asosiy (asosiy) ipi deb ataladi.

Ularning bajarilishi uchun jismoniy protsessorlarga qaraganda deyarli har doim ko'proq iplar mavjud bo'lganligi sababli, iplar aslida bir vaqtning o'zida emas, balki o'z navbatida bajariladi (protsessor vaqtini taqsimlash iplar o'rtasida aniq sodir bo'ladi). Ammo ular o'rtasida almashinish shunchalik tez-tez sodir bo'ladiki, ular parallel ravishda ishlayotganga o'xshaydi.

Vaziyatga qarab, iplar uchta holatda bo'lishi mumkin. Birinchidan, protsessor vaqti berilganda, ip ishlashi mumkin, ya'ni. u faol bo'lishi mumkin. Ikkinchidan, u faol bo'lmasligi va protsessor ajratilishini kutishi mumkin, ya'ni. tayyor holatda bo'lish. Uchinchisi ham bor, juda ham muhim shart- blokirovka holati. Mavzu bloklanganda, unga umuman vaqt ajratilmaydi. Odatda, biron bir voqea kutilayotganda qulf qo'yiladi. Ushbu hodisa sodir bo'lganda, ip avtomatik ravishda bloklangan holatdan tayyor holatga o'tadi. Misol uchun, agar bitta ip hisob-kitoblarni bajarayotgan bo'lsa, boshqasi natijalar diskda saqlanishini kutishi kerak. Ikkinchisi "while(!isCalcFinished) continue;" kabi sikldan foydalanishi mumkin, biroq amalda shuni ko'rish mumkinki, bu sikl ishlayotgan vaqtda protsessor 100% band bo'ladi (bu faol kutish deb ataladi). Qulflash mexanizmi bebaho yordam ko'rsatadigan imkon qadar bunday pastadirlardan qochish kerak. Birinchi ip o'qish tugaganligini bildiruvchi hodisani o'rnatmaguncha ikkinchi ip o'zini bloklashi mumkin.

Windows operatsion tizimida mavzularni sinxronlashtirish

Windows preemptive multitaskingni amalga oshiradi, ya'ni istalgan vaqtda tizim bir ipning bajarilishini to'xtatishi va boshqaruvni boshqasiga o'tkazishi mumkin. Ilgari, Windows 3.1 da kooperativ multitasking deb ataladigan tashkil etish usuli qo'llanilgan: tizim ipning o'zi boshqaruvni unga o'tkazguncha kutib turdi va shuning uchun bitta dastur qotib qolsa, kompyuterni qayta ishga tushirish kerak edi.

Xuddi shu jarayonga tegishli bo'lgan barcha oqimlar RAM manzil maydoni yoki ochiq fayllar kabi ba'zi umumiy resurslarni baham ko'radi. Bu resurslar butun jarayonga, demak, uning har bir qismiga tegishli. Shuning uchun, har bir mavzu ushbu resurslar bilan hech qanday cheklovlarsiz ishlashi mumkin. Lekin... Agar bitta ip hali biron bir umumiy resurs bilan ishlashni tugatmagan bo'lsa va tizim xuddi shu resursdan foydalangan holda boshqa ipga o'tgan bo'lsa, u holda bu iplar ishining natijasi mo'ljallanganidan keskin farq qilishi mumkin. Bunday qarama-qarshiliklar turli jarayonlarga tegishli iplar o'rtasida ham paydo bo'lishi mumkin. Ikki yoki undan ortiq mavzular umumiy manbadan foydalanganda, bu muammo yuzaga keladi.

Misol. Sinxronlashdan tashqari mavzular: Agar siz displey oqimini vaqtincha to'xtatib qo'ysangiz (pauza), fon massivini to'ldirish ipi ishlashda davom etadi.

#o'z ichiga oladi #o'z ichiga oladi int a; HANDLE hThr; imzosiz uzun uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( for (i=0; i)<5; i++) a[i] = num; num++; } } int main(void) { hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) printf("%d %d %d %d %d\n", a, a, a, a, a); return 0; }

Shuning uchun ish zarrachalariga o'z ishlarini umumiy resurslar bilan muvofiqlashtirishga imkon beradigan mexanizm kerak. Bu mexanizm ipni sinxronlash mexanizmi deb ataladi.

Bu mexanizm dasturiy ta'minot yordamida yaratiladigan va boshqariladigan, tizimdagi barcha oqimlar uchun umumiy bo'lgan (ba'zilari bir xil jarayonga tegishli bo'lgan iplar tomonidan taqsimlanadi) va resurslarga kirishni muvofiqlashtirish uchun foydalaniladigan operatsion tizim ob'ektlari to'plamidir. Resurslar ikki yoki undan ortiq oqimlar tomonidan almashinadigan har qanday narsa bo'lishi mumkin - diskdagi fayl, port, ma'lumotlar bazasiga kirish, GDI ob'ekti va hatto global dastur o'zgaruvchisi (bir xil jarayonga tegishli iplardan kirish mumkin).

Bir nechta sinxronizatsiya ob'ektlari mavjud bo'lib, ulardan eng muhimlari mutex, kritik bo'lim, hodisa va semafordir. Ushbu ob'ektlarning har biri o'zining sinxronlash usulini amalga oshiradi. Shuningdek, jarayonlar va oqimlarning o'zlari sinxronizatsiya ob'ektlari sifatida ishlatilishi mumkin (bir ip boshqa ip yoki jarayonning tugashini kutayotganda); shuningdek, fayllar, aloqa qurilmalari, konsol kiritish va o'zgartirish bildirishnomalari.

Har qanday sinxronizatsiya ob'ekti signalli deb ataladigan holatda bo'lishi mumkin. Har bir turdagi ob'ekt uchun bu holat har xil ma'noga ega. Mavzular ob'ektning joriy holatini tekshirishi va/yoki bu holatning o'zgarishini kutishi va shu bilan ularning harakatlarini muvofiqlashtirishi mumkin. Bu ish zarrachalari sinxronizatsiya ob'ektlari bilan ishlaganda (ularni yaratadi, holatini o'zgartiradi), tizim ushbu amalni tugatmaguncha uning bajarilishini to'xtatmasligini ta'minlaydi. Shunday qilib, sinxronizatsiya ob'ektlarida barcha yakuniy operatsiyalar atomik (bo'linmas.

Sinxronizatsiya ob'ektlari bilan ishlash

U yoki bu sinxronizatsiya obyektini yaratish uchun Create... tipidagi maxsus WinAPI funksiyasi chaqiriladi (masalan, CreateMutex). Ushbu qo'ng'iroq berilgan jarayonga tegishli barcha oqimlar tomonidan ishlatilishi mumkin bo'lgan ob'ekt dastagini (HANDLE) qaytaradi. Sinxronizatsiya ob'ektiga boshqa jarayondan kirish mumkin, ya'ni ob'ektning dastagini meros qilib olish yoki yaxshisi, ob'ektning Open... funksiyasini chaqirish orqali. Ushbu qo'ng'iroqdan so'ng, jarayon keyinchalik ob'ekt bilan ishlash uchun ishlatilishi mumkin bo'lgan tutqichni oladi. Ob'ekt, agar u bitta jarayonda foydalanish uchun mo'ljallanmagan bo'lsa, unga nom berilishi kerak. Barcha ob'ektlarning nomlari har xil bo'lishi kerak (ular har xil turdagi bo'lsa ham). Siz, masalan, bir xil nomdagi hodisa va semafor yarata olmaysiz.

Ob'ektning mavjud deskriptori orqali siz uning joriy holatini aniqlashingiz mumkin. Bu deb atalmish yordamida amalga oshiriladi. kutilayotgan funktsiyalar. Eng ko'p ishlatiladigan funksiya WaitForSingleObject. Ushbu funktsiya ikkita parametrni oladi, birinchisi - ob'ekt dastagi, ikkinchisi - milodiy vaqt oralig'i. Agar ob'ekt signal holatida bo'lsa, funktsiya WAIT_OBJECT_0, agar kutish muddati tugagan bo'lsa, WAIT_TIMEOUT va agar mutex egalik oqimi tugatilishidan oldin bo'shatilmagan bo'lsa, WAIT_ABANDONED qaytaradi. Vaqt tugashi nolga teng bo'lsa, funktsiya darhol qaytadi, aks holda u belgilangan vaqtni kutadi. Agar ob'ektning holati bu vaqt tugashidan oldin signal berilsa, funktsiya WAIT_OBJECT_0 qiymatini qaytaradi, aks holda funksiya WAIT_TIMEOUTni qaytaradi. Agar vaqt sifatida INFINITE ramziy doimiysi ko'rsatilgan bo'lsa, u holda ob'ekt holati signalga aylanguncha funktsiya cheksiz kutadi.

Kutish funktsiyasiga qo'ng'iroq joriy ipni bloklashi juda muhim, ya'ni. ip bo'sh turganda, unga protsessor vaqti ajratilmaydi.

Muhim bo'limlar

Obyekt-tanqidiy bo'lim dasturchiga kodning umumiy resursga kirishi mumkin bo'lgan qismni ajratishga yordam beradi va resursdan bir vaqtda foydalanishni oldini oladi. Resursdan foydalanishdan oldin ip muhim bo'limga kiradi (EnterCriticalSection funktsiyasini chaqiradi). Agar bir xil kritik bo'limga boshqa har qanday mavzu kirishga harakat qilsa, uning bajarilishi LeaveCriticalSection qo'ng'irog'i bilan birinchi ip qismdan chiqmaguncha to'xtatiladi. Faqat bitta jarayonda iplar uchun ishlatiladi. Kritik bo'limga kirish tartibi aniqlanmagan.

TryEnterCriticalSection funksiyasi ham mavjud bo'lib, u hozirda muhim bo'lim bandligini tekshiradi. Uning yordami bilan resursga kirishni kutish jarayonida ipni bloklab bo'lmaydi, lekin ba'zi foydali amallarni bajaring.

Misol. Muhim bo'limlar yordamida iplarni sinxronlashtirish.

#o'z ichiga oladi #o'z ichiga oladi CRITICAL_SECTION cs; int a; HANDLE hThr; imzosiz uzun uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( EnterCriticalSection(&cs); for (i=0; i)<5; i++) a[i] = num; num++; LeaveCriticalSection(&cs); } } int main(void) { InitializeCriticalSection(&cs); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { EnterCriticalSection(&cs); printf("%d %d %d %d %d\n", a, a, a, a, a); LeaveCriticalSection(&cs); } return 0; }

O'zaro istisno

O'zaro istisno ob'ektlari (mutexlar, mutex - MUTual Exclusion'dan) umumiy manbaga kirishni o'zaro istisno qilishni muvofiqlashtirish imkonini beradi. Ob'ektning signalli holati (ya'ni, "to'siq" holati) ob'ekt hech qanday ipga tegishli bo'lmagan va uni "tutib olish" mumkin bo'lgan vaqt nuqtasiga to'g'ri keladi. Aksincha, "qayta o'rnatish" (signal berilmagan) holati ba'zi bir ip allaqachon ushbu ob'ektga egalik qilgan paytga to'g'ri keladi. Ob'ektga kirish huquqi ob'ektga ega bo'lgan ip uni ozod qilganda beriladi.

Ikki (yoki undan ko'p) iplar CreateMutex funksiyasini chaqirish orqali bir xil nomli mutex yaratishi mumkin. Birinchi ip aslida mutexni yaratadi va keyingi iplar allaqachon mavjud ob'ektga ishlov beradi. Bu dasturchini mutexni aslida kim yaratayotgani haqida tashvishlanishdan ozod qilib, bir nechta iplarga bir xil mutexga ishlov berish imkonini beradi. Agar ushbu yondashuv qo'llanilsa, bInitialOwner bayrog'ini FALSE ga o'rnatish tavsiya etiladi, aks holda mutexning haqiqiy yaratuvchisini aniqlashda biroz qiyinchiliklar paydo bo'ladi.

Bir nechta iplar bir xil mutex uchun tutqichga ega bo'lishi mumkin, bu jarayonlar o'rtasida aloqa o'rnatishga imkon beradi. Ushbu yondashuv uchun quyidagi mexanizmlardan foydalanishingiz mumkin:

  • CreateProcess funksiyasi yordamida yaratilgan bola jarayoni, agar lpMutexAttributes parametri CreateMutex funksiyasi tomonidan mutex yaratilganda ko'rsatilgan bo'lsa, mutex tutqichini meros qilib olishi mumkin.
  • DuplicateHandle funksiyasidan foydalanib, ip mavjud mutexning dublikatini olishi mumkin.
  • OpenMutex yoki CreateMutex funksiyalarini chaqirishda ip mavjud mutex nomini belgilashi mumkin.

Joriy oqimga tegishli mutexni e'lon qilish uchun kutilayotgan funksiyalardan birini chaqirish kerak. Ob'ektga ega bo'lgan ip uni xohlagancha qayta-qayta "qo'lga olishi" mumkin (bu o'z-o'zini blokirovka qilishga olib kelmaydi), lekin ReleaseMutex funksiyasidan foydalangan holda uni ko'p marta qo'yib yuborishi kerak bo'ladi.

Bitta jarayonning iplarini sinxronlashtirish uchun muhim bo'limlardan foydalanish samaraliroq bo'ladi.

Misol. Mutekslar yordamida iplarni sinxronlashtirish.

#o'z ichiga oladi #o'z ichiga oladi HANDLE hMutex; int a; HANDLE hThr; imzosiz uzun uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( WaitForSingleObject(hMutex, INFINITE); for (i=0; i)<5; i++) a[i] = num; num++; ReleaseMutex(hMutex); } } int main(void) { hMutex=CreateMutex(NULL, FALSE, NULL); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { WaitForSingleObject(hMutex, INFINITE); printf("%d %d %d %d %d\n", a, a, a, a, a); ReleaseMutex(hMutex); } return 0; }

Ishlanmalar

Voqea ob'ektlari kutilayotgan mavzularni voqea sodir bo'lganligi haqida xabardor qilish uchun ishlatiladi. Ikki turdagi hodisalar mavjud - qo'lda va avtomatik qayta o'rnatish bilan. Qo'lda tiklash ResetEvent funktsiyasi tomonidan amalga oshiriladi. Qo'lda tiklash hodisalari bir vaqtning o'zida bir nechta mavzularni xabardor qilish uchun ishlatiladi. Avtomatik qayta tiklash hodisasidan foydalanilganda, faqat bitta kutayotgan ip bildirishnoma oladi va uning bajarilishini davom ettiradi, qolganlari esa kutadi.

CreateEvent funktsiyasi hodisa ob'ektini yaratadi, SetEvent - hodisani signal holatiga o'rnatadi, ResetEvent - hodisani qayta tiklaydi. PulseEvent funktsiyasi hodisani o'rnatadi va ushbu hodisani kutayotgan mavzularni davom ettirgandan so'ng (barchasi qo'lda qayta o'rnatish bilan va faqat bitta avtomatik ravishda) uni qayta tiklaydi. Agar kutayotgan mavzular bo'lmasa, PulseEvent shunchaki hodisani qayta tiklaydi.

Misol. Voqealar yordamida iplarni sinxronlashtirish.

#o'z ichiga oladi #o'z ichiga oladi HEVENT1, HEvent2; int a; HANDLE hThr; imzosiz uzun uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( WaitForSingleObject(hEvent2, INFINITE); uchun (i=0; i)<5; i++) a[i] = num; num++; SetEvent(hEvent1); } } int main(void) { hEvent1=CreateEvent(NULL, FALSE, TRUE, NULL); hEvent2=CreateEvent(NULL, FALSE, FALSE, NULL); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { WaitForSingleObject(hEvent1, INFINITE); printf("%d %d %d %d %d\n", a, a, a, a, a); SetEvent(hEvent2); } return 0; }

semaforlar

Semafor ob'ekti aslida hisoblagichga ega mutex ob'ektidir. Ushbu ob'ekt o'zini ma'lum miqdordagi iplar tomonidan "qo'lga olish" imkonini beradi. Shundan so'ng, semaforning ilgari "qo'lga olingan" iplaridan biri uni chiqarmaguncha, "qo'lga olish" imkonsiz bo'ladi. Semaforlar bir vaqtning o'zida resursga kirishi mumkin bo'lgan mavzular sonini cheklash uchun ishlatiladi. Initsializatsiya paytida maksimal iplar soni ob'ektga o'tkaziladi, har bir "qo'lga olish" dan keyin semafor hisoblagichi kamayadi. Signal holati noldan katta hisoblagich qiymatiga mos keladi. Hisoblagich nolga teng bo'lsa, sema o'rnatilmagan (qayta o'rnatilgan) hisoblanadi.

CreateSemaphore funksiyasi maksimal mumkin bo'lgan boshlang'ich qiymatini ko'rsatuvchi semafor ob'ektini yaratadi, OpenSemaphore - mavjud semaforga tutqichni qaytaradi, semafor kutish funktsiyalari yordamida olinadi, semafor qiymati esa bittaga kamayadi, ReleaseSemaphore - semaforni chiqaradi. parametr raqamida ko'rsatilgan qiymat bo'yicha sema qiymatining oshishi.

Misol. Semaforlar yordamida iplarni sinxronlashtirish.

#o'z ichiga oladi #o'z ichiga oladi HANDLE hSem; int a; HANDLE hThr; imzosiz uzun uThrID; void Thread(void* pParams) ( int i, num = 0; while (1) ( WaitForSingleObject(hSem, INFINITE); (i=0; i) uchun<5; i++) a[i] = num; num++; ReleaseSemaphore(hSem, 1, NULL); } } int main(void) { hSem=CreateSemaphore(NULL, 1, 1, "MySemaphore1"); hThr=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Thread,NULL,0,&uThrID); while(1) { WaitForSingleObject(hSem, INFINITE); printf("%d %d %d %d %d\n", a, a, a, a, a); ReleaseSemaphore(hSem, 1, NULL); } return 0; }

O'zgaruvchilarga himoyalangan kirish

Sinxronizatsiya haqida qayg'urmasdan, barcha mavzulardagi global o'zgaruvchilar bilan ishlashga imkon beruvchi bir qator funktsiyalar mavjud, chunki. bu funktsiyalarning o'zi g'amxo'rlik qiladi - ularning bajarilishi atomikdir. Bular InterlockedIncrement, InterlockedDecrement, InterlockedExchange, InterlockedExchangeAdd va InterlockedCompareExchange funksiyalaridir. Masalan, InterlockedIncrement funktsiyasi 32-bitli o'zgaruvchining qiymatini atomik ravishda bittaga oshiradi, bu turli hisoblagichlar uchun foydalidir.

Barcha WIN32 API funktsiyalarining maqsadi, ishlatilishi va sintaksisi haqida to'liq ma'lumot olish uchun siz Borland Delphi yoki CBuilder dasturlash muhitlarining bir qismi bo'lgan MS SDK yordam tizimidan, shuningdek, dasturning bir qismi sifatida taqdim etilgan MSDN dan foydalanishingiz kerak. Visual C dasturlash tizimi.


Bir nechta iplar yoki jarayonlardan foydalanadigan dasturlar uchun ularning barchasi kerakli ketma-ketlikda o'zlariga yuklangan funktsiyalarni bajarishlari kerak. Windows 9x muhitida bu maqsadda iplarning silliq ishlashini ta'minlaydigan bir nechta mexanizmlardan foydalanish taklif etiladi. Ushbu mexanizmlar deyiladi sinxronizatsiya mexanizmlari. Aytaylik, siz ikkita ip parallel ravishda ishlaydigan dastur ishlab chiqmoqdasiz. Har bir mavzu bitta umumiy global o'zgaruvchiga kiradi. Bitta ip, har safar ushbu o'zgaruvchiga kirishda uni oshiradi, ikkinchi ip esa uni kamaytiradi. Iplarning bir vaqtning o'zida asinxron ishlashi bilan quyidagi holat muqarrar ravishda yuzaga keladi: - birinchi ip global o'zgaruvchining qiymatini lokalga o'qidi; - OS uni to'xtatadi, chunki unga ajratilgan protsessor vaqti kvanti tugadi va boshqaruvni ikkinchi ipga o'tkazadi; - ikkinchi ip global o'zgaruvchining qiymatini mahalliy qiymatga o'qiydi, uni qisqartirdi va yangi qiymatni qayta yozdi; - OT yana boshqaruvni birinchi ipga o'tkazadi, u ikkinchi ipning harakatlari haqida hech narsa bilmasdan, mahalliy o'zgaruvchini oshiradi va uning qiymatini globalga yozadi. Shubhasiz, ikkinchi ip tomonidan kiritilgan o'zgarishlar yo'qoladi. Bunday holatlarning oldini olish uchun umumiy ma'lumotlardan foydalanishni o'z vaqtida ajratish kerak. Bunday hollarda bir nechta iplarning to'g'ri ishlashini ta'minlaydigan sinxronizatsiya mexanizmlari qo'llaniladi. OTda sinxronlash vositalariWindows: 1) tanqidiy bo'lim (TanqidiyBo'lim) yadroga emas, balki jarayonga tegishli ob'ektdir. Bu shuni anglatadiki, u turli jarayonlardagi iplarni sinxronlashtira olmaydi. Initsializatsiya (yaratish) va o‘chirish, muhim bo‘limdan kirish va chiqish funksiyalari ham mavjud: yaratish – InitializeCriticalSection(...), o‘chirish – DeleteCriticalSection(...), kiritish – EnterCriticalSection(...), chiqish – LeaveCriticalSection (...). Cheklovlar: yadro ob'ekti bo'lmagani uchun u boshqa jarayonlarga ko'rinmaydi, ya'ni siz faqat o'zingizning jarayoningizning iplarini himoya qilishingiz mumkin. Kritik bo'lim bir vaqtning o'zida bir nechta ish zarrachalarining kod qismini bajarishini oldini olish uchun bayroq sifatida ishlatiladigan maxsus jarayon o'zgaruvchisining qiymatini tahlil qiladi. Sinxronizatsiya qiluvchi ob'ektlar orasida muhim bo'limlar eng oddiy hisoblanadi. 2) mutexo'zgaruvchanistisno qilish. Bu yadro ob'ekti, uning nomi bor, ya'ni ular bir nechta jarayonlardan, aniqrog'i, turli jarayonlarning iplaridan umumiy ma'lumotlarga kirishni sinxronlashtirish uchun ishlatilishi mumkin. Hech bir boshqa ip allaqachon iplardan biriga tegishli bo'lgan mutexni ololmaydi. Agar mutex ba'zi umumiy ma'lumotlarni himoya qilsa, u o'z vazifasini faqat har bir ip ushbu ma'lumotlarga kirishdan oldin ushbu mutex holatini tekshirsagina bajara oladi. Windows mutexga signal berilishi yoki qayta o'rnatilishi mumkin bo'lgan umumiy ob'ekt sifatida qaraydi. Muteksning signalli holati uning band ekanligini ko'rsatadi. Mavzular mutekslarning joriy holatini mustaqil ravishda tahlil qilishi kerak. Agar siz mutexga boshqa jarayonlardagi iplar orqali kirishni istasangiz, unga nom berishingiz kerak. Funksiyalar: CreateMutex(name) - yaratish, hnd=OpenMutex(nom) - ochish, WaitForSingleObject(hnd) - kutish va band qilish, ReleaseMutex(hnd) - chiqarish, CloseHandle(hnd) - yopish. U dasturlarni qayta ishga tushirishdan himoya qilish uchun ishlatilishi mumkin. 3) semafor -semafor. Yadro ob'ekti "semafor" resurslarni hisobga olish uchun ishlatiladi va bir vaqtning o'zida bir nechta oqimlar bilan resursga kirishni cheklash uchun xizmat qiladi. Semafordan foydalanib, siz dastur ishini shunday tashkil qilishingiz mumkinki, bir vaqtning o'zida bir nechta iplar manbaga kirishi mumkin, ammo bu iplar soni cheklangan bo'ladi. Semaforni yaratishda resurs bilan bir vaqtning o'zida ishlashi mumkin bo'lgan maksimal iplar soni ko'rsatilgan. Har safar dastur semaforga kirganda, semaforning resurs hisoblagichi bittaga kamayadi. Resurs hisoblagichi qiymati nolga aylanganda, semafor mavjud bo'lmaydi. CreateSemaphore-ni yarating, OpenSemaphore-ni oching, WaitForSingleObject-ni oling, ReleaseSemaphore-ni chiqaring 4 ) voqea -voqea. Voqealar, odatda, ba'zi operatsiyalarning tugashi haqida xabar beradi, ular yadro ob'ektlari hamdir. Siz nafaqat aniq chiqarishingiz mumkin, balki hodisani sozlash operatsiyasi ham mavjud. Voqealar qo'lda (qo'lda) va bitta (bitta) bo'lishi mumkin. Bitta hodisa ko'proq umumiy bayroqdir. Voqea qandaydir ip tomonidan o'rnatilgan bo'lsa, signal holatida bo'ladi. Agar dastur voqea sodir bo'lgan taqdirda faqat bitta ipning unga munosabat bildirishini talab qilsa, qolgan barcha oqimlar kutishda davom etsa, u holda bitta hodisa ishlatiladi. Qo'lda sodir bo'lgan hodisa shunchaki bir nechta mavzulardagi umumiy bayroq emas. U biroz murakkabroq funktsiyalarni bajaradi. Har qanday mavzu ushbu hodisani o'rnatishi yoki uni qayta o'rnatishi (o'chirish) mumkin. Hodisa o'rnatilgandan so'ng, hodisa o'rnatilishini qancha mavzu kutayotganidan qat'iy nazar, u o'zboshimchalik bilan uzoq vaqt davomida shu holatda qoladi. Ushbu hodisani kutayotgan barcha mavzular voqea sodir bo'lganligi haqida xabar olganida, u avtomatik ravishda qayta tiklanadi. Funktsiyalar: SetEvent, ClearEvent, WaitForEvent. Hodisa turlari: 1) avtomatik tiklash hodisasi: WaitForSingleEvent. 2) qo'lda qayta o'rnatish (qo'lda) bo'lgan voqea, keyin voqea qayta o'rnatilishi kerak: ReleaseEvent. Ba'zi nazariyotchilar boshqa sinxronizatsiya ob'ektini ajratib ko'rsatishadi: WaitAbleTimer - bu ma'lum vaqt oralig'idan (budilnik) keyin mustaqil ravishda erkin holatga o'tadigan OS yadrosi ob'ekti.

Ba'zan bir nechta iplar yoki jarayonlar bilan ishlashda zarurat tug'iladi bajarilishini sinxronlashtirish ulardan ikkitasi yoki undan ko'pi. Buning sababi, ko'pincha ikki yoki undan ortiq mavzular umumiy manbaga kirishni talab qilishi mumkin haqiqatan ham bir vaqtning o'zida bir nechta mavzularni taqdim etish mumkin emas. Umumiy resurs - bu bir vaqtning o'zida bir nechta ishlaydigan vazifalar orqali kirish mumkin bo'lgan manba.

Sinxronizatsiya jarayonini ta'minlaydigan mexanizm deyiladi kirishni cheklash. Unga bo'lgan ehtiyoj, shuningdek, bitta ip boshqa ip tomonidan yaratilgan hodisani kutayotgan hollarda paydo bo'ladi. Tabiiyki, voqea sodir bo'lgunga qadar birinchi ipni to'xtatib turishning qandaydir usuli bo'lishi kerak. Shundan so'ng, ip o'z bajarilishini davom ettirishi kerak.

Vazifa bo'lishi mumkin bo'lgan ikkita umumiy holat mavjud. Birinchidan, vazifa mumkin amalga oshirilsin(yoki protsessor resurslariga kirishi bilanoq bajarishga tayyor bo'ling). Ikkinchidan, vazifa bo'lishi mumkin bloklangan. Bunday holda, uning bajarilishi kerakli resurs chiqarilgunga qadar yoki ma'lum bir voqea sodir bo'lgunga qadar to'xtatiladi.

Windows-da ma'lum bir tarzda umumiy resurslarga kirishni cheklash imkonini beruvchi maxsus xizmatlar mavjud, chunki operatsion tizimning yordamisiz alohida jarayon yoki oqim resursga yagona kirish imkoniyatini o'zi aniqlay olmaydi. Windows operatsion tizimida bitta uzluksiz operatsiyada resursga kirish bayrog'ini tekshiradigan va iloji bo'lsa o'rnatadigan protsedura mavjud. Operatsion tizimni ishlab chiquvchilar tilida bunday operatsiya deyiladi ishlashini tekshiring va o'rnating. Sinxronizatsiyani ta'minlash va resurslarga kirishni boshqarish uchun ishlatiladigan bayroqlar deyiladi semaforlar(semafor). Win32 API semaforlar va boshqa sinxronizatsiya ob'ektlarini qo'llab-quvvatlaydi. MFC kutubxonasi ushbu ob'ektlarni qo'llab-quvvatlashni ham o'z ichiga oladi.

Sinxronizatsiya ob'ektlari va mfc sinflari

Win32 interfeysi to'rt turdagi sinxronizatsiya ob'ektlarini qo'llab-quvvatlaydi, ularning barchasi u yoki bu tarzda semafor tushunchasiga asoslangan.

Birinchi turdagi ob'ekt semaforning o'zi yoki klassik (standart) semafor. Bu cheklangan miqdordagi jarayonlar va oqimlarga bitta resursga kirish imkonini beradi. Bunday holda, resursga kirish butunlay cheklangan (bitta va faqat bitta mavzu yoki jarayon ma'lum vaqt ichida resursga kirishi mumkin) yoki bir vaqtning o'zida faqat oz sonli iplar va jarayonlarga kirish mumkin. Semaforlar hisoblagich bilan amalga oshiriladi, u vazifaga semafor ajratilganda kamayadi va topshiriq semaforni chiqarganda ortadi.

Sinxronizatsiya ob'ektlarining ikkinchi turi eksklyuziv (mutex) semafor. U resursga kirishni to'liq cheklash uchun mo'ljallangan, shuning uchun har qanday vaqtda faqat bitta jarayon yoki oqim manbaga kirishi mumkin. Aslida, bu semaforning o'ziga xos turi.

Sinxronizatsiya ob'ektlarining uchinchi turi voqea, yoki hodisa ob'ekti. U resursdan foydalanish mumkinligini boshqa jarayon yoki oqim e'lon qilmaguncha resursga kirishni bloklash uchun ishlatiladi. Shunday qilib, bu ob'ekt talab qilinadigan hodisaning bajarilishi haqida signal beradi.

To'rtinchi turdagi sinxronizatsiya ob'ektidan foydalanib, dastur kodining ma'lum bo'limlarini bir vaqtning o'zida bir nechta iplar tomonidan bajarilishini taqiqlash mumkin. Buning uchun ushbu posilkalar sifatida e'lon qilinishi kerak tanqidiy bo'lim. Ushbu bo'limga bitta ip kirsa, birinchi ip ushbu bo'limdan chiqmaguncha, boshqa iplar ham xuddi shunday qilishlari taqiqlanadi.

Kritik bo'limlar, sinxronizatsiya ob'ektlarining boshqa turlaridan farqli o'laroq, faqat bitta jarayon doirasidagi oqimlarni sinxronlashtirish uchun ishlatiladi. Jarayon ichidagi oqimlarni sinxronlashtirish yoki jarayonlarni sinxronlashtirish uchun boshqa turdagi ob'ektlardan foydalanish mumkin.

MFC-da Win32 interfeysi tomonidan taqdim etilgan sinxronizatsiya mexanizmi CSyncObject sinfidan olingan quyidagi sinflar orqali qo'llab-quvvatlanadi:

    CCriticalSection- tanqidiy qismni amalga oshiradi.

    CEvent- hodisa obyektini amalga oshiradi

    CMutex- eksklyuziv semani amalga oshiradi.

    CSemafora- klassik semani amalga oshiradi.

Ushbu sinflarga qo'shimcha ravishda, MFC ikkita yordamchi sinxronizatsiya sinfini ham belgilaydi: CSingleLock va CMultiLock. Ular sinxronizatsiya ob'ektiga kirishni nazorat qiladi va bunday ob'ektlarni berish va chiqarish uchun ishlatiladigan usullarni o'z ichiga oladi. Sinf CSingleLock yagona sinxronlash obyektiga va sinfga kirishni nazorat qiladi CMultiLock- bir nechta ob'ektlarga. Keyinchalik, biz faqat sinfni ko'rib chiqamiz CSingleLock.

Har qanday sinxronizatsiya ob'ekti yaratilganda, unga kirishni sinf yordamida boshqarish mumkin CSingleLock. Buning uchun avvalo turdagi ob'ektni yaratish kerak CSingleLock konstruktor yordamida:

CSingleLock(CSyncObject* pObject, BOOL bInitialLock = FALSE);

Birinchi parametr sinxronizatsiya ob'ektiga ko'rsatgichdir, masalan, semafor. Ikkinchi parametrning qiymati konstruktor berilgan ob'ektga kirishga harakat qilishi kerakligini aniqlaydi. Agar bu parametr nolga teng bo'lmasa, u holda kirishga ruxsat beriladi, aks holda kirishga urinilmaydi. Agar kirish huquqi berilsa, u holda sinf ob'ektini yaratgan ip CSingleLock, mos keladigan sinxronizatsiya ob'ekti usul bilan bo'shatilguncha to'xtatiladi Qulfni ochish sinf CSingleLock.

CSingleLock tipidagi ob'ekt yaratilgandan so'ng, pObject parametri bilan ko'rsatilgan ob'ektga kirish ikkita funktsiya yordamida boshqarilishi mumkin: qulf va Qulfni ochish sinf CSingleLock.

Usul qulf ob'ektga sinxronizatsiya ob'ektiga kirish uchun mo'ljallangan. Uni chaqirgan ip, usul tugagunga qadar, ya'ni resursga kirishgacha to'xtatiladi. Parametrning qiymati funksiya kerakli ob'ektga kirish uchun qancha vaqt kutishini aniqlaydi. Har safar usul muvaffaqiyatli yakunlanganda, sinxronizatsiya ob'ekti bilan bog'langan hisoblagichning qiymati bittaga kamayadi.

Usul Qulfni ochish sinxronizatsiya ob'ektini chiqaradi, bu boshqa oqimlarga resursdan foydalanishga imkon beradi. Usulning birinchi variantida berilgan ob'ekt bilan bog'langan hisoblagichning qiymati bittaga oshiriladi. Ikkinchi variantda birinchi parametr bu qiymatni qanchalik oshirish kerakligini aniqlaydi. Ikkinchi parametr hisoblagichning oldingi qiymati yoziladigan o'zgaruvchiga ishora qiladi.

Sinf bilan ishlashda CSingleLock Resursga kirishni nazorat qilishning umumiy tartibi quyidagicha:

    resursga kirishni boshqarish uchun foydalaniladigan CSyncObj tipidagi ob'ektni yaratish (masalan, semafor);

    yaratilgan sinxronizatsiya ob'ektidan foydalanib, CSingleLock tipidagi ob'ektni yarating;

    resursga kirish uchun Lock usulini chaqiring;

    resursga qo'ng'iroq qilish;

    resursni chiqarish uchun Unlock usulini chaqiring.

Quyida semaforlar va hodisa ob'ektlarini qanday yaratish va ulardan foydalanish tasvirlangan. Ushbu tushunchalarni tushunganingizdan so'ng, siz boshqa ikki turdagi sinxronizatsiya ob'ektlarini osongina o'rganishingiz va ulardan foydalanishingiz mumkin: muhim bo'limlar va mutekslar.