Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

На сортировку / 5 / 777301 / 2ргр_акбопе

.docx
Скачиваний:
2
Добавлен:
12.12.2017
Размер:
46.23 Кб
Скачать

Коммерциялық емес акционерлік қоғам

«АЛМАТЫ ЭНЕРГЕТИКА ЖӘНЕ БАЙЛАНЫС УНИВЕРСИТЕТІ»

Ақпараттық жүйелер кафедрасы

2 Есептік – сызба жұмысы

Пәні: «Жүйелік программалау»

Тақырыбы: «CRITICAL_SECTION объектісі»

Мамандығы: 5B070400 – Есептеу техникасы және бағдарламалық қамтамасыз ету

Орындаған: Чакеева А.С. Топ: ВТк-15-1

Қабылдаған: аға оқытушы Айткулов Ж.

« » 201 ж.

бағасы қолы

Алматы 2017

Мазмұны

  1. Кіріспе........................................................................................3

  2. Негізгі бөлім..............................................................................5

  3. Қорытынды................................................................................13

Кіріспе

Windows операциялық жүйелерінде бір үрдіс (процесс) контекстінде орындалатын параллелді ағындар үшін өзара ерекшелік (взаимного исключения) мəселелері операциялық жүйенің ядросы болып саналмайтын CRITICAL_SECTION типтік объектісінің көмегімен шешіледі. CRITICAL_SECTION типтік объектілермен жұмыс істеу үшін келесі функциялар қолданылады:

// критикалық секцияны инициализациялау

VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

// критикалық секцияға кіру

VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

// критикалық секцияға кіру əрекетін жасау

BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

// критикалық секциядан шығу

VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

// критикалық секцияны жою

VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

Бұл функциялардың əрбірі CRITICAL_SECTION типтік объектісіне сілтеме жасайтын жалғыз параметрге ие. TryEnterCriticalSelection функциясынан басқа барлық функциялар мəндерді қайтармайды. TryEnterCriticalSelection функциясы егер ағын критикалық секцияға кірген болса нөлдік емес мəнді қайтарады, қарсы жағдайда функция FALSE мəнін қайтарады.

Осы функцялармен жұмыс істеу тəртібін қарастырайық. Ол үшін программаны жобалау кезінде параллелдік ағындарда критикалық секциялар бөлдік делік, бұл критикалық секцияларда осы ағындар жіктейтін ресурстар қолдыналады. Сонда программада CRITICAL_SECTION типтік объектіні анықтаймыз жəне осы объектінің аты қолданылатын жіктеуші ресурстармен логикалық тұрғыда байланысты деп санаймыз. CRITICAL_SECTION типтік объектісімен жұмыс жасаудан бұрын, оны инициализациялау керек. Бұл үшін InitializeCriticalSelection функциясы арналған. CRITICAL_SECTION типтік объектісі инициализацияланған соң əрбір параллелді ағындарда критикалық секцяға кірместен бұрын EnterCriticalSelection функциясын шақырамыз, ол параллелдік ағындарда орындалып жатқан жəне бөлінуші ресурстармен байланысты критикалық секцияларға біруақытта кіруге жол бермейді. Бөліуші ресурстармен жұмыс істеп біткен соң, ағын өзінің критикалық секциясынан шығуы керек, бұл LeaveCriticalSelection функциясының көмегімен іске асырылады. CRITICAL_SECTION типтік объектісімен жұмыс істеп біткен соң осы объектімен жұмыс істеген барлық жүйелік ресурстарды босату керек. Осы мақсатында DeleteCriticalSection функциясы қолданылады.

Негізгі бөлім

Критикалық секция - бір мезгілде кейбір маңызды деректерге бір уақытта қолданбаның барлық ағындарына қол жеткізуді бұғаттау үшін қолданылатын нысандар. Мысалы, m_pObject айнымалысы бар және m_pObject сілтеме жасайтын нысанның әдістерін атайтын бірнеше ағындар бар және бұл айнымалы мән уақыт бойынша өзгерте алады. Кейде тіпті нөлге тең. Мысалы:

// Нить №1

void Proc1()

{

if (m_pObject)

m_pObject->SomeMethod();

}

// Нить №2

void Proc2(IObject *pNewObject)

{

if (m_pObject)

delete m_pObject;

m_pObject = pNewobject;

Мұнда m_pObject-> SomeMethod () параметрін жойып, нысан m_pObject арқылы жойылғаннан кейін ықтимал қауіп бар. Мәселе мынада, алдын ала мультисенцировка бар жүйелерде, процестің кез келген ағынының орындалуы оның ең сәтсіз сәтінде үзілуі мүмкін және мүлдем басқа жіп орындала бастайды. Бұл мысалда, m_pObject-ті тексерген # 1-дегі ағын дәл сәт емес, бірақ SomeMethod () деп атала алмады. №1 жіптің орындалуы үзілді және № 2-нің орындалуы басталды. Және 2-шi дана объектiнiң деструкторын шақырды. # 1-жады процессорлық уақытты біраз уақыт алады және болмаған нысаннан SomeMethod () шақырады не болады? Әрине, қорқынышты нәрсе.

Бұл жерде сыни секциялар құтқаруға келеді. Біздің мысалымызды қайта жазамыз.

// Нить №1

void Proc1()

{

::EnterCriticalSection(&m_lockObject);

if (m_pObject)

m_pObject->SomeMethod();

::LeaveCriticalSection(&m_lockObject);

}

// Нить №2

void Proc2(IObject *pNewObject)

{

::EnterCriticalSection(&m_lockObject);

if (m_pObject)

delete m_pObject;

m_pObject = pNewobject;

::LeaveCriticalSection(&m_lockObject);

}

EnterCriticalSection () және :: LeaveCriticalSection () параметрлері сияқты ең маңызды бөлімі бар код арасындағы параллель ешқашан орындалмайды. Бұл дегеніміз, егер # 1-дегі thread m_lockObject сыни бөлігін «басып» алу үшін уақыт болса, онда сол секілді сыни секцияны өзінің жеке пайдалануына шығару үшін # 2-ге жүгінуге тырысқанда, оны орындау # 1-ні «босататын» дейін m_lockObject call қалдырыңыз :: LeaveCriticalSection (). Және керісінше, егер 2-ші ағынның алдындағы жіп болса, онда ол m_pObject-мен жұмыс істей бастағанға дейін «күту» керек.

Күрделі секцияларда не болады және олар қалай ұйымдастырылады? Ең алдымен, сыни секциялар операциялық жүйенің ядросы емес. Барлығы дерлік сыни секциялармен жұмыс жасайды, оларды жасаған процесс. Осыдан кейін сыни бөлімдер тек бір процесте синхрондау үшін ғана қолданыла алады. Енді сыни бөлімдерді жақынырақ қарастырыңыз.

Структура RTL_CRITICAL_SECTION

typedef

struct _RTL_CRITICAL_SECTION {

PRTL_CRITICAL_SECTION_DEBUG DebugInfo; // Используется операционной системой

LONG LockCount; // Счетчик использования этой критической секции

LONG RecursionCount; // Счетчик повторного захвата из нити-владельца

HANDLE OwningThread; // Уникальный ID нити-владельца

HANDLE LockSemaphore; // Объект ядра используемый для ожидания

ULONG_PTR SpinCount; // Количество холостых циклов перед вызовом ядра

} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

LockCount өрісі әрқашан :: EnterCriticalSection () және incremented сайын төмендейді :: LeaveCriticalSection (). Бұл сыни бөлімнің «басып алу» жолында бірінші (және көбінесе жалғыз тест). Егер осы өрістің ұлғаюынан кейін нөл болса, бұл басқа ағындардан шыққан қоңыраулар: EnterCriticalSection () осы сәтке дейін болған жоқ дегенді білдіреді. Бұл жағдайда, сіз осы маңызды бөлікпен қорғалған деректерді эксклюзивті пайдалану арқылы ала аласыз. Осылайша, сыни секция бірден көп емес жолмен белсенді пайдаланылса, EnterCriticalSection () ++ LockCount, және :: LeaveCriticalSection () ішінде --LockCount-де дерлік деструктивті болады. Бұл өте маңызды. Бұл бір процестегі мыңдаған сыни секциялардың пайдаланылуы жүйелік ресурстардың немесе CPU уақытының айтарлықтай шығындарына алып келмейді.

RecursionCount өрісі бірдей ағыннан қайталанатын қоңыраулар санын сақтайды: EnterCriticalSection (). Шынында да, егер бірдей хабардан бірнеше рет «EnterCriticalSection» () деп атасаңыз, барлық қоңыраулар сәтті болады. Яғни. Мұнда екінші кодта: EnterCriticalSection (), және соңына дейін орындалады.

// Нить №1

void Proc1()

{

::EnterCriticalSection(&m_lock);

// ...

Proc2()

// ...

::LeaveCriticalSection(&m_lock);

}

// Все еще нить №1void Proc2()

{

::EnterCriticalSection(&m_lock);

// ...

::LeaveCriticalSection(&m_lock);

}

Шынында да, сыни бөлімдер деректерді бірнеше ағыннан қатынасудан қорғау үшін жасалған. Сол бір сынақтың бірдей сын бөлімін қайта пайдалану қатеге әкелмейді. Бұл қалыпты құбылыс. Қоңыраулардың саны :: EnterCriticalSection () және :: LeaveCriticalSection () сәйкес екеніне көз жеткізіңіз және барлығы жақсы болады.

OwningThread өрісі бөлінбеген сын бөлімдер үшін немесе меншік иесі үшін бірегей идентификатор үшін 0 мәнін қамтиды. Бұл өріс EnterCriticalSection () шақырғанда, LockCount өрісі нөлден жоғары болып шыққаннан кейін тексеріледі. OwningThread ағымдық тасқын бірегей идентификаторы сәйкес келеді болса, онда RecursionCount жай ұлғаяды және :: EnterCriticalSection () дереу қайтарады. Олай болмаған жағдайда: EnterCriticalSection () кідіртілген бөлімді иеленгенге дейін күтеді: :: LeaveCriticalSection () қажетті уақыт санын.

LockSemaphore өрісі маңызды бөлім босатылғанша күте тұрсаңыз, пайдаланылады. нөлден LockCount үлкен, және OwningThread ағымдық тасқын бірегей идентификатор сәйкес емес болса, онда күту жіп ядро ​​нысанын (іс-шара) жасайды және :: таймаут (LockSemaphore) атайды. Тақырып иесі, RecursionCount қысқарту кейін, оны тексереді, және бұл мән нөлге тең, және нөлден LockCount үлкен болса, онда ол LockSemaphore «дұрыс» пайда болмайды дейін күтіп, кем дегенде бір жіп бар екенін білдіреді. Бұл әрекетті орындау үшін, иесі тақырыпты SetEvent () деп атайды, ал күту жақтарының біреуі (тек біреуі) оянады және сыни деректерге қол жеткізеді.

Критикалық секциялармен жұмыс істеу үшін API

BOOL InitializeCriticalSection (LPCRITICAL_SECTION lpCriticalSection);

BOOL InitializeCriticalSectionAndSpinCount (LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount);

LpCriticalSection деп аталатын құрылымның өрістерін толтырыңыз. Осы функциялардың кез-келгенін шақырғаннан кейін сыни бөлім пайдалану үшін дайын.

VOID RtlInitializeCriticalSection(LPRTL_CRITICAL_SECTION pcs)

{

RtlInitializeCriticalSectionAndSpinCount(pcs, 0)

}

VOID RtlInitializeCriticalSectionAndSpinCount(

LPRTL_CRITICAL_SECTION pcs, DWORD dwSpinCount)

{

pcs->DebugInfo = NULL;

pcs->LockCount = -1;

pcs->RecursionCount = 0;

pcs->OwningThread = 0;

pcs->LockSemaphore = NULL;

pcs->SpinCount = dwSpinCount;

if (0x80000000 & dwSpinCount)

_CriticalSectionGetEvent(pcs);

}

DWORD SetCriticalSectionSpinCount (LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount);

SpinCount өрісінің мәнін орнатады және оның алдыңғы мәнін қайтарады. Еске сала кетейін, жоғары дәрежелі бит бұл сыни бөлімге қол жеткізуді күтіп отырған оқиғаны «байланыстыратын» жауап береді.

DWORD RtlSetCriticalSectionSpinCount(

LPRTL_CRITICAL_SECTION pcs, DWORD dwSpinCount)

{

DWORD dwRet = pcs->SpinCount;

pcs->SpinCount = dwSpinCount;

return dwRet;

}

VOID DeleteCriticalSection (LPCRITICAL_SECTION lpCriticalSection); Қиындықтағы бөліммен жұмыс істейтін ресурстарды босатады.

VOID RtlDeleteCriticalSection(LPRTL_CRITICAL_SECTION pcs)

{

pcs->DebugInfo = NULL;

pcs->LockCount = -1;

pcs->RecursionCount = 0;

pcs->OwningThread = 0;

if (pcs->LockSemaphore)

{

::CloseHandle(pcs->LockSemaphore);

pcs->LockSemaphore = NULL;

}

}

VOID EnterCriticalSection (LPCRITICAL_SECTION lpCriticalSection);

BOOL TryEnterCriticalSection (LPCRITICAL_SECTION lpCriticalSection);

Сыни бөлімнің «басып алуын» орындаңыз. Егер критичный бөлік басқа ағынмен алса, EnterCriticalSection () босатылғанша күтеді және :: TryEnterCriticalSection () FALSE қайтарады. Windows 9x / ME жүйесінде қол жетімді емес.

VOID RtlEnterCriticalSection(LPRTL_CRITICAL_SECTION pcs)

{

if (::InterlockedIncrement(&pcs->LockCount))

{

if (pcs->OwningThread == (HANDLE)::GetCurrentThreadId())

{

pcs->RecursionCount++;

return;

}

RtlpWaitForCriticalSection(pcs);

}

pcs->OwningThread = (HANDLE)::GetCurrentThreadId();

pcs->RecursionCount = 1;

}

BOOL RtlTryEnterCriticalSection(LPRTL_CRITICAL_SECTION pcs)

{

if (-1L == ::InterlockedCompareExchange(&pcs->LockCount, 0, -1))

{

pcs->OwningThread = (HANDLE)::GetCurrentThreadId();

pcs->RecursionCount = 1;

}

elseif (pcs->OwningThread == (HANDLE)::GetCurrentThreadId())

{

::InterlockedIncrement(&pcs->LockCount);

pcs->RecursionCount++;

}

elsereturn FALSE;

return TRUE;

}

VOID LeaveCriticalSection (LPCRITICAL_SECTION lpCriticalSection);

Сын бөлімін шығарады,

VOID RtlLeaveCriticalSectionDbg(LPRTL_CRITICAL_SECTION pcs)

{

if (--pcs->RecursionCount)

::InterlockedDecrement(&pcs->LockCount);

elseif (::InterlockedDecrement(&pcs->LockCount) >= 0)

RtlpUnWaitCriticalSection(pcs);

}

Критикалық секцияларға арналған класстар:

class CLock

{

friendclass CScopeLock;

CRITICAL_SECTION m_CS;

public:

void Init() { ::InitializeCriticalSection(&m_CS); }

void Term() { ::DeleteCriticalSection(&m_CS); }

void Lock() { ::EnterCriticalSection(&m_CS); }

BOOL TryLock() { return ::TryEnterCriticalSection(&m_CS); }

void Unlock() { ::LeaveCriticalSection(&m_CS); }

};

class CAutoLock : public CLock

{

public:

CAutoLock() { Init(); }

~CAutoLock() { Term(); }

};

class CScopeLock

{

LPCRITICAL_SECTION m_pCS;

public:

CScopeLock(LPCRITICAL_SECTION pCS) : m_pCS(pCS) { Lock(); }

CScopeLock(CLock& lock) : m_pCS(&lock.m_CS) { Lock(); }

~CScopeLock() { Unlock(); }

void Lock() { ::EnterCriticalSection(m_pCS); }

void Unlock() { ::LeaveCriticalSection(m_pCS); }

};

CLock және CAutoLock сыныптары сынып айнымалыларына қол жеткізуді синхрондау үшін пайдалы және CScopeLock, ең алдымен, процедураларда пайдалану үшін арналған. Ықтимал, компилятор жойқын жойғыш арқылы Call: LeaveCriticalSection () шақырады.

CAutoLock m_lockObject;

CObject *m_pObject;

void Proc1()

{

CScopeLock lock(m_ lockObject); // Вызов lock.Lock();if (!m_pObject)

return; // Вызов lock.Unlock();

m_pObject->SomeMethod();

// Вызов lock.Unlock();

}

Өте қызықты және қызықты жұмыс. Сіз сағаттар мен аптадарды өткізе аласыз, бірақ сіз қай жерде пайда болатындығын таба алмайсыз. Бұған ерекше назар аудару керек. Қате бөлімдермен байланысты қателер екі түрге бөлінеді: орындалу қателері және архитектуралық қателіктер.

Бұл әдетте оңай емес анықталған қателер, әдетте, сирек емес қоңыраулармен байланысты: :: EnterCriticalSection () және :: LeaveCriticalSection ().

// Процедура предполагает, что m_lockObject.Lock(); уже был вызван

void Pool()

{

for (int i = 0; i < m_vectSinks.size(); i++)

{

m_lockObject.Unlock();

m_vectSinks[i]->DoSomething();

m_lockObject.Lock();

}

}

:: LeaveCriticalSection () жоқ :: EnterCriticalSection () бірінші шақыруды әкеледі: EnterCriticalSection () мәңгі өшіруді тоқтатады.

void Proc()

{

m_lockObject.Lock();

if (!m_pObject)

return;

// ...

m_lockObject.Unlock();

}

Бұл мысалда, әрине, CScopeLock сияқты сыныпты пайдалану мағынасы бар.

Сонымен қатар, :: EnterCriticalSection () сыни бөлімін InitializeCriticalSection () арқылы баптандырусыз шақырылады. Әсіресе жиі бұл ATL көмегімен жазылған жобаларда орын алады. Debug-нұсқасында барлығы жақсы жұмыс істейді, ал шығарылым нұсқасы құлады. Бұл «минималды» CRT (_ATL_MIN_CRT) деп аталады, ол статикалық нысандардың конструкторлары деп аталмайды (Q166480, Q165076). ATL 7.0 нұсқасында бұл мәселе шешілді.

Мен де осы қатеге тап болдым: бағдарламашы CScopeLock секілді сыныпты пайдаланды, бірақ кеңістікті үнемдеу үшін осы айнымалы мәнді бір әріппен атадым:

CScopeLock l(m_lock);

және айнымалы мәнді жіберіп алмады. Шықты

CScopeLock (m_lock);

Бұл нені білдіреді? Компилятор әділетті түрде CScopeLock конструкторына қоңырау шалып, стандартты күткендей бұл дереу белгісіз нысанды жойды. Яғни. Lock () әдісін шақырғаннан кейін, Unlock () шақырылды және синхрондау тоқтатылды. Жалпы айтқанда, айнымалыларға, тіпті жергілікті тұрғындарға, бір хаттың аттары - әртүрлі рейстерге жылдам шабуылдың жолы.

Қорытынды

Сонымен критикалық секциялар туралы мыналарды білуіміз керек:

  1. Критикалық секциялар жылдам жұмыс істейді және көптеген жүйелік ресурстарды қажет етпейді.

  2. Бірнеше (тәуелсіз) айнымалыларға қол жеткізуді синхрондау үшін бір емес, бірнеше критикалық секцияларды пайдалану керек.

  3. Күрделі бөлімдерге арналған кодты оңайлатады.

  4. Критикалық секцияда болғанда, басқа объекттердің функцияларын шақырып қажет емес.

Соседние файлы в папке 777301