Amaliy mashg’ulot № 14
Mavzu: Python dasturlash tili yordamida ko’pprotsessorli ilova yaratish.
Ishdan maqsad: Talabalarda Python dasturlash tili yordamida ko’pprotsessorli ilova to’g’risida tushuncha hosil qilish, Python dasturlash tili yordamida ko’pprotsessorli ilova yaratishni o’rganish.
Nazariy qism
Ushbu maqola tajribali Python tamerlari uchun emas, ular uchun bu ilon to'pini ochish bolalar o'yinidir, aksincha, yangi qo'shilgan python uchun juda ko'p o'qish qobiliyatlarini qisqacha ko'rib chiqish.
Afsuski, Pythonda multithreading mavzusida rus tilida juda ko'p materiallar mavjud emas va masalan, GIL haqida hech narsa eshitmagan pitonchilar menga hasad qilayotgan muntazamlik bilan duch kela boshladilar. Ushbu maqolada men ko'p qirrali pythonning eng asosiy xususiyatlarini tavsiflashga, GIL nima ekanligini va u bilan qanday yashashni (yoki u holda) va boshqalarni aytib berishga harakat qilaman.
Python - dilbar dasturlash tili. U ko'plab dasturlash paradigmalarini chiroyli tarzda birlashtiradi. Dasturchi duch kelishi mumkin bo'lgan vazifalarning aksariyati bu erda osongina, nafis va ixcham hal qilinadi. Ammo bu barcha vazifalar uchun ko'pincha bitta ipli echim etarli bo'ladi va bitta dasturli dasturlar odatda oldindan taxmin qilinadigan va disk raskadrovka qilish oson. Xuddi shu narsani ko'p qirrali va ko'p ishlov beradigan dasturlar haqida aytish mumkin emas.
Ko'p tishli dasturlar
Pythonda torli modul mavjud va u erda ko'p tarmoqli dasturlash uchun zarur bo'lgan barcha narsalar mavjud: har xil turdagi qulflar, semafor va voqea mexanizmi mavjud. Bir so'z bilan aytganda, ko'p qirrali dasturlarning aksariyati uchun zarur bo'lgan hamma narsa. Bundan tashqari, ushbu vositalardan foydalanish juda oddiy. Keling, 2 ta mavzuni boshlaydigan dasturning misolini ko'rib chiqaylik. Bitta ip o'nta "0" ni, ikkinchisi o'nta "1" ni va qat'iy ravishda o'z navbatida yozadi.
import threading
def writer(x, event_for_wait, event_for_set):
for i in xrange(10):
event_for_wait.wait() # wait for event
event_for_wait.clear() # clean event for future
print x
event_for_set.set() # set event for neighbor thread
# init events
e1 = threading.Event()
e2 = threading.Event()
# init threads
t1 = threading.Thread(target=writer, args=(0, e1, e2))
t2 = threading.Thread(target=writer, args=(1, e2, e1))
# start threads
t1.start()
t2.start()
e1.set() # initiate the first event
# join threads to the main thread
t1.join()
t2.join()
import threading
def writer(x, event_for_wait, event_for_set):
for i in xrange(10):
event_for_wait.wait() # wait for event
event_for_wait.clear() # clean event for future
print x
event_for_set.set() # set event for neighbor thread
# init events
e1 = threading.Event()
e2 = threading.Event()
e3 = threading.Event()
# init threads
t1 = threading.Thread(target=writer, args=(0, e1, e2))
t2 = threading.Thread(target=writer, args=(1, e2, e3))
t3 = threading.Thread(target=writer, args=(2, e3, e1))
# start threads
t1.start()
t2.start()
t3.start()
e1.set() # initiate the first event
# join threads to the main thread
t1.join()
t2.join()
t3.join()
Biz yangi voqea, yangi mavzu qo'shdik va parametrlarni biroz o'zgartirdik
oqimlar boshlanadi (albatta, masalan, MapReduce yordamida umumiy echim yozishingiz mumkin, ammo bu ushbu maqola doirasidan tashqarida).
Ko'rib turganingizdek, hali ham sehr yo'q. Hammasi oddiy va tushunarli. Keling, oldinga boramiz.
Global tarjimonni qulflash
Iplarni ishlatishning eng keng tarqalgan ikkita sababi bor: birinchidan, zamonaviy protsessorlarning ko'p yadroli arxitekturasidan foydalanish samaradorligini oshirish va shu sababli dasturning ishlashi;
ikkinchidan, agar dastur mantig'ini parallel, to'liq yoki qisman asenkron qismlarga bo'lishimiz kerak bo'lsa (masalan, bir vaqtning o'zida bir nechta serverlarni ping qilish imkoniyatiga ega bo'lish uchun).
Birinchi holda, biz global tarjimonni qulflash (yoki qisqacha GIL) kabi Python (yoki aksincha, uning asosiy CPython dasturini) cheklashimizga duch kelmoqdamiz. GILning kontseptsiyasi shundaki, protsessor tomonidan bir vaqtning o'zida faqat bitta ipni bajarish mumkin. Bu alohida o'zgaruvchilar uchun iplar o'rtasida kurash bo'lmasligi uchun amalga oshiriladi. Bajariladigan ip butun atrof-muhitga kirish huquqini oladi. Python-da iplarni amalga oshirishning bu xususiyati iplar bilan ishlashni ancha osonlashtiradi va ma'lum bir ipning xavfsizligini ta'minlaydi.
Ammo nozik bir nuqta bor: tuyulishi mumkinki, ko'p tarmoqli dastur xuddi shu vaqtni bajaradigan bitta protsessorli dastur bilan bir xil vaqtni bajarishi yoki protsessorda har bir ish zarrachasining bajarilish vaqtining yig'indisi bo'lishi mumkin. Ammo bu erda bizni bitta noxush effekt kutmoqda. Dasturni ko'rib chiqing:
Ushbu dastur 2 ta mavzu yaratadi. Har bir satrda u alohida faylga yarim million satr "1" yozadi. Aslida, ish hajmi avvalgi dasturda bo'lgani kabi. Vaqt o'tishi bilan bu erda qiziqarli effekt olinadi. Dastur 0,7 soniyadan 7 soniyagacha ishlashi mumkin. Nima uchun bu sodir bo'layapti?
Buning sababi shundaki, ish zarrachasi CPU resursiga muhtoj bo'lmaganda, u GIL-ni chiqaradi va shu vaqtda uni, boshqa ipni va asosiy ipni olishga harakat qilishi mumkin. Shu bilan birga, operatsion tizim yadrolar ko'pligini bilib, yadrolar orasidagi iplarni taqsimlashga urinib, hamma narsani og'irlashtirishi mumkin.
UPD: hozirda Python 3.2-da GILning takomillashtirilgan amaliyoti mavjud bo'lib, unda bu muammo qisman hal qilindi, xususan, har bir ip, GILni qo'lga kiritishidan oldin qisqa vaqt kutib turishi sababli ingliz tilida yaxshi taqdimot)
"Shunday qilib, Python-da samarali ko'p qirrali dasturlar yozolmaysizmi?" Yo'q, albatta, chiqish yo'li bor, va hatto bir nechtasi.
Dasturlarni qayta ishlash
Oldingi xatboshida tasvirlangan muammoni qaysidir ma'noda hal qilish uchun Pythonda subprocess moduli mavjud. Parallel ipda bajarishni istagan dasturni yozishimiz mumkin (aslida allaqachon jarayon). Va uni boshqa dasturdagi bir yoki bir nechta mavzularda ishlating. Bu, albatta, bizning dasturimizni tezlashtiradi, chunki GIL ishga tushirgichida yaratilgan iplar yig'ilmaydi, faqat ishlash jarayoni tugashini kutadi. Biroq, bu usul juda ko'p muammolarga ega. Asosiy muammo shundaki, jarayonlar o'rtasida ma'lumotlarni uzatish qiyinlashadi. Siz qandaydir tarzda ob'ektlarni seriyalashingiz, PIPE yoki boshqa vositalar orqali aloqa o'rnatishingiz kerak edi, ammo bularning barchasi muqarrar ravishda ortiqcha xarajatlarni keltirib chiqaradi va kodni tushunish qiyin bo'ladi.
Bu erda yana bir yondashuv bizga yordam berishi mumkin. Python-da ko'p ishlov berish moduli mavjud. Funktsional jihatdan ushbu modul ipni eslatishga o'xshaydi. Masalan, muntazam funktsiyalardan jarayonlar xuddi shu tarzda yaratilishi mumkin. Jarayonlar bilan ishlash usullari deyarli tortish moduli iplari bilan bir xil. Ammo jarayonlarni sinxronlashtirish va ma'lumotlar almashinuvi uchun boshqa vositalardan foydalanish odatiy holdir. Biz navbat (Navbat) va quvurlar (Quvur) haqida gapiramiz. Biroq, bu erda qulflar, voqealar va semaforlarning analoglari ham bu erda.
Bundan tashqari, ko'p ishlov berish moduli umumiy xotira bilan ishlash mexanizmiga ega. Buning uchun modulda o'zgaruvchilar (Value) va massiv (Array) sinflari mavjud bo'lib, ularni jarayonlar o'rtasida "bo'lishish" mumkin. Umumiy o'zgaruvchilar bilan ishlash qulayligi uchun menejer sinflaridan foydalanishingiz mumkin. Ular yanada moslashuvchan va ulardan foydalanish osonroq, ammo sekinroq. Multiprosessing.sharedctypes moduli yordamida ctypes modulidan umumiy turlarni yaratish uchun yaxshi imkoniyat borligini ta'kidlash kerak.
|