10.6.2. Синхрондау. Оқиғалар, семафоралар,
мьютекстер
Бір уақытта жұмыс істейтін бірнеше бәсекелес процестерді
244
сүйемелдейтін операциялық жүйелердің көпшілігі, Windows тобының
операциялық жүйелері процестер мен ағымдарды синхрондау
механизмдерінің бір қатарын құрайды. Бұл процестерге, бөлінбейтін
ресурстарды бір уақытта қолданудан құтылуға мүмкіндік береді, бұл
процестердің бұзылуына, кейбір жағдайларда оның барлық
жүйелерінің бүзылуына әкеледі.
Windows тобының операциалық жүйелері: оқиғалар, семафорлар,
мьютекстер, критикалық облыстар және бәсекелесетін процестердің
арасында ресурстарды бөлуді ұйымдастыратын тәсілдердің бір қатары
сияқты синхрондау механизмдерін көтереді. Берілген тарауда біз
оқиғалар, семафорлар және мьютекстер сияқты синхрондау
механизмдерінің жұмыстарын қарастырамыз.
Оқиға (events) синхрондау механизмінің обьектісі ретінде
ұсынылады, кейбір бағдарламалы басқару оқиғаларының түскенін
хабарлауға арналған. Синхрондаудің екі типі бар — қолмен
атқарылып тасталатын оқиғалар, және автоматты тасталатын оқиғалар.
Автоматты тасталатын оқиғалар, егер осындай оқиға сигналды
күйге ауысқанда, жүйе автоматты ағымның сигналды күйге күтілуі
таңдалады және оған басқаруын беруімен ерекшеленеді. Сонымен
қатар, осындай оқиға сигналды емес күйге автоматты ауысады. Қолмен
тасталатын оқиғалармен жұмыс кезінде, сигналды емес күйдегі
оқиғаның қалпына келуі жауапкершілігін бағдарламалаушы алады. Бұл
жағдайда ол, оқиғаны сигналды күйге ауыстыру қажет болғанда сайын
ResetEvent() функциясын нақты шақырады.
Оқиғаның құрылуы үшін CreateEventQ функциясы қолданылады:
include
HANDLE WINAPI CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName);
Бірінші параметр lpEventAttributes, процесс-тобымен құрылатын
оқиғаның дескрипторы бар немесе мұраға берілетін, сондай-ақ
құрылатын оқиғаның қолжеткізу құқығын өзгертуге жауап береді. Егер
берілген параметр NULL мәніне ие болса, онда дескриптор
мұраланбайды және әдепкі жағдай бойынша қол жеткізу құқығы
қондырылады.
Параметр bManualReset, оқиғаның қандай типінің құрылуы тиісті
екеніне жауап береді. Егер осы параметр TRUE мәнін қабылдаса, онда
245
қолмен тасталатын оқиғалар құрылады. Егер де FALSE мәні берілсе,
автоматикалық тасталатын оқиғалар құрылады.
Параметр bInitialState, құрылатын оқиғаның бастапқы күйін беру
үшін арналған. Егер осы параметр TRUE тең болса, онда бастапқы
сигналды күйдегі оқиғамен оқиғалар құрылады. Қарсы жағдайда,
оқиғаның бастапқы күйі сигналды емеске қондырылады.
LpName параметр, құрылатын оқиғаның атауын беру үшін
қолданылады. Егер осы параметр NULL тең болса, онда анонимді
объект-оқиғалар құрылады. Егер осы параметрде аты берілсе, онда
жүйе осындай аты бар оқиғаның бар-жоғын тексереді. Егер осындай
оқиға болмаса, онда бастапқы қондырғылардың талаптарымен жаңа
оқиға құрылады. Қарсы жағдайда жаңа оқиға құрылмайды, бұдан
бұрын құрылған берілген атпен ассоциацияланған дескриптор
ашылады. Құрылатын оқиғалар саны процесс үшін тек жүйелік
ресурстармен
және
дескрипторлардың
шектелген
санымен
лимиттеледі, олар жүйе әкімшісімен өзгертіле алады.
Жүйедегі айқын нұсқау үшін бұдан бұрын құрылған оқиғаларға
дескрипторды алу қажет, ол үшін OpenEvent() функциясы
қолданылады:
include
HANDLE WINAPI OpenEvent(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName);
dwDesiredAccess параметрі жүйеге пайдаланушының оқиғаны
басқару үшін қандай рұқсат құқықтарын талап ететінін хабарлайды.
Осы параметр оқиғаға толық рұқсатты алуға мүмкіндік беретін
EVENT_ALL_ACCESS не EVENT_MODIFY_ STATE мәнін өзгерте
алады. Бұл мән пайдаланушыға оқиғаның жай-күйін өзгерте алады
және көп жағдайларда оның жұмыс істеуіне осы жеткілікті болады.
Параметр bInheritHandle, оқиғаның құрылатын дескрипторы
еншілес процеске мұралана ма, жоқ па, соны анықтайды. Егер осы
параметр TRUE тең болса, онда құрылатын дескриптор процесс-
тобымен мұраланады, басқа жағдайда мұраланбайтын дескриптор
құрылады.
Параметр lpName, бар оқиғаның атауын береді, пайдаланушы оған
қолжеткізу құқығына жеткісі келеді. Осылайша, осы функцияның
көмегімен тек атаулы оқиғаларға қол жеткізуге болады. Анонимді
оқиғалар, оқиға құратын процестерді және ағымдарды қолдануы
246
мүмкін.
Кез келген тип оқиғасының сигналды күйге ауысуы SetEvent()
функциясымен жүзеге асырылады.:
include
DWORD WINAPI SetEvent(HANDLE hEvent);
Параметрі ретінде осы функцияға, сол оқиғаның дескрипторы
беріледі, ол сигналды күйге ауысуы керек.
Оқиғаның тасталуы үшін ResetEvent() функциясы қолданылады:
include
DWORD WINAPI ResetEvent(HANDLE hEvent);
Параметр hEvent, өз кезегінде, күйі сигналды жағдайдан, сигналдық
емес қалпына келетін оқиғаның дескрипторын береді.
Процестер немесе ағымдар жұмысының аяқталуын күткен
жағдайда, процесс басқа оқиғаның сигналды күйге ауысуы, жоғарыда
қарастырылған WaitForSingleObjectQ және WaitForMultipleObjectsQ
функцияларының көмегімен жүретінін хабарлайды.
Процестер мен ағымдардың синхрондау үшін оқиғалардың
қолданылуына мысал ретінде, жоғарыдағы, әртүрлі уақыт орындалуы
бар, екі процестің үйлестірілген мысалын қарастырамыз.:
#include
#include
#include
DWORD WINAPI ThreadProc(LPVOID lpParam);
void ErrorReport(LPTSTRlpszFunction);
int main()
{
DWORD dwThreadId;
HANDLE hThread, hEvent1, hEvent2;
unsigned i;
// автоматты тасталатын оқиға құрамыз //
бастапқы сигналды емес оқиғамен
if((hEvent1=CreateEvent(NULL, FALSE, FALSE,
"Thread1")) == NULL)
{
ErrorReport(TEXT("CreateEvent()"));
return(1);
}
// бірінші ағыммен синхрондауға тырысатын
247
ағым құрамыз // hThread = CreateThread(
NULL,
// әдепкі бойынша құқық
0,
// әдеттегідей қамшы өлшемі
ThreadProc, // ағымның функциясы
NULL,
// функция үшін аргумент
0,
// әдеттегідей жалауы жоқ
&dwThreadId); if
(hThread == NULL)
{
ErrorReport(TEXT("CreateThread()"));
return(1);
}
// екінші ағынмен тағы бір оқиғаның құрылуын күтеміз
// екінші оқиғаның құрылғанынан кейін// ол алғашқы
оқиғаны сигналды күйге ауыстырады // ResetEvents()
функциясы шақырылмайды,
// себебі оқиға автоматты тасталынады
WaitForSingleObject(hEvent1, INFINITE);
// туындаған ағыммен құрылған оқиғаны ашамыз
if((hEvent2 = OpenEvent(EVENT_ALL_ACCESS, FALSE,
"Thread2")) == NULL)
{
ErrorReport(TEXT("OpenEvent()"));
return(1);
}
// ағым-ұрпағына басқаруды береміз // екінші оқиғаны
сигналды күйге SetEvent(hEvent2);
// алғашқы ағымның негізгі цикл for(i = 0; i < 10;
++i)
{
// туындаған ағымның басқарылуын беруді күтеміз //
WaitForSingleObject(hEvent1, INFINITE);
// ақпараттарды консолға шығарамыз printf("p%d ",
i);
// алғашқы ағымды 1 сек блоктаймыз Sleep(1000);
// туындаған ағымға басқаруды береміз
SetEvent(hEvent2);
}
// туындаған ағымның аяқталуын күтеміз
WaitForSingleObject(hThread, INFINITE);
// оның дескрипторын жабамыз CloseHandle(hThread);
// оқиғаның дескрипторын жабамыз
CloseHandle(hEvent1);
CloseHandle(hEvent2);
printf("\n");
248
return(0);
}
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
HANDLE hEvent1, hEvent2;
unsigned i;
// алғашқы ағыммен құрылған оқиғаны ашамыз if((hEvent1 =
OpenEvent(EVENT_ALL_ACCESS, FALSE, "Threadl")) == NULL)
{
ErrorReport(TEXT("OpenEvent()"));
return(1);
}
// автоматты лақтыруды құрамыз С // сигналды емес
алғашқы күймен ((hEvent2 =CreateEvent(NULL, FALSE,
FALSE, "Thread2")) == NULL)
{
ErrorReport(TEXT("CreateEvent()"));
return(1);
}
// алғашқы ағымға, қазіргі ағымда құрылған
SetEvent(hEvent1) оқиғаны ашу үшін // береміз
басқаруды;
// еншілес ағымның негізгі циклі for(i = 0; i < 10;
++i)
{
// алғашқы ағымнан басқару жіберілімдерін алу
//
WaitForSingleObject(hEvent2, INFINITE);
// ақпараттарды консолға шығарамыз printf("c%d
", i);
// ағымды 4 сек блоктаймыз Sleep(4000);
// алғашқы ағымның басқаруын жіберу
SetEvent(hEvent1);
}
// оқиғаның дескрипторын жабамыз
CloseHandle(hEvent1);
CloseHandle(hEvent2);
return(0);
}
void ErrorReport(LPTSTR lpszFunction)
{
LPVOIDlpMsgBuf;
249
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)
&lpMsgBuf,
0, NULL ) ;
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT,
(lstrlen((LPCTSTR)lpMsgBuf)+lstrlen((LPCTSTR)
lpszFunction)+40)*sizeof(TCHAR));
_stprintf((LPTSTR)lpDisplayBuf,
TEXT("%s failed with error %d: %s"), lpszFunction,
dw, lpMsgBuf);
MessageBox(NULL, (LPCTSTR)lpDisplayBuf,
TEXT("Error"), MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
}
Осы бағдарламаның жұмысы нәтижесінде консолға келесі түрдің
жолы шығады:
c0 p0 c1 p1 c2 p2 c3 p3 c4 p4 c5 p5 c6 p6 c7 p7 c8 p8 c9
p9
Ағымдарды блоктаудың әр түрлі уақыттарына қарамастан, олардың
шығарылуы қатаң кезектеседі. Егер де жоғарыда келтірілген
бағдарламаларда циклдің ішінде SetEvent() және WaitForSmgleObjectQ
функцияларының шақыртуларын және алғашқы ағым мен ағым-тобын
түсіндіре кетсе, онда бағдарлама жұмысының нәтижесі келесі түрге ие
болады:
p0 c0 p1 p2 p3 p4 c1 p5 p6 p7 p8 c2 p9 c3 c4 c5 c6 c7 c8
c9
бірінші ағымның ағым-тобын еш синхрондамағаны көрініп тұр
және еншілес ағымға қарағанда, өзінің негізгі жұмысын ертерек
аяқтайды.
Екінші механизм Windowsті синхрондау үшін қолданылады, Linux
250
— семафор синхрондағыш құрылғылармен танысқанда кездестіргенбіз.
Windows семафорлары ішкі есептегіші бар синхрондаушы обьектілерін
ұсынады, оның мәні нөлден берілген максималды мәнге дейінгі
диапозонда орналасуы мүмкін. Бұл есептегіштің мәні, ағымның біріне
(WaitForSingleObjectQ и WaitForMultipleObjects()) күту функциясының
басқарылуы қайтарылған сайын азаяды, ол ағым семафорды
босатқанда артады. Семафор, есептегіштің мәні 0-ге тең болмағанда
сигналды күйге түседі, ал есептегіш мәні 0-ге тең болғанда, сигналдық
емес болады.
Семафорды құру үшін CreateSemaphore() функциясы қолданылады:
#include
HANDLE WINAPI CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,
LONG lMaximumCount,
LPCTSTR lpName);
LpSemaphoreAttributes параметрі процесс тобының құрылатын
семафор дескрипторын мұраланатынын не мұраланбайтынына жауап
береді, сондай-ақ оның қолжеткізу құқығын өзгертеді. Егер осы
параметр NULL мәніне ие болса, дескриптор топпен мұраланбайды
және әдеттегідей қолжеткізу құқығын орнатады.
LInitialCount
параметрінде семафордың ішкі есептегішінің
бастапқы мәндері беріледі. Есептегіштің максималды қолжетімді мәні
lMaximumCount параметрінде беріледі.
Параметр lpName, құрылатын семафордың атауын беруге
қолданылады. Егер осы параметр NULL тең болса, анонимді семафор
құрылады. Егер де Windows жүйелік обьектілерін құру кезінде
қолданылған атауы берілетін болса, оқиғалар, семафорлар, мьютекстер,
онда семафор құрылмайды және функция қате аяқталады.
Егер функция сәтті аяқталса және семафор құрылса, онда
CreateSemaphore() функциясы құрылған семафордың дескрипторын
қайтарады. Басқа жағдайда функция NULL мәнін қайтарады. Құрылған
семафордың саны, оқиғалар саны сияқты, жүйелік ресурспен және бір
ғана процесті құратын, дескриптор саны лимитімен шектеледі.
Алдымен құрылған семафордың дескрипторын алу қажеттілігін
жүйеде көрсету үшін OpenSemaphore() функция қолданылады:
include
HANDLE WINAPI OpenSemaphore(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
251
LPCTSTR lpName);
Параметр dwDesiredAccess, семафорды басқару үшін пайдаланушы
қандай қол жеткізу құқықтарын талап етіп, жүйеге хабарлайды. Бұл
параметр
обьектіге
толық
қолжеткізуге
мүмкіндік
беретін
SEMAPHORE_ALL_ACCESS немесе SEMAPHORE_ MODIFY_STATE
мәнін қабылдайды.
Параметр bInheritHandle, семафордың құрылатын дескрипторы,
еншілес процесті мұрағаттайтынын анықтайды. Егер осы параметр
TRUE мәніне ие болса, онда құрылатын дескриптор процесс-тобымен
мұрағатталады керісінше мұрағаттанбайтын дескриптор құрылады.
Параметр lpName қолжетімділікті алуы қажет, алдыңғы құрылған
семафордың атауын береді. Осы функцияның көмегімен атаулы
семафорға ғана қол жеткізуге болады, ал атаусыз семафорлар осы
обьекттің және ағымдары құратын процесспен ғана қолданылады.
Семафорды
босату
үшін
ReleaseSemaphore()
функциясы
қолданылады:
include
BOOL WINAPI ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount,
LPLONG lpPreviousCount);
Параметр hSemaphore, босатылуы тиіс семафордың дескрипторын
береді. Параметр lReleaseCount, семафордың ішкі есептегішін
арттыратын, мәнді береді. Берілген мән 0-ден көп болуы қажет.
Параметр lpPreviousCount, семафор есептегішінің алдыңғы мәндерін
қайтару үшін қолданылады.
Windows – те процесстер мен ағымдарды синхрондаудың бірі
мьютекс болып табылады. Мьютекс өзімен, бір ағымға да жатпайтын
болғанда, сигналды күйге ауысатын, синхрондау обьектісін ұсынады.
Егер де мьютекс қандай да бір ағымға жататын болса, онда сигналды
емес күйге ауысады. Осы тұрғыда, мьютекстер, олармен бөлінетін
ресурсқа бірнеше ағымның қол жеткізуін болдырмауды ұйымдастыру
кезінде, ыңғайлы. Мұндай ресурсқа мысал болып, бөлінетін жады
сияқты, процессаралық өзара әрекеттесудің объектісі болып табылады.
Осы объектіге ақпараттарды жазуға, әр уақыт сәтінде бір есептеуші
ағым міндетті. Сондықтан, мұндай процессаралық өзара әрекеттесудің
механизмінің қолжетімділігін болдырмауды ұйымдастырудың міндеті
өте маңызды болып табылады. Жалпы мьютекстерді семафордың бір
252
нұсқасы ретінде қарастыруға болады.
Мьютексті құру үшін CreateMutexQ функциясы қолданылады:
#include
HANDLE WINAPI CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName);
Параметр lpMutexAttributes топтарымен құрылатын мьютекс
дескрипторы мұралануға, сондай-ақ оның қолжеткізу құқығын
өзгертілуіне жауап береді. Егер осы параметр NULL мәніне ие болса,
дескриптор мұраланбайды және әдеттегідей құқықтары қондырылады.
Егер bInitialOwner параметрі TRUE тең болса, онда мьютекс
құрған ағым, пайдаланушымен жарияланады. Егер де осы параметр
FALSE тең болса, онда мьютексті құратын есептеуші ағым, оны
иелікке алмайды.
LpName параметрі, мьютекстің атауын беру үшін қолданылады.
Егер осы параметр NULL тең болса анонимді объект құрылады. Егер
Windows – те оқиға, семафор, мьютекс т.б. сияқты жүйелік
обьектілерін сәтсіз құруда, функция қатемен аяқталады. Егер функция
сәтті аяқталса және семафор құрылса CreateMutexQ функциясы
құрылған объектінің дескрипторын қайтарады. Басқа жағдайда
функция NULL мәнін қайтарады.
Жүйеде құрылған мьютекстердің максималды саны, жүйедегі бос
ресурстардың болуымен (бос виртуалды жадының ерекшелігі) және
процестегі дескриптор санының шектелуімен анықталады.
Алдымен құрылған мьютекстің дескрипторын алу қажеттілігін,
жүйеде нақты көрсету үшін OpenMutex() функциясы қолданылады:
include
HANDLE WINAPI OpenMutex(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
LPCTSTR lpName);
DwDesiredAccess параметрі пайдаланушының мьютексті басқару
үшін қандай қолжетімділік құқықтарын талап ететінін жүйеге
хабарлайды. Осы параметр обьектіге толық қол жеткізу үшін
MUTEX_ALL_ACCESS немесе MUTEX_ MODIFY_STATE мәндерін
қабылдай алады.
253
ВInheritHandle параметрі, мьютекстің құрылатын дескрипторы
еншілес процеске мұрағатталатынын анықтайды. Егер бұл параметр
TRUE мәніне ие болса, құрылатын дескриптор еншілес процесті
мұралайды, басқа жағдайда мұраланбайтын дескриптор құрылады.
Параметр lpName қолжетімділікті алуы керек мьютекстің атауын
береді. Осы функцияның көмегімен, тек атаулы семафорға
қолжетімділік алуға болады, ал атаусыз семафорлар, тек берілген
обьект және оның тобы құратын процеспен ғана пайдаланылады.
Мьютексті босату үшін ReleaseMutex() функциясы қолданылады:
include
BOOL WINAPI ReleaseMutex(HANDLE hMutex);
Параметр hSemaphore босатылуы қажет мьютекстің дескрипторын
береді.
Есептеуші ағымдарды синхрондау үшін мьютекстер мен
семафорларды пайдалану үлгісін қарастырамыз. Ол үшін алдында
қарастырылған үлгіні жетілдіреміз және синхрондаудың екі
механизмін қолданамыз: семафорлар және мьютекстер.
Достарыңызбен бөлісу: |