Mga multithreaded na programa na may mga halimbawa. Multi-threaded na mga application para sa. Pamamahala ng Thread sa .NET

Clay Breshears

Panimula

Kasama sa mga pamamaraan ng pagpapatupad ng multithreading ng Intel ang apat na pangunahing yugto: pagsusuri, disenyo at pagpapatupad, pag-debug, at pag-tune ng pagganap. Ito ang diskarte na ginamit upang lumikha ng isang multi-threaded na application mula sa sequential code. Ang pagtatrabaho sa software sa panahon ng pagpapatupad ng una, pangatlo at ikaapat na yugto ay sakop ng malawak, habang ang impormasyon sa pagpapatupad ng ikalawang hakbang ay malinaw na hindi sapat.

Maraming mga libro ang nai-publish sa parallel algorithm at parallel computing. Gayunpaman, ang mga publikasyong ito ay pangunahing tumatalakay sa pagpasa ng mensahe, distributed memory system, o theoretical parallel computing models na kung minsan ay hindi naaangkop sa mga tunay na multi-core na platform. Kung handa ka nang maging seryoso tungkol sa multithreaded programming, malamang na kailangan mo ng kaalaman tungkol sa pagbuo ng mga algorithm para sa mga modelong ito. Siyempre, ang paggamit ng mga modelong ito ay medyo limitado, kaya maraming mga developer ng software ang maaaring kailanganin pa ring ipatupad ang mga ito sa pagsasanay.

Nang walang pagmamalabis, maaari nating sabihin na ang pagbuo ng mga multi-threaded na application ay una at pangunahin sa isang malikhaing aktibidad, at pagkatapos ay isang aktibidad na pang-agham. Sa artikulong ito, matututunan mo ang walong simpleng panuntunan na tutulong sa iyong palawakin ang iyong base ng mga parallel programming practices at pagbutihin ang kahusayan ng pagpapatupad ng thread computing sa iyong mga application.

Panuntunan 1. I-highlight ang mga operasyong isinagawa sa code ng programa nang hiwalay sa isa't isa

Ang parallel processing ay naaangkop lamang sa mga sequential code operations na iisasagawa nang hiwalay sa isa't isa. Ang isang magandang halimbawa kung paano humahantong sa isang tunay na resulta ang mga pagkilos na independyente sa isa't isa ay ang pagtatayo ng isang bahay. Kabilang dito ang mga manggagawa ng maraming specialty: mga karpintero, mga elektrisyan, mga plasterer, mga tubero, mga bubong, mga pintor, mga mason, mga landscaper, atbp. Siyempre, ang ilan sa kanila ay hindi maaaring magsimulang magtrabaho bago matapos ang iba sa kanilang trabaho (halimbawa, ang mga bubong ay hindi magsisimulang magtrabaho hanggang sa maitayo ang mga dingding, at ang mga pintor ay hindi magpipintura sa mga dingding na iyon maliban kung sila ay nakapalitada). Ngunit sa pangkalahatan maaari nating sabihin na ang lahat ng mga taong kasangkot sa konstruksiyon ay kumikilos nang nakapag-iisa sa bawat isa.

Isaalang-alang natin ang isa pang halimbawa - ang siklo ng trabaho ng isang tindahan ng pag-arkila ng DVD, na tumatanggap ng mga order para sa ilang partikular na pelikula. Ang mga order ay ipinamamahagi sa mga manggagawa sa istasyon, na naghahanap ng mga pelikulang ito sa bodega. Naturally, kung ang isa sa mga manggagawa ay kumuha ng disc mula sa bodega kung saan naitala ang isang pelikula kasama si Audrey Hepburn, hindi ito makakaapekto sa ibang manggagawa na naghahanap ng susunod na aksyon na pelikula kasama si Arnold Schwarzenegger, at tiyak na hindi makakaapekto sa kanilang kasamahan na naghahanap ng mga disc na may bagong season ng Friends. Sa aming halimbawa, ipinapalagay namin na ang lahat ng out-of-stock na isyu ay nalutas na bago dumating ang mga order sa lokasyon ng rental, at ang packaging at pagpapadala ng anumang order ay hindi makakaapekto sa pagproseso ng iba.

Sa iyong trabaho, malamang na makakatagpo ka ng mga kalkulasyon na maaari lamang iproseso sa isang tiyak na pagkakasunud-sunod, at hindi kahanay, dahil ang iba't ibang mga pag-ulit o hakbang ng loop ay nakasalalay sa isa't isa at dapat na maisagawa sa isang mahigpit na pagkakasunud-sunod. Kumuha tayo ng isang buhay na halimbawa mula sa ligaw. Isipin ang isang buntis na usa. Dahil ang pagbubuntis ay tumatagal sa average na walong buwan, kahit anong tingin mo dito, hindi lilitaw ang usa sa isang buwan, kahit na ang walong usa ay mabuntis ng sabay. Gayunpaman, ang walong reindeer nang sabay-sabay ay gagawa ng isang mahusay na trabaho kung gagamitin mo silang lahat sa sleigh ni Santa Claus.

Panuntunan 2: Ilapat ang concurrency sa mababang antas ng granularity

Mayroong dalawang diskarte sa parallel partitioning ng sequential program code: bottom-up at top-down. Una, sa yugto ng pagsusuri ng code, natukoy ang mga segment ng code (tinatawag na "hot" spot), na tumatagal ng malaking bahagi ng oras ng pagpapatupad ng programa. Ang paghihiwalay ng mga segment ng code na ito nang magkatulad (kung maaari) ay magbibigay ng pinakamalaking pakinabang sa pagganap.

Ang bottom-up approach ay nagpapatupad ng multi-threaded processing ng code hot spots. Kung hindi posible ang parallel partitioning ng mga nahanap na punto, kailangan mong suriin ang call stack ng application upang matukoy ang iba pang mga segment na available para sa parallel partitioning at tumatakbo nang mahabang panahon. Sabihin nating gumagawa ka ng isang application na nag-compress ng mga graphics. Maaaring ipatupad ang compression gamit ang ilang independiyenteng parallel na mga thread na nagpoproseso ng mga indibidwal na segment ng imahe. Gayunpaman, kahit na pinamamahalaan mong ipatupad ang mga multi-threading hot spot, huwag pabayaan ang pagsusuri ng call stack, bilang resulta kung saan makakahanap ka ng mga segment na magagamit para sa parallel division, na matatagpuan sa mas mataas na antas ng code ng programa. Sa ganitong paraan maaari mong pataasin ang granularity ng parallel processing.

Sa top-down na diskarte, ang gawain ng code ng programa ay nasuri, at ang mga indibidwal na mga segment nito ay natukoy, ang pagpapatupad nito ay humahantong sa pagkumpleto ng buong gawain. Kung ang mga pangunahing segment ng code ay hindi malinaw na independyente, suriin ang kanilang mga bahagi ng bahagi upang maghanap ng mga independiyenteng kalkulasyon. Sa pamamagitan ng pagsusuri sa iyong code, matutukoy mo ang mga module ng code na tumatagal ng pinakamaraming oras ng CPU upang maipatupad. Tingnan natin ang pagpapatupad ng threading sa isang application na idinisenyo para sa pag-encode ng video. Maaaring ipatupad ang parallel processing sa pinakamababang antas - para sa mga independiyenteng pixel ng isang frame, o sa mas mataas na antas - para sa mga grupo ng mga frame na maaaring maproseso nang hiwalay sa iba pang mga grupo. Kung ang application ay ginawa upang iproseso ang maramihang mga video file nang sabay-sabay, ang parallel division sa antas na ito ay maaaring maging mas simple, at ang detalye ay nasa pinakamababang antas.

Ang parallel computation granularity ay tumutukoy sa dami ng computation na dapat gawin bago ang pag-synchronize sa pagitan ng mga thread. Sa madaling salita, ang hindi gaanong madalas na pag-synchronize ay nangyayari, mas mababa ang antas ng detalye. Ang threaded computing sa isang mataas na granularity ay maaaring maging sanhi ng overhead ng system na nauugnay sa pag-aayos ng mga thread na lumampas sa dami ng kapaki-pakinabang na pag-compute na ginawa ng mga thread na iyon. Ang pagtaas ng bilang ng mga thread na may pare-parehong halaga ng pagtutuos ay nagpapalubha sa proseso ng pagproseso. Ang low-granularity multithreading ay nagdudulot ng mas kaunting latency ng system at may mas malaking potensyal para sa scalability, na maaaring makamit sa pamamagitan ng pagpapakilala ng mga karagdagang thread. Upang ipatupad ang parallel processing sa mababang granularity, inirerekomendang gumamit ng top-down na diskarte at ayusin ang mga thread sa mataas na antas ng call stack.

Panuntunan 3: Bumuo ng scalability sa iyong code para tumaas ang performance nito habang dumarami ang bilang ng mga core.

Hindi pa katagal, bilang karagdagan sa mga dual-core na processor, lumitaw ang mga quad-core processor sa merkado. Bukod dito, inihayag na ng Intel ang paglikha ng isang processor na may 80 core, na may kakayahang magsagawa ng isang trilyong floating point operations kada segundo. Dahil ang bilang ng mga core sa mga processor ay tataas lamang sa paglipas ng panahon, ang iyong code ay dapat na may sapat na potensyal na scalability. Ang scalability ay isang parameter kung saan mahuhusgahan ng isang tao ang kakayahan ng isang application na sapat na tumugon sa mga pagbabago tulad ng pagtaas sa mga mapagkukunan ng system (bilang ng mga core, laki ng memorya, dalas ng bus, atbp.) o pagtaas ng dami ng data. Isinasaalang-alang na ang bilang ng mga core sa hinaharap na mga processor ay tataas, sumulat ng scalable code na magpapataas ng pagganap dahil sa pagtaas ng mga mapagkukunan ng system.

Upang i-paraphrase ang isa sa mga batas ng C. Northecote Parkinson, maaari nating sabihin na "sinasakop ng pagproseso ng data ang lahat ng magagamit na mapagkukunan ng system." Nangangahulugan ito na habang dumarami ang mga mapagkukunan sa pag-compute (tulad ng bilang ng mga core), lahat ng ito ay malamang na gagamitin para sa pagproseso ng data. Bumalik tayo sa application ng video compression na tinalakay sa itaas. Ang hitsura ng karagdagang mga core ng processor ay malamang na hindi makakaapekto sa laki ng mga naprosesong frame - sa halip, ang bilang ng mga thread na nagpoproseso ng frame ay tataas, na hahantong sa pagbaba sa bilang ng mga pixel bawat thread. Bilang isang resulta, dahil sa organisasyon ng mga karagdagang thread, ang halaga ng overhead ay tataas, at ang antas ng paralelismo ay bababa. Ang isa pang mas malamang na senaryo ay ang pagtaas sa laki o bilang ng mga video file na kailangang ma-encode. Sa kasong ito, ang pag-aayos ng mga karagdagang thread na magpoproseso ng mas malalaking (o karagdagang) mga video file ay magbibigay-daan sa iyong hatiin ang buong dami ng trabaho nang direkta sa yugto kung saan naganap ang pagtaas. Sa turn, ang isang application na may ganitong mga kakayahan ay magkakaroon ng mataas na potensyal para sa scalability.

Ang pagdidisenyo at pagpapatupad ng parallel processing gamit ang data decomposition ay nagbibigay ng mas mataas na scalability kumpara sa paggamit ng functional decomposition. Ang bilang ng mga independiyenteng function sa program code ay kadalasang limitado at hindi nagbabago sa panahon ng pagpapatupad ng application. Dahil ang bawat independiyenteng pag-andar ay inilalaan ng isang hiwalay na thread (at, nang naaayon, isang core ng processor), pagkatapos ay sa isang pagtaas sa bilang ng mga core, ang dagdag na organisadong mga thread ay hindi magiging sanhi ng pagtaas ng pagganap. Kaya, ang mga parallel partitioning model na may data decomposition ay magbibigay ng mas mataas na potensyal para sa scalability ng application dahil sa katotohanan na sa pagtaas ng bilang ng mga core ng processor, tataas ang dami ng naprosesong data.

Kahit na ang program code ay nag-organisa ng sinulid na pagproseso ng mga independiyenteng function, malamang na ang mga karagdagang thread ay maaaring gamitin na inilunsad kapag tumaas ang input load. Bumalik tayo sa halimbawa ng pagtatayo ng bahay na tinalakay sa itaas. Ang natatanging layunin ng konstruksiyon ay upang makumpleto ang isang limitadong bilang ng mga independiyenteng gawain. Gayunpaman, kung inutusan kang magtayo ng dalawang beses sa dami ng mga palapag, malamang na gusto mong kumuha ng mga karagdagang manggagawa sa ilang mga specialty (pintor, roofer, tubero, atbp.). Samakatuwid, kailangan mong bumuo ng mga application na maaaring umangkop sa data decomposition na nangyayari bilang isang resulta ng pagtaas ng workload. Kung ang iyong code ay nagpapatupad ng functional decomposition, isaalang-alang ang pag-aayos ng mga karagdagang thread habang dumarami ang bilang ng mga core ng processor.

Panuntunan 4: Gumamit ng mga library na ligtas sa thread

Kung maaaring kailanganin mo ang isang library upang mahawakan ang data sa mga hot spot sa iyong code, siguraduhing isaalang-alang ang paggamit ng mga pre-built na function sa halip na ang iyong sariling code. Sa madaling salita, huwag subukang muling likhain ang gulong sa pamamagitan ng pagbuo ng mga segment ng code na ang functionality ay naibigay na sa mga naka-optimize na pamamaraan ng library. Maraming library, kabilang ang Intel® Math Kernel Library (Intel® MKL) at Intel® Integrated Performance Primitives (Intel® IPP), ay naglalaman na ng mga multi-threaded function na na-optimize para sa mga multi-core na processor.

Kapansin-pansin na kapag gumagamit ng mga pamamaraan mula sa mga multithreaded na aklatan, dapat mong tiyakin na ang pagtawag sa isang partikular na aklatan ay hindi makakaapekto sa normal na operasyon ng mga thread. Ibig sabihin, kung ang mga procedure call ay ginawa mula sa dalawang magkaibang thread, ang bawat tawag ay dapat magbalik ng mga tamang resulta. Kung ang mga pamamaraan ay nag-a-access at nag-a-update ng mga nakabahaging variable ng library, maaaring magkaroon ng "data race," na magkakaroon ng masamang epekto sa pagiging maaasahan ng mga resulta ng pagkalkula. Upang gumana nang tama sa mga thread, ang pamamaraan ng library ay idinagdag bilang bago (iyon ay, hindi ito nag-a-update ng anuman maliban sa mga lokal na variable) o naka-synchronize upang maprotektahan ang access sa mga nakabahaging mapagkukunan. Konklusyon: bago gumamit ng anumang third-party na library sa iyong program code, basahin ang dokumentasyong nakalakip dito upang matiyak na gumagana ito nang tama sa mga thread.

Panuntunan 5: Gumamit ng naaangkop na modelo ng threading

Sabihin nating ang mga function mula sa mga multithreaded na aklatan ay malinaw na hindi sapat upang hatiin ang lahat ng nauugnay na mga segment ng code nang magkatulad, at kailangan mong mag-isip tungkol sa pag-aayos ng mga thread. Huwag magmadali upang lumikha ng iyong sariling (mahirap) threading structure kung ang OpenMP library ay naglalaman na ng lahat ng functionality na kailangan mo.

Ang kawalan ng tahasang multithreading ay ang kawalan ng kakayahang tumpak na kontrolin ang mga thread.

Kung kailangan mo lang ng parallel separation ng resource-intensive loops, o ang karagdagang flexibility na ibinibigay ng tahasang mga thread ay pangalawang kahalagahan sa iyo, kung gayon sa kasong ito ay walang saysay na gumawa ng karagdagang trabaho. Kung mas kumplikado ang pagpapatupad ng multithreading, mas malaki ang posibilidad ng mga error sa code at mas mahirap ang kasunod na pagbabago nito.

Nakatuon ang OpenMP library sa decomposition ng data at partikular na angkop para sa sinulid na pagproseso ng mga loop na gumagana sa malaking halaga ng impormasyon. Sa kabila ng katotohanan na ang data decomposition lamang ang naaangkop sa ilang mga application, kinakailangang isaalang-alang ang mga karagdagang kinakailangan (halimbawa, isang employer o customer), ayon sa kung saan ang paggamit ng OpenMP ay hindi katanggap-tanggap at nananatili itong ipatupad ang multithreading gamit ang mga tahasang pamamaraan. . Sa kasong ito, maaaring gamitin ang OpenMP sa pre-thread upang matantya ang mga potensyal na nadagdag sa performance, scalability, at ang tinantyang pagsisikap na kinakailangan upang kasunod na hatiin ang code gamit ang tahasang threading.

Rule 6. Ang resulta ng program code ay hindi dapat nakadepende sa execution sequence ng parallel threads

Para sa sequential code, kailangan mo lang tukuyin ang isang expression na isasagawa pagkatapos ng anumang iba pang expression. Sa multi-threaded code, ang pagkakasunud-sunod ng pagpapatupad ng mga thread ay hindi tinukoy at depende sa mga tagubilin ng operating system scheduler. Sa mahigpit na pagsasalita, halos imposible na mahulaan ang pagkakasunud-sunod ng mga thread na ilulunsad upang magsagawa ng anumang operasyon, o upang matukoy kung aling thread ang ilulunsad ng scheduler sa kasunod na sandali. Pangunahing ginagamit ang hula upang bawasan ang latency ng application, lalo na kapag tumatakbo sa isang platform na may processor na may mas kaunting mga core kaysa sa mga thread. Kung na-block ang isang thread dahil kailangan nitong i-access ang isang lugar na hindi nakasulat sa cache o dahil kailangan nitong magsagawa ng kahilingan sa pagpapatakbo ng I/O, sususpindihin ito ng scheduler at magsisimula ng thread na handang tumakbo.

Ang direktang resulta ng kawalan ng katiyakan sa pag-iiskedyul ng thread ay ang mga sitwasyon ng data race. Ang pagpapalagay na babaguhin ng isang thread ang halaga ng isang nakabahaging variable bago basahin ng isa pang thread ang value na iyon ay maaaring mali. Sa swerte, ang pagkakasunud-sunod ng pagpapatupad ng mga thread para sa isang partikular na platform ay mananatiling pareho sa lahat ng pagpapatakbo ng application. Gayunpaman, ang mga maliliit na pagbabago sa estado ng system (halimbawa, ang lokasyon ng data sa hard drive, bilis ng memorya, o kahit na isang paglihis mula sa nominal na dalas ng AC ng power supply) ay maaaring mag-trigger ng ibang pagkakasunud-sunod ng pagpapatupad ng thread. Kaya, para sa code ng programa na gumagana lamang nang tama sa isang tiyak na pagkakasunud-sunod ng mga thread, malamang ang mga problemang nauugnay sa mga sitwasyon ng lahi ng data at mga deadlock.

Mula sa pananaw ng pagganap, mas mainam na huwag limitahan ang pagkakasunud-sunod kung saan ang mga thread ay pinaandar. Ang isang mahigpit na pagkakasunud-sunod ng pagpapatupad ng thread ay pinapayagan lamang sa mga kaso ng matinding pangangailangan, na tinutukoy ng isang paunang natukoy na pamantayan. Kung mangyari ang mga ganitong pangyayari, ang mga thread ay ilulunsad sa pagkakasunud-sunod na tinukoy ng ibinigay na mga mekanismo ng pag-synchronize. Halimbawa, isipin ang dalawang magkaibigan na nagbabasa ng isang pahayagan na inilatag sa mesa. Una, nakakabasa sila sa iba't ibang bilis, at pangalawa, nakakabasa sila ng iba't ibang artikulo. At narito, hindi mahalaga kung sino ang unang nagbabasa ng pahayagan na kumalat - sa anumang kaso, kailangan niyang maghintay para sa kanyang kaibigan bago buksan ang pahina. Kasabay nito, walang mga paghihigpit sa oras o pagkakasunud-sunod ng pagbabasa ng mga artikulo - ang mga kaibigan ay nagbabasa sa anumang bilis, at ang pag-synchronize sa pagitan ng mga ito ay nangyayari kaagad kapag binubuksan ang pahina.

Panuntunan 7: Gumamit ng stream ng lokal na storage. Kung kinakailangan, magtalaga ng mga kandado sa mga indibidwal na lugar ng data

Ang pag-synchronize ay hindi maaaring hindi madagdagan ang pag-load sa system, na sa anumang paraan ay nagpapabilis sa proseso ng pagkuha ng mga resulta ng parallel na mga kalkulasyon, ngunit tinitiyak ang kanilang kawastuhan. Oo, kailangan ang pag-synchronize, ngunit hindi ito dapat abusuhin. Upang mabawasan ang pag-synchronize, ginagamit ang lokal na imbakan ng thread o inilalaan na mga lugar ng memorya (halimbawa, mga elemento ng array na minarkahan ng mga identifier ng kaukulang mga thread).

Ang pangangailangan na magbahagi ng mga pansamantalang variable sa pagitan ng iba't ibang mga thread ay lumitaw na medyo bihira. Ang ganitong mga variable ay dapat na ipahayag o ilaan nang lokal sa bawat thread. Ang mga variable na ang mga halaga ay mga intermediate na resulta ng thread execution ay dapat ding ideklarang lokal sa kaukulang mga thread. Kakailanganin ang pag-synchronize upang ibuod ang mga intermediate na resulta sa ilang karaniwang lugar ng memorya. Upang mabawasan ang posibleng stress sa system, mas mainam na i-update ang pangkalahatang lugar na ito nang madalang hangga't maaari. Ang mga tahasang pamamaraan ng multithreading ay nagbibigay ng mga thread-local na storage API na nagtitiyak ng integridad ng lokal na data mula sa simula ng isang multithreaded na segment ng code hanggang sa susunod (o mula sa isang threaded function na tawag hanggang sa susunod na pagpapatupad ng parehong function na iyon).

Kung hindi posible ang lokal na imbakan ng thread, ang pag-access sa mga nakabahaging mapagkukunan ay naka-synchronize gamit ang iba't ibang mga bagay, tulad ng mga lock. Mahalagang tama na magtalaga ng mga kandado sa mga partikular na bloke ng data, na pinakamadaling gawin kung ang bilang ng mga kandado ay katumbas ng bilang ng mga bloke ng data. Ang nag-iisang mekanismo ng pag-lock na nag-synchronize ng access sa maraming mga rehiyon ng memorya ay ginagamit lamang kapag ang lahat ng mga rehiyong ito ay naninirahan sa parehong kritikal na seksyon ng code ng programa.

Ano ang dapat mong gawin kung kailangan mong i-synchronize ang access sa isang malaking halaga ng data, halimbawa, isang array na binubuo ng 10,000 elemento? Ang pag-aayos ng isang solong lock para sa buong array ay malamang na lumikha ng isang bottleneck sa application. Kailangan ba talaga nating ayusin ang locking para sa bawat elemento nang hiwalay? Pagkatapos, kahit na 32 o 64 na magkatulad na mga thread ang nag-access sa data, kakailanganin mong pigilan ang mga salungatan sa pag-access sa isang medyo malaking lugar ng memorya, at ang posibilidad ng gayong mga salungatan na nagaganap ay 1%. Sa kabutihang palad, mayroong isang uri ng ginintuang ibig sabihin, ang tinatawag na "modulo lock". Kung gagamitin ang N modulo lock, ang bawat lock ay magsi-synchronize ng access sa Nth na bahagi ng kabuuang lugar ng data. Halimbawa, kung nakaayos ang dalawang ganoong kandado, pipigilan ng isa sa mga ito ang pag-access sa kahit na mga elemento ng array, at pipigilan ng pangalawa ang pag-access sa mga kakaibang elemento. Sa kasong ito, ang mga thread, ang pag-access sa kinakailangang elemento, ay tinutukoy ang pagkakapare-pareho nito at itakda ang naaangkop na lock. Ang bilang ng mga modulo lock ay pinili na isinasaalang-alang ang bilang ng mga thread at ang posibilidad ng sabay-sabay na pag-access ng ilang mga thread sa parehong lugar ng memorya.

Tandaan na ang sabay-sabay na paggamit ng maraming mekanismo ng pagla-lock ay hindi pinapayagan na i-synchronize ang access sa isang lugar ng memorya. Alalahanin natin ang batas ni Segal: “Alam ng taong may isang relo kung anong oras na. Ang isang tao na may kaunting mga relo ay hindi sigurado sa anumang bagay." Ipagpalagay na ang pag-access sa isang variable ay kinokontrol ng dalawang magkaibang lock. Sa kasong ito, ang unang lock ay maaaring gamitin ng isang code segment, at ang pangalawa ng isa pang segment. Pagkatapos ay makikita ng mga thread na nagsasagawa ng mga segment na ito ang kanilang mga sarili sa isang sitwasyon ng lahi para sa nakabahaging data na sabay nilang ina-access.

Panuntunan 8. Baguhin ang algorithm ng software kung kinakailangan upang ipatupad ang multithreading

Ang criterion para sa pagsusuri sa pagganap ng mga application, parehong sequential at parallel, ay execution time. Ang asymptotic order ay angkop bilang isang pagtatantya ng algorithm. Gamit ang teoretikal na tagapagpahiwatig na ito, halos palaging posible na suriin ang pagganap ng isang aplikasyon. Ibig sabihin, lahat ng iba pang bagay ay pantay, ang isang application na may growth rate na O(n log n) (mabilis na pag-uuri) ay tatakbo nang mas mabilis kaysa sa isang application na may growth rate na O(n2) (selective sort), bagama't ang mga resulta ng ang mga application na ito ay pareho.

Kung mas mahusay ang asymptotic execution order, mas mabilis na tumatakbo ang parallel application. Gayunpaman, kahit na ang pinaka-produktibong sequential algorithm ay hindi palaging nahahati sa parallel na mga thread. Kung napakahirap hatiin ang isang program hotspot, at walang paraan para ipatupad ang multithreading sa mas mataas na antas ng call stack ng hotspot, dapat mo munang isaalang-alang ang paggamit ng ibang sequential algorithm na mas madaling hatiin kaysa sa orihinal. Siyempre, may iba pang mga paraan upang maghanda ng code ng programa para sa pagproseso ng thread.

Upang ilarawan ang huling pahayag, isaalang-alang ang pagpaparami ng dalawang square matrice. Ang algorithm ni Strassen ay may isa sa mga pinakamahusay na asymptotic execution order: O(n2.81), na mas mahusay kaysa sa O(n3) order ng ordinaryong triple nested loop algorithm. Ayon sa algorithm ni Strassen, ang bawat matrix ay nahahati sa apat na submatrices, pagkatapos nito pitong recursive na tawag ang ginawa upang i-multiply ang n/2 × n/2 submatrices. Para iparallelize ang mga recursive na tawag, maaari kang gumawa ng bagong thread na magkakasunod na magsasagawa ng pitong independiyenteng submatrix multiplications hanggang sa maabot nila ang isang partikular na laki. Sa kasong ito, ang bilang ng mga thread ay tataas nang husto, at ang granularity ng mga kalkulasyon na ginagawa ng bawat bagong nabuong thread ay tataas habang ang laki ng mga submatrice ay bumababa. Isaalang-alang natin ang isa pang pagpipilian - pag-aayos ng isang pool ng pitong mga thread na gumagana nang sabay-sabay at nagsasagawa ng isang multiplikasyon ng mga submatrice. Kapag ang thread pool ay tapos nang tumakbo, ang Strassen method ay tinatawag na recursively para i-multiply ang mga submatrice (tulad ng sa sequential na bersyon ng code). Kung ang system na nagpapatakbo ng naturang programa ay may higit sa walong mga core ng processor, ang ilan sa mga ito ay magiging idle.

Ang matrix multiplication algorithm ay mas madaling iparallelize gamit ang isang triple nested loop. Sa kasong ito, ginagamit ang decomposition ng data, kung saan ang mga matrice ay nahahati sa mga row, column o submatrice, at ang bawat thread ay nagsasagawa ng ilang mga kalkulasyon. Ang pagpapatupad ng naturang algorithm ay isinasagawa gamit ang OpenMP pragmas na ipinasok sa ilang antas ng loop, o sa pamamagitan ng tahasang pag-aayos ng mga thread na nagsasagawa ng matrix division. Upang maipatupad ang mas simpleng sequential algorithm na ito, mas kaunting mga pagbabago sa program code ang kakailanganin kumpara sa pagpapatupad ng multi-threaded na Strassen algorithm.

Kaya, ngayon alam mo na ang walong simpleng panuntunan para sa epektibong pag-convert ng sequential program code sa parallel. Sa pamamagitan ng pagsunod sa mga panuntunang ito, gagawa ka ng mga multi-threaded na solusyon nang mas mabilis na nagpapataas ng pagiging maaasahan, pinakamainam na pagganap at mas kaunting mga bottleneck.

Upang bumalik sa web page ng mga tutorial sa Multithreaded Programming, pumunta sa

Aling paksa ang nagtataas ng pinakamaraming tanong at kahirapan para sa mga nagsisimula? Nang tanungin ko ang guro at Java programmer na si Alexander Pryakhin tungkol dito, agad niyang sinagot: "Multithreading." Salamat sa kanya para sa ideya at tulong sa paghahanda ng artikulong ito!

Susuriin natin ang panloob na mundo ng isang application at ang mga proseso nito, alamin kung ano ang multithreading, kung kailan ito kapaki-pakinabang, at kung paano ito ipatupad gamit ang Java bilang isang halimbawa. Kung nag-aaral ka ng isa pang wikang OOP, huwag mag-alala: pareho ang mga pangunahing prinsipyo.

Tungkol sa mga stream at mga pinagmumulan ng mga ito

Upang maunawaan ang multithreading, unawain muna natin kung ano ang isang proseso. Ang proseso ay isang piraso ng virtual memory at mga mapagkukunan na inilalaan ng OS upang magpatakbo ng isang programa. Kung magbubukas ka ng ilang pagkakataon ng isang application, maglalaan ang system ng proseso para sa bawat isa. Sa mga modernong browser, maaaring maging responsable ang isang hiwalay na proseso para sa bawat tab.

Marahil ay nakatagpo ka na ng Windows "Task Manager" (sa Linux ito ang "System Monitor") at alam mo na ang mga hindi kinakailangang proseso ng pagpapatakbo ay naglo-load sa system, at ang pinakamabigat sa kanila ay madalas na nag-freeze, kaya kailangan nilang wakasan nang puwersahan.

Ngunit mahilig ang mga user sa multitasking: huwag silang pakainin ng tinapay - hayaan silang magbukas ng isang dosenang bintana at tumalon pabalik-balik. Mayroong isang dilemma: kailangan mong tiyakin ang sabay-sabay na operasyon ng mga aplikasyon at sa parehong oras bawasan ang pagkarga sa system upang hindi ito bumagal. Sabihin nating hindi makakasabay ang hardware sa mga pangangailangan ng mga may-ari - kailangang malutas ang isyu sa antas ng software.

Gusto naming makapagsagawa ang processor ng higit pang mga command at magproseso ng mas maraming data sa bawat yunit ng oras. Iyon ay, kailangan nating magkasya ng mas maraming nai-execute na code sa bawat hiwa ng oras. Isipin ang isang yunit ng pagpapatupad ng code bilang isang bagay - ito ay isang thread.

Mas madaling lapitan ang isang kumplikadong gawain kung hahatiin mo ito sa ilang mga simpleng gawain. Ang parehong ay totoo kapag nagtatrabaho sa memorya: ang isang "mabigat" na proseso ay nahahati sa mga thread na kumukuha ng mas kaunting mga mapagkukunan at naghahatid ng code sa computer nang mas mabilis (tingnan sa ibaba kung paano eksakto).

Ang bawat aplikasyon ay may hindi bababa sa isang proseso, at ang bawat proseso ay may hindi bababa sa isang thread, na tinatawag na pangunahing thread at kung saan inilulunsad ang mga bago kung kinakailangan.

Pagkakaiba sa pagitan ng mga thread at proseso

    Ang mga thread ay gumagamit ng memorya na inilaan para sa isang proseso, at ang mga proseso ay nangangailangan ng isang hiwalay na espasyo sa memorya. Samakatuwid, ang mga thread ay nilikha at winakasan nang mas mabilis: ang system ay hindi kailangang maglaan ng bagong puwang ng address sa kanila sa bawat oras at pagkatapos ay ilabas ito.

    Pinoproseso ang bawat trabaho gamit ang kanilang sariling data - maaari silang makipagpalitan ng isang bagay lamang sa pamamagitan ng mekanismo ng interprocess na interaksyon. Direktang ina-access ng mga thread ang data at mapagkukunan ng isa't isa: kung ano ang pagbabago ay agad na magagamit ng lahat. Maaaring kontrolin ng isang thread ang "mga kapatid" nito sa proseso, habang ang isang proseso ay kumokontrol sa "mga anak na babae" nito. Samakatuwid, ang paglipat sa pagitan ng mga stream ay mas mabilis at ang komunikasyon sa pagitan ng mga ito ay mas madaling organisado.

Ano ang konklusyon? Kung kailangan mong iproseso ang isang malaking halaga ng data sa lalong madaling panahon, hatiin ito sa mga tipak na maaaring iproseso ng magkahiwalay na mga thread, at pagkatapos ay pagsamahin ang resulta. Ito ay mas mahusay kaysa sa paglikha ng mga prosesong gutom sa mapagkukunan.

Ngunit bakit ang sikat na application tulad ng Firefox ay napupunta sa ruta ng paglikha ng maraming proseso? Dahil ito ay para sa browser na ang nakahiwalay na operasyon ng mga tab ay maaasahan at nababaluktot. Kung may mali sa isang proseso, hindi kinakailangan na wakasan ang buong programa - posible na i-save ang hindi bababa sa bahagi ng data.

Ano ang multithreading

Ngayon ay dumating tayo sa pangunahing bagay. Ang multithreading ay kapag ang proseso ng aplikasyon ay nahahati sa mga thread na pinoproseso nang magkatulad - sa isang yunit ng oras - ng processor.

Ang pag-load ng computing ay ipinamamahagi sa dalawa o higit pang mga core upang ang interface at iba pang mga bahagi ng programa ay hindi bumagal sa isa't isa.

Ang mga multi-threaded na application ay maaari ding patakbuhin sa mga single-core na processor, ngunit pagkatapos ay ang mga thread ay isinasagawa sa turn: ang una ay nagtrabaho, ang estado nito ay nai-save - ang pangalawa ay pinahintulutang gumana, ang mga thread ay nai-save - bumalik sila sa una o inilunsad ang pangatlo, atbp.

Nagrereklamo ang mga busy na dalawa lang ang kamay nila. Ang mga proseso at programa ay maaaring magkaroon ng maraming mga kamay kung kinakailangan upang makumpleto ang isang gawain sa lalong madaling panahon.

Maghintay para sa signal: pag-synchronize sa mga multi-threaded na application

Isipin ang maraming mga thread na sinusubukang baguhin ang parehong lugar ng data sa parehong oras. Kaninong mga pagbabago ang sa huli ay tatanggapin at kaninong mga pagbabago ang mababaligtad? Upang maiwasan ang pagkalito kapag nagtatrabaho sa mga nakabahaging mapagkukunan, kailangang i-coordinate ng mga thread ang kanilang mga aksyon. Para magawa ito, nagpapalitan sila ng impormasyon gamit ang mga signal. Ang bawat thread ay nagsasabi sa iba kung ano ang ginagawa nito ngayon at kung ano ang mga pagbabagong aasahan. Sa ganitong paraan, ang data mula sa lahat ng mga thread tungkol sa kasalukuyang estado ng mga mapagkukunan ay naka-synchronize.

Mga pangunahing tool sa pag-synchronize

Mutual exclusion (mutual exclusion, abbreviated as mutex) - isang "bandila" na pumasa sa thread na kasalukuyang may karapatang magtrabaho sa mga nakabahaging mapagkukunan. Pinipigilan ang iba pang mga thread na ma-access ang sinasakop na lugar ng memorya. Maaaring may ilang mga mutex sa isang application, at maaari silang ibahagi sa pagitan ng mga proseso. May catch: pinipilit ng mutex ang application na i-access ang kernel ng operating system sa bawat oras, na mahal.

Semaphore - nagbibigay-daan sa iyong limitahan ang bilang ng mga thread na nag-a-access sa isang mapagkukunan sa isang naibigay na sandali. Babawasan nito ang pag-load ng CPU kapag nag-execute ng code na may mga bottleneck. Ang problema ay ang pinakamainam na bilang ng mga thread ay nakasalalay sa makina ng gumagamit.

Kaganapan - tukuyin mo ang isang kundisyon sa paglitaw kung saan ang kontrol ay ililipat sa nais na thread. Ang mga thread ay nagpapalitan ng data tungkol sa mga kaganapan upang bumuo at lohikal na ipagpatuloy ang mga aksyon ng bawat isa. Ang isa ay nakatanggap ng data, ang isa ay sinuri ang kawastuhan nito, ang pangatlo ay nai-save ito sa hard drive. Ang mga kaganapan ay nag-iiba sa kung paano sila sinenyasan. Kung kailangan mong abisuhan ang ilang mga thread tungkol sa isang kaganapan, kakailanganin mong manu-manong itakda ang function ng pagkansela upang ihinto ang signal. Kung mayroon lamang isang target na thread, maaari kang lumikha ng isang kaganapan na may awtomatikong pag-reset. Ihihinto nito ang signal mismo pagkatapos nitong maabot ang stream. Para sa flexible na kontrol sa daloy, maaaring i-queue ang mga kaganapan.

Kritikal na seksyon - isang mas kumplikadong mekanismo na pinagsasama ang isang loop counter at isang semaphore. Ang counter ay nagbibigay-daan sa iyo na antalahin ang pagsisimula ng semaphore para sa nais na oras. Ang kalamangan ay ang kernel ay ginagamit lamang kung ang seksyon ay abala at ang semaphore ay kailangang i-on. Ang natitirang oras ay tumatakbo ang thread sa user mode. Sa kasamaang palad, ang seksyon ay magagamit lamang sa loob ng isang proseso.

Paano Magpapatupad ng Multithreading sa Java

Ang klase ng Thread ay responsable para sa pagtatrabaho sa mga thread sa Java. Upang lumikha ng isang bagong thread upang magsagawa ng isang gawain ay nangangahulugang lumikha ng isang halimbawa ng klase ng Thread at iugnay ito sa nais na code. Magagawa ito sa dalawang paraan:

    subclass na Thread;

    ipatupad ang Runnable interface sa iyong klase, at pagkatapos ay ipasa ang mga instance ng klase sa Thread constructor.

Bagama't hindi namin talakayin ang paksa ng mga deadlock na sitwasyon, kapag ang mga thread ay humarang sa trabaho ng isa't isa at nag-freeze, iiwan namin ito para sa susunod na artikulo Ngayon ay magpatuloy tayo sa pagsasanay.

Halimbawa ng multithreading sa Java: ping pong na may mga mutex

Kung sa tingin mo ay may mangyayaring kakila-kilabot, huminga ka. Isasaalang-alang namin ang pagtatrabaho sa mga bagay sa pag-synchronize halos sa isang form ng laro: dalawang thread ang ililipat ng mutex Ngunit sa esensya, makikita mo ang isang tunay na aplikasyon kung saan sa isang pagkakataon ay isang thread lamang ang maaaring magproseso ng pampublikong data.

Una, gumawa tayo ng klase na nagmamana ng mga katangian ng Thread na kilala na natin, at magsulat ng "kickBall" na paraan:

Ang pampublikong klase ng PingPongThread ay nagpapalawak ng Thread( PingPongThread(String name)( this.setName(name); // override the thread name ) @Override public void run() ( Ball ball = Ball.getBall(); while(ball.isInGame() ) ( kickBall(ball); ) ) private void kickBall(Ball ball) ( if(!ball.getSide().equals(getName()))( ball.kick(getName()); ) ) ) )

Ngayon ay alagaan natin ang bola. Ang atin ay hindi magiging simple, ngunit hindi malilimutan: upang masabi nito kung sino ang tumama dito, mula saang panig at kung gaano karaming beses. Upang gawin ito, gumagamit kami ng isang mutex: ito ay mangolekta ng impormasyon tungkol sa gawain ng bawat thread - ito ay magpapahintulot sa mga nakahiwalay na mga thread na makipag-usap sa isa't isa. Pagkatapos ng 15th hit, aalisin namin ang bola sa paglalaro para hindi ito masugatan ng husto.

Pampublikong Class Ball ( private int kicks = 0; private static Ball instance = new Ball(); private String side = ""; private Ball()() static Ball getBall())( return instance; ) synchronized void kick(String playername ) ( kicks++; side = playername; System.out.println(kicks + " " + side); ) String getSide())( return side; ) boolean isInGame())( return (kicks< 15); } }

At ngayon, dalawang thread ng manlalaro ang pumasok sa eksena. Tawagan natin sila, nang walang karagdagang abala, Ping at Pong:

Pampublikong klase PingPongGame ( PingPongThread player1 = bagong PingPongThread("Ping"); PingPongThread player2 = bagong PingPongThread("Pong"); Ball ball; PingPongGame())( ball = Ball.getBall(); ) void startGame() throws InterruptedException ( player1 .start(); player2.start();

"Ang stadium ay puno ng mga tao - oras na para simulan ang laban." Opisyal nating ipahayag ang pagbubukas ng pulong - sa pangunahing klase ng aplikasyon:

Public class PingPong ( public static void main(String args) throws InterruptedException ( PingPongGame game = new PingPongGame(); game.startGame(); ) )

Tulad ng nakikita mo, walang nakakagulat dito. Ito ay isang panimula lamang sa multithreading, ngunit mayroon ka nang ideya kung paano ito gumagana at maaaring mag-eksperimento - nililimitahan ang tagal ng laro hindi sa bilang ng mga hit, ngunit sa pamamagitan ng oras, halimbawa. Babalik tayo sa paksa ng multithreading mamaya - titingnan natin ang java.util.concurrent package, ang Akka library at ang pabagu-bagong mekanismo. Pag-usapan din natin ang pagpapatupad ng multithreading sa Python.

Andrey Kolesov

Kapag sinimulan naming isaalang-alang ang mga prinsipyo ng paglikha ng mga multi-threaded na application para sa Microsoft .NET Framework, agad kaming gagawa ng reserbasyon: kahit na ang lahat ng mga halimbawa ay ibinigay sa Visual Basic .NET, ang pamamaraan para sa paglikha ng mga naturang programa ay karaniwang pareho para sa lahat. mga programming language na sumusuporta sa .NET, kabilang ang C#. Ang VB ay pinili upang ipakita ang teknolohiya para sa paglikha ng mga multi-threaded na application lalo na dahil ang mga nakaraang bersyon ng tool na ito ay hindi nagbibigay ng gayong tampok.

Mag-ingat: Magagawa rin ITO ng Visual Basic .NET!

Tulad ng alam mo, ang Visual Basic (hanggang sa bersyon 6.0 inclusive) ay hindi kailanman pinayagan ang paglikha ng mga multi-threaded na bahagi ng software (EXE, ActiveX DLL at OCX). Dito kailangan mong tandaan na ang arkitektura ng COM ay may kasamang tatlong magkakaibang modelo ng threading: single threaded (Single Thread), shared (Single Threaded Apartment, STA) at libre (Multi-Threaded Apartment). Pinapayagan ka ng VB 6.0 na lumikha ng mga programa ng unang dalawang uri. Ang opsyon ng STA ay nagbibigay ng pseudo-multithreading mode - maraming mga thread ang aktwal na gumagana nang magkatulad, ngunit ang program code ng bawat isa sa kanila ay protektado mula sa pag-access dito mula sa labas (sa partikular, ang mga thread ay hindi maaaring gumamit ng mga nakabahaging mapagkukunan).

Ang Visual Basic .NET ay maaari na ngayong magpatupad ng katutubong multithreading. Mas tiyak, sa .NET ang mode na ito ay sinusuportahan sa antas ng mga karaniwang library ng klase, ang Class Library, at ang Common Language Runtime. Bilang resulta, ang VB.NET ay may access sa mga kakayahan na ito kasama ng iba pang .NET programming language.

Sa isang pagkakataon, ang komunidad ng developer ng VB, habang nagpapahayag ng kawalang-kasiyahan sa maraming mga inobasyon sa hinaharap ng wikang ito, ay tumugon nang may mahusay na pag-apruba sa balita na ang paggamit ng bagong bersyon ng tool ay posible na lumikha ng mga multi-threaded na programa (tingnan ang "Naghihintay para sa Visual Studio .NET", "BYTE /Russia" No. 1/2001). Gayunpaman, maraming mga eksperto ang nagpahayag ng mas pinigilan na mga pagtatasa ng pagbabagong ito. Narito, halimbawa, ang opinyon ni Dan Appleman, isang sikat na developer at may-akda ng maraming mga libro para sa mga programmer ng VB: "Ang multithreading sa VB.NET ay nakakatakot sa akin nang higit kaysa sa anumang iba pang pagbabago, at, tulad ng maraming mga bagong .NET na teknolohiya, ito Ito ay dahil sa tao sa halip na mga teknolohikal na kadahilanan... Natatakot ako sa multi-threading sa VB.NET dahil ang mga programmer ng VB sa pangkalahatan ay walang karanasan sa pagdidisenyo at pag-debug ng mga multi-threaded na application."

Sa katunayan, tulad ng iba pang mga tool sa programming na mababa ang antas (halimbawa, ang parehong mga Win API), ang libreng multithreading, sa isang banda, ay nagbibigay ng mas malaking pagkakataon para sa paglikha ng mga solusyon na nasusukat na may mataas na pagganap, at sa kabilang banda, naglalagay ito ng mas mataas na mga pangangailangan sa mga kwalipikasyon ng mga developer. Bukod dito, ang problema dito ay pinalala ng katotohanan na ang paghahanap ng mga error sa isang multi-threaded na application ay napakahirap, dahil madalas silang lumilitaw nang random, bilang isang resulta ng isang tiyak na intersection ng mga parallel na proseso ng computing (madalas na imposibleng magparami. ganoon na naman ang sitwasyon). Iyon ang dahilan kung bakit ang mga pamamaraan ng tradisyonal na pag-debug ng mga programa sa anyo ng muling pagpapatakbo ng mga ito ay karaniwang hindi nakakatulong sa kasong ito. At ang tanging paraan sa ligtas na paggamit ng multithreading ay ang de-kalidad na disenyo ng application bilang pagsunod sa lahat ng mga klasikal na prinsipyo ng "tamang programming".

Ang problema sa mga programmer ng VB ay bagama't marami sa kanila ay medyo may karanasan na mga propesyonal at alam na alam ang mga pitfalls ng multithreading, ang paggamit ng VB6 ay maaaring mapurol ang kanilang pagbabantay. Pagkatapos ng lahat, kapag sinisisi natin ang VB para sa mga limitasyon nito, kung minsan ay nakakalimutan natin na marami sa mga limitasyon ay natukoy ng mga pinahusay na feature ng seguridad ng tool na ito, na pumipigil o nag-aalis ng mga error sa developer. Halimbawa, ang VB6 ay awtomatikong gumagawa ng isang hiwalay na kopya ng lahat ng mga global na variable para sa bawat thread, kaya pinipigilan ang mga posibleng salungatan sa pagitan ng mga ito. Sa VB.NET, ang mga naturang problema ay ganap na inilipat sa mga balikat ng programmer. Dapat ding tandaan na ang paggamit ng isang multi-threaded na modelo sa halip na isang single-threaded ay hindi palaging humahantong sa pagtaas ng pagganap ng programa ay maaaring bumaba pa (kahit sa mga multiprocessor system!).

Gayunpaman, ang lahat ng sinabi sa itaas ay hindi dapat ituring bilang payo na huwag gulo sa multithreading. Kailangan mo lang magkaroon ng magandang ideya kung kailan dapat gamitin ang mga ganitong mode, at unawain na ang isang mas malakas na tool sa pag-unlad ay palaging naglalagay ng mas mataas na pangangailangan sa mga kwalipikasyon ng programmer.

Parallel Processing sa VB6

Siyempre, posible na ayusin ang pseudo-parallel na pagproseso ng data gamit ang VB6, ngunit ang mga kakayahan na ito ay napakalimitado. Halimbawa, ilang taon na ang nakalilipas kailangan kong magsulat ng isang pamamaraan na nag-pause sa pagpapatupad ng isang programa para sa isang tinukoy na bilang ng mga segundo (ang kaukulang SLEEP na pahayag ay naroroon sa handa na form sa Microsoft Basic/DOS). Hindi mahirap ipatupad ito sa iyong sarili sa anyo ng sumusunod na simpleng subroutine:

Madali mong ma-verify ang functionality nito, halimbawa, gamit ang sumusunod na code para sa pagproseso ng pag-click sa button sa form:

Upang malutas ang problemang ito sa VB6, sa loob ng Do...Loop ng SleepVB procedure, kailangan mong alisin sa komento ang tawag sa function ng DoEvents, na naglilipat ng kontrol sa operating system at ibinabalik ang bilang ng mga bukas na form sa VB application na ito. Ngunit tandaan na ang pagpapakita ng isang window na may mensaheng "Hello again!", sa turn, ay humaharang sa pagpapatupad ng buong application, kasama ang SleepVB procedure.

Gamit ang mga pandaigdigang variable bilang mga flag, maaari mo ring tiyakin na ang tumatakbong pamamaraan ng SleepVB ay hindi normal na nagwawakas. Ito naman, ang pinakasimpleng halimbawa ng proseso ng pag-compute na ganap na sumasakop sa mga mapagkukunan ng processor. Ngunit kung magsasagawa ka ng ilang mga kapaki-pakinabang na kalkulasyon (at hindi tumatakbo sa isang walang laman na loop), kailangan mong tandaan na ang pagtawag sa DoEvent function ay tumatagal ng maraming oras, kaya kailangan itong gawin sa medyo malalaking pagitan. .

Upang makita ang mga limitasyon ng parallel computing na suporta ng VB6, palitan ang tawag sa function ng DoEvents ng isang label na output:

Label1.Caption = Timer

Sa kasong ito, hindi lamang hindi gagana ang Command2 button, ngunit kahit sa loob ng 5 s ay hindi magbabago ang mga nilalaman ng label.

Para sa isa pang eksperimento, magdagdag ng wait call sa code para sa Command2 (magagawa mo ito dahil ang SleepVB procedure ay reentrant):

Pribadong Sub Command2_Click() Tawagan ang SleepVB(5) MsgBox "Hello again!" End Sub

Susunod, ilunsad ang application at i-click ang Command1, at pagkatapos ng 2-3 segundo - Command2. Ang mensaheng "Isa pang Hello" ay unang lalabas, kahit na ang kaukulang proseso ay inilunsad sa ibang pagkakataon. Ang dahilan nito ay ang pag-andar ng DoEvents ay nagsusuri lamang ng mga kaganapan sa visual na elemento, hindi para sa pagkakaroon ng iba pang mga thread sa pagpoproseso. Bukod dito, ang VB application ay aktwal na tumatakbo sa isang solong thread, kaya ang kontrol ay bumalik sa pamamaraan ng kaganapan na huling na-fired.

Pamamahala ng Thread sa .NET

Ang pagbuo ng mga multithreaded .NET na application ay batay sa paggamit ng isang pangkat ng mga baseng klase ng .NET Framework na inilarawan ng System.Threading namespace. Sa kasong ito, ang pangunahing papel ay kabilang sa klase ng Thread, sa tulong kung saan halos lahat ng mga operasyon sa pamamahala ng thread ay ginaganap. Mula sa puntong ito, lahat ng sinabi tungkol sa pagtatrabaho sa mga thread ay nalalapat sa lahat ng mga tool sa programming sa .NET, kabilang ang C#.

Para sa unang kakilala sa paglikha ng parallel na mga thread, gumawa tayo ng Windows application na may form kung saan ilalagay natin ang ButtonStart at ButtonAbort button at isulat ang sumusunod na code:

Gusto ko agad na iguhit ang iyong pansin sa tatlong puntos. Una, ang mga Import na keyword ay ginagamit upang sumangguni sa mga pinaikling pangalan ng mga klase na inilarawan ng namespace dito. Partikular kong isinama ang isa pang use case para sa Imports upang ilarawan ang isang shorthand na katumbas ng isang mahabang pangalan ng namespace (VB = Microsoft.VisualBasic) na maaaring ilapat sa text ng programa. Sa kasong ito, makikita mo kaagad kung saang namespace kabilang ang object ng Timer.

Pangalawa, gumamit ako ng mga lohikal na bracket ng #Region upang biswal na paghiwalayin ang code na isinulat ko mula sa code na awtomatikong nabuo ng form designer (ang huli ay hindi ipinapakita dito).

Pangatlo, ang mga paglalarawan ng mga parameter ng input ng mga pamamaraan ng kaganapan ay espesyal na inalis (ito ay gagawin minsan sa hinaharap) upang hindi magambala ng mga bagay na hindi mahalaga sa kasong ito.

Ilunsad ang application at i-click ang button na ButtonStart. Ang proseso ng paghihintay ay nagsimula sa isang cycle para sa isang naibigay na agwat ng oras, at sa kasong ito (hindi katulad ng halimbawa sa VB6) - sa isang independiyenteng thread. Madali itong i-verify - lahat ng visual na elemento ng form ay naa-access. Halimbawa, sa pamamagitan ng pag-click sa ButtonAbort na button, maaari mong wakasan ang proseso nang hindi normal gamit ang Abort method (ngunit ang pagsasara ng form gamit ang Close system button ay hindi makakaabala sa procedure!). Upang mailarawan ang dynamics ng proseso, maaari kang maglagay ng label sa form, at magdagdag ng output ng kasalukuyang oras sa sleep loop ng SleepVBNET procedure:

Label1.Text = _ "Kasalukuyang oras = " & VB.TimeOfDay

Ang pamamaraan ng SleepVBNET (na sa kasong ito ay isang paraan na ng isang bagong bagay) ay patuloy na ipapatupad kahit na magdagdag ka ng isang kahon ng mensahe sa ButtonStart code upang magpakita ng isang kahon ng mensahe na nagpapahiwatig ng pagsisimula ng mga kalkulasyon pagkatapos magsimula ang thread (Figure 1) .

Ang isang mas kumplikadong opsyon ay isang stream bilang isang klase

Upang magsagawa ng karagdagang mga eksperimento sa mga thread, gumawa tayo ng bagong VB application na may uri ng Console, na binubuo ng isang regular na module ng code na may Pangunahing pamamaraan (na magsisimulang isagawa kapag nagsimula ang application) at isang WorkerThreadClass class module:

Ilunsad natin ang nilikhang application. Lilitaw ang isang console window kung saan makikita ang isang tumatakbong linya ng mga character, na nagpapakita ng modelo ng tumatakbong proseso ng computing (WorkerThread). Pagkatapos ay lilitaw ang window ng mensahe na ibinigay ng proseso ng pagtawag (Main), at sa wakas ay makikita natin ang larawan na ipinapakita sa Fig. 2 (kung hindi ka nasisiyahan sa bilis ng pagpapatupad ng simulate na proseso, pagkatapos ay alisin o magdagdag ng ilang mga operasyon sa aritmetika na may variable na "a" sa pamamaraan ng WorkerThread).

Pakitandaan: ang window ng mensahe na "Nagsimula ang unang thread" ay ipinakita nang may kapansin-pansing pagkaantala pagkatapos magsimula ang proseso ng WorkerThread (sa kaso ng form na inilarawan sa nakaraang talata, ang ganoong mensahe ay lalabas halos kaagad pagkatapos na pindutin ang ButtonStart button).

Malamang, nangyayari ito dahil kapag nagtatrabaho sa isang form, ang mga pamamaraan ng kaganapan ay may mas mataas na priyoridad kumpara sa prosesong inilulunsad. Sa kaso ng isang console application, ang lahat ng mga pamamaraan ay may parehong priyoridad. Tatalakayin natin ang isyu ng mga priyoridad sa ibang pagkakataon, ngunit sa ngayon ay itatakda natin ang pinakamataas na priyoridad para sa thread ng pagtawag (Main):

Thread.CurrentThread.Priority = _ ThreadPriority.Highest Thread1.Start()

Ngayon ang window ay lilitaw halos kaagad. Tulad ng nakikita mo, mayroong dalawang paraan upang lumikha ng mga pagkakataon ng isang bagay na Thread. Sa una ginamit namin ang una sa kanila - lumikha kami ng isang bagong bagay (thread) Thread1 at nagtrabaho kasama nito. Ang pangalawang opsyon ay upang makuha ang Thread object para sa kasalukuyang tumatakbong thread gamit ang static na CurrentThread na paraan. Ito ay kung paano itinakda ng Pangunahing pamamaraan ang sarili nitong isang mas mataas na priyoridad, ngunit maaari itong gawin para sa anumang iba pang thread, halimbawa:

Thread1.Priority = ThreadPriority.Lowest Thread1.Start()

Upang ipakita ang mga kakayahan ng pamamahala ng isang tumatakbong proseso, idagdag ang mga sumusunod na linya ng code sa dulo ng Pangunahing pamamaraan:

Ngayon patakbuhin ang application habang gumagawa ng ilang mga operasyon ng mouse (sana ay naitakda mo ang tamang antas ng latency sa WorkerThread upang hindi ito masyadong mabilis, ngunit hindi masyadong mabagal).

Una, magsisimula ang "Process 1" sa console window at lalabas ang mensaheng "First thread running". Ang "Proseso 1" ay tumatakbo, at mabilis mong na-click ang OK sa kahon ng mensahe.

Susunod - "Proseso 1" ay nagpapatuloy, ngunit pagkatapos ng dalawang segundo ang mensaheng "Thread suspended" ay lilitaw. Nag-freeze ang "Process 1". I-click ang "OK" sa kahon ng mensahe: Ang "Proseso 1" ay nagpatuloy sa pagpapatupad nito at matagumpay na nakumpleto.

Sa snippet na ito, ginamit namin ang Sleep method para suspindihin ang kasalukuyang proseso. Tandaan: Ang pagtulog ay isang static na paraan at maaari lamang ilapat sa kasalukuyang proseso, hindi sa anumang instance ng Thread object. Ang syntax ng wika ay nagpapahintulot sa iyo na magsulat ng Thread1.Sleep o Thread.Sleep, ngunit sa kasong ito ang CurrentThread object ay ginagamit pa rin.

Ang isa pang kawili-wiling kaso ng paggamit para sa Sleep ay ang Timeout.Infinite na halaga. Sa kasong ito, ang thread ay masususpinde nang walang katiyakan hanggang sa ang estado ay maantala ng isa pang thread gamit ang Thread.Interrupt na paraan.

Upang suspindihin ang isang panlabas na thread mula sa isa pang thread nang hindi pinipigilan ang huli, kailangan mong gamitin ang Thread.Suspend method call. Pagkatapos ay maaari mong ipagpatuloy ang pagpapatupad nito gamit ang pamamaraan ng Thread.Resume, na kung ano ang ginawa namin sa code sa itaas.

Medyo tungkol sa pag-synchronize ng thread

Ang pag-synchronize ng thread ay isa sa mga pangunahing hamon kapag nagsusulat ng mga multi-threaded na application, at ang System.Threading space ay may malaking hanay ng mga tool para sa paglutas nito. Ngunit ngayon ay makikilala lamang natin ang pamamaraan ng Thread.Join, na nagbibigay-daan sa iyong subaybayan ang pagtatapos ng pagpapatupad ng thread. Upang makita kung paano ito gumagana, palitan ang mga huling linya ng Pangunahing pamamaraan ng code na ito:

Pamamahala ng Priyoridad sa Proseso

Ang pamamahagi ng mga hiwa ng oras ng processor sa pagitan ng mga thread ay ginagawa gamit ang mga priyoridad, na tinukoy bilang Thread.Priority property. Ang mga thread na ginawa sa runtime ay maaaring itakda sa limang halaga: Pinakamataas, AboveNormal, Normal (default), BelowNormal, at Pinakamababa. Upang makita kung paano nakakaapekto ang mga priyoridad sa bilis ng pagpapatupad ng mga thread, isulat natin ang sumusunod na code para sa Pangunahing pamamaraan:

Sub Main() " paglalarawan ng unang proseso Dim Thread1 Bilang Thread Dim oWorker1 Bilang Bagong WorkerThreadClass() Thread1 = Bagong Thread(AddressOf _ oWorker1.WorkerThread) " Thread1.Priority = _ " ThreadPriority.BelowNormal " ipinapasa namin ang paunang data: oWorker1 .Start = 1 oWorker1.Finish = 10 oWorker1.ThreadName = "Count 1" oWorker1.SymThread = "."

" paglalarawan ng pangalawang proseso Dim Thread2 Bilang Thread Dim oWorker2 Bilang Bagong WorkerThreadClass() Thread2 = Bagong Thread(AddressOf _ oWorker2.WorkerThread) " na nagpapasa sa unang data: oWorker2.Start = 11 oWorker2.Finish = 20 oWorker2.Finish = 20 oWorker2. 2" oWorker2 .SymThread = "*" " " patakbuhin ang karera Thread.CurrentThread.Priority = _ ThreadPriority.Highest Thread1.Start() Thread2.Start() " Naghihintay para sa pagkumpleto ng mga proseso Thread1.Join() Thread2.Join () MsgBox("Nakumpleto na ang parehong mga proseso ") End Sub

Ngayon, bago simulan ang unang thread, itakda natin ang priyoridad nito sa isang antas na mas mababa:

Thread1.Priority = _ ThreadPriority.BelowNormal

Ang larawan ay nagbago nang malaki: ang pangalawang daloy ay halos ganap na tumagal sa lahat ng oras mula sa una (Larawan 4).

Tandaan din ang paggamit ng paraan ng Join. Sa tulong nito, nagsasagawa kami ng isang medyo karaniwang variant ng pag-synchronize ng thread, kung saan ang pangunahing programa ay naghihintay para sa pagkumpleto ng ilang parallel computing na proseso.

Konklusyon

Nahawakan lang namin ang mga pangunahing kaalaman sa pagbuo ng mga multi-threaded na .NET na aplikasyon. Ang isa sa mga pinaka-kumplikado at praktikal na nauugnay na mga isyu ay ang pag-synchronize ng thread. Bilang karagdagan sa paggamit ng Thread object na inilarawan sa artikulong ito (ito ay may maraming mga pamamaraan at katangian na hindi namin saklaw dito), ang mga klase ng Monitor at Mutex, pati na rin ang lock (C#) at SyncLock (VB.NET) na mga pahayag, naglalaro. isang napakahalagang papel sa pamamahala ng thread.

Ang isang mas detalyadong paglalarawan ng teknolohiyang ito ay ibinibigay sa magkahiwalay na mga kabanata ng mga aklat at, kung saan nais kong magbigay ng ilang mga quote (na lubos kong sinasang-ayunan) bilang isang napakaikling buod ng paksang "Multithreading sa .NET".

"Kung ikaw ay isang baguhan, maaari kang magulat na matuklasan na ang overhead na nauugnay sa paggawa at pagpapadala ng mga thread ay maaaring maging sanhi ng isang single-threaded na application na tumakbo nang mas mabilis... Kaya palaging subukang subukan ang parehong single-threaded at multi-threaded na mga prototype ng programa."

"Dapat kang magdisenyo ng multithreading nang maingat at mahigpit na kontrolin ang pag-access sa mga nakabahaging bagay at variable."

"Hindi dapat ituring ang multithreading bilang default na diskarte."

"Tinanong ko ang isang madla ng mga makaranasang programmer ng VB kung gusto nilang makakuha ng libreng multi-threading sa hinaharap na bersyon ng VB. Halos lahat ay nagtaas ng kanilang mga kamay. Pagkatapos ay tinanong ko kung sino ang nakakaalam kung ano ang kanilang pinapasok. Iilan lamang ang mga tao ang nagtaas ng kanilang mga kamay. this time." and there were knowing smiles on their faces."

"Kung hindi ka natatakot sa mga hamon ng pagdidisenyo ng mga multithreaded na application, kapag ginamit nang tama, ang multithreading ay maaaring makabuluhang mapabuti ang pagganap ng application."

Gusto kong idagdag na ang teknolohiya para sa paglikha ng mga multi-threaded na .NET na aplikasyon (tulad ng maraming iba pang .NET na teknolohiya) sa pangkalahatan ay halos independyente sa wikang ginamit. Samakatuwid, pinapayuhan ko ang mga developer na pag-aralan ang iba't ibang mga libro at artikulo, anuman ang programming language na kanilang pinili upang ipakita ang isang partikular na teknolohiya.

Panitikan:

  1. Dan Appleman. Paglipat sa VB.NET: mga diskarte, konsepto, code/Trans. mula sa Ingles
  2. - St. Petersburg: "Peter", 2002, - 464 pp.: may sakit.

Tom Archer. Mga Pangunahing Kaalaman sa C#. Mga pinakabagong teknolohiya/Trans. mula sa Ingles - M.: Publishing at trading house "Russian Edition", 2001. - 448 p.: ill.

Multitasking at multithreading

Magsimula tayo sa simpleng pahayag na ito: Sinusuportahan ng 32-bit na Windows operating system ang multitasking (multi-process) at multi-threaded na mga mode ng pagpoproseso ng data. Maaari nating pag-usapan kung gaano nila ito kahusay, ngunit isa pang tanong iyon.

Ang multitasking ay isang mode ng pagpapatakbo kapag ang isang computer ay maaaring magsagawa ng ilang mga gawain nang sabay-sabay, nang magkatulad. Malinaw na kung ang isang computer ay may isang processor, pagkatapos ay pinag-uusapan natin ang tungkol sa pseudo-parallelism, kapag ang OS, ayon sa ilang mga patakaran, ay maaaring mabilis na lumipat sa pagitan ng iba't ibang mga gawain.

Ang gawain ay isang programa o bahagi ng isang programa (application) na nagsasagawa ng ilang lohikal na aksyon at isang yunit kung saan ang OS ay naglalaan ng mga mapagkukunan.

Ano ang daloy? Ang isang thread ay isang autonomous na proseso ng computing, ngunit inilalaan hindi sa antas ng OS, ngunit sa loob ng isang gawain. Ang pangunahing pagkakaiba sa pagitan ng isang thread at isang "proseso ng gawain" ay ang lahat ng mga thread ng isang gawain ay naisakatuparan sa isang espasyo ng address, iyon ay, maaari silang gumana sa mga mapagkukunan ng nakabahaging memorya. Ito ay tiyak kung saan ang kanilang mga pakinabang (parallel data processing) at disadvantages (banta sa pagiging maaasahan ng programa). Dito dapat tandaan na sa kaso ng multitasking, ang OS ay pangunahing responsable sa pagprotekta sa mga application, at kapag ginamit ang multithreading, ang developer mismo ang may pananagutan.

Tandaan na ang paggamit ng multitasking mode sa mga single-processor system ay nagbibigay-daan sa iyo upang mapataas ang pangkalahatang pagganap ng multitasking system sa kabuuan (bagaman hindi palaging, dahil habang tumataas ang bilang ng mga switching, tumataas ang bahagi ng mga mapagkukunan na inookupahan ng OS). Ngunit ang oras na kinakailangan upang makumpleto ang isang tiyak na gawain ay palaging, kahit na bahagya lamang, ay tumataas dahil sa karagdagang gawain ng OS.

Kung ang processor ay puno ng mga gawain (na may kaunting I/O downtime, halimbawa, sa kaso ng paglutas ng mga problemang pangmatematika), ang tunay na pangkalahatang mga pagpapabuti sa pagganap ay makakamit lamang kapag gumagamit ng mga multiprocessor system. Ang ganitong mga sistema ay nagbibigay-daan sa iba't ibang mga modelo ng parallelization - sa antas ng gawain (ang bawat gawain ay maaaring sakupin lamang ang isang processor, habang ang mga thread ay pinaandar lamang ng pseudo-parallel) o sa antas ng thread (kapag ang isang gawain ay maaaring sakupin ang ilang mga processor kasama ang mga thread nito).

Dito rin natin maaalala na kapag nagpapatakbo ng makapangyarihang shared computing system, ang nagtatag kung saan ay ang IBM System/360 na pamilya noong huling bahagi ng 60s, ang isa sa mga pinakapinipilit na gawain ay ang pagpili ng pinakamainam na opsyon para sa pamamahala ng multitasking - kabilang ang sa dynamic na mode , isinasaalang-alang ang iba't ibang mga parameter. Karaniwan, ang pamamahala ng multitasking ay isang function ng operating system. Ngunit ang pagiging epektibo ng pagpapatupad ng isa o isa pang pagpipilian ay direktang nauugnay sa mga katangian ng arkitektura ng computer sa kabuuan, at lalo na ang processor. Halimbawa, ang parehong high-performance na IBM System/360 ay gumana nang perpekto sa mga shared system para sa mga gawain sa negosyo, ngunit sa parehong oras ito ay ganap na hindi angkop para sa paglutas ng "real-time" na mga problema sa klase. Sa lugar na ito, malinaw na nangunguna sa panahong iyon ang makabuluhang mas mura at mas simpleng mga mini-computer tulad ng DEC PDP 11/20.

Isang halimbawa ng pagbuo ng isang simpleng multi-threaded na application.

Ipinanganak tungkol sa dahilan ng malaking bilang ng mga tanong tungkol sa pagbuo ng mga multi-threaded na application sa Delphi.

Ang layunin ng halimbawang ito ay upang ipakita kung paano maayos na bumuo ng isang multi-threaded na application, na may pangmatagalang trabaho na inilipat sa isang hiwalay na thread. At kung paano sa naturang application upang matiyak ang pakikipag-ugnayan sa pagitan ng pangunahing thread at ang worker thread upang ilipat ang data mula sa form (visual na bahagi) sa thread at likod.

Ang halimbawa ay hindi sinasabing kumpleto; ito ay nagpapakita lamang ng mga pinakasimpleng paraan ng pakikipag-ugnayan sa pagitan ng mga thread. Nagbibigay-daan sa user na "mabilis na gumawa" (na nakakaalam kung gaano ko ito hindi gusto) ng isang gumaganang multi-threaded na application nang tama.
Ang lahat ay nagkomento nang detalyado (sa aking opinyon), ngunit kung mayroon kang anumang mga katanungan, magtanong.
Ngunit binabalaan kita muli: Ang mga stream ay hindi madali. Kung wala kang ideya kung paano gumagana ang lahat, kung gayon mayroong isang malaking panganib na kadalasan ang lahat ay gagana nang maayos para sa iyo, at kung minsan ang programa ay kumikilos nang higit pa sa kakaiba. Ang pag-uugali ng isang maling nakasulat na multi-threaded na programa ay lubos na nakadepende sa isang malaking bilang ng mga salik na minsan ay imposibleng ma-reproduce sa panahon ng pag-debug.

Kaya isang halimbawa. Para sa kaginhawahan, isinama ko ang code at nag-attach ng archive na may module at form code

unit ExThreadForm;

gamit
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Mga dialog, StdCtrls;

// constants na ginagamit kapag naglilipat ng data mula sa isang stream patungo sa isang form gamit
// magpadala ng mga window message
const
WM_USER_SendMessageMetod = WM_USER+10;
WM_USER_PostMessageMetod = WM_USER+11;

uri
// paglalarawan ng klase ng thread, isang inapo ng tThread
tMyThread = klase(tThread)
pribado
SyncDataN:Integer;
SyncDataS:String;
pamamaraan SyncMetod1;
protektado
pamamaraan Ipatupad; override;
pampubliko
Param1: String;
Param2:Integer;
Param3:Boolean;
Huminto:Boolean;
LastRandom:Integer;
IterationNo:Integer;
ResultList:tStringList;

Gumawa ng Constructor(aParam1:String);
destructor Wasakin; override;
wakas;

// paglalarawan ng klase ng form gamit ang stream
TForm1 = klase(TForm)
Label1: TLabel;
Memo1: TMemo;
btnStart: TButton;
btnStop: TButton;
Edit1: TEdit;
Edit2: TEdit;
CheckBox1: TCheckBox;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
procedure btnStartClick(Sender: TObject);
procedure btnStopClick(Sender: TObject);
pribado
(Mga pribadong deklarasyon)
MyThread:tMyThread;
procedure EventMyThreadOnTerminate(Sender:tObject);
pamamaraan EventOnSendMessageMetod (var Msg: TMessage);mensahe WM_USER_SendMessageMetod;
procedure EventOnPostMessageMetod(var Msg: TMessage); mensahe WM_USER_PostMessageMetod;

Pampubliko
(Mga pampublikong deklarasyon)
wakas;

var
Form1: TForm1;

{
Huminto - nagpapakita ng paglipat ng data mula sa isang form patungo sa isang thread.
Hindi nangangailangan ng karagdagang pag-synchronize dahil ito ay simple
uri ng isang salita, at isinulat lamang ng isang thread.
}

pamamaraan TForm1.btnStartClick(Sender: TObject);
magsimula
Randomize(); // pagtiyak ng randomness sa sequence gamit ang Random() - ay walang kinalaman sa daloy

// Lumikha ng isang instance ng thread object, na nagpapasa dito ng input parameter
{
PANSIN!
Ang thread constructor ay isinulat sa paraang nalikha ang isang thread
sinuspinde dahil pinapayagan nitong:
1. Kontrolin ang sandali ng paglulunsad nito. Ito ay halos palaging mas maginhawa, dahil...
ay nagbibigay-daan sa iyo upang i-configure ang stream kahit na bago ilunsad, ipasa ito input
mga parameter, atbp.
2. Dahil ang isang link sa nilikhang bagay ay ise-save sa field ng form, pagkatapos
pagkatapos ng pagsira sa sarili ng thread (tingnan sa ibaba) na nangyayari kapag tumatakbo ang thread
maaaring mangyari anumang oras, ang link na ito ay magiging hindi wasto.
}
MyThread:= tMyThread.Create(Form1.Edit1.Text);

// Gayunpaman, dahil nasuspinde ang thread, anumang mga error
// sa panahon ng pagsisimula nito (bago ilunsad), dapat nating sirain ito mismo
// bakit gumamit ng try / maliban sa block
subukan

// Pagtatalaga ng isang thread completion handler kung saan kami makakatanggap
// resulta ng thread, at "i-overwrite" ang link dito
MyThread.OnTerminate:= EventMyThreadOnTerminate;

// Dahil kukunin natin ang mga resulta sa OnTerminate, i.e. sa pagsira sa sarili
// stream, pagkatapos ay papawiin natin ang ating sarili sa mga alalahanin na sirain ito
MyThread.FreeOnTerminate:= True;

// Isang halimbawa ng pagpasa ng mga parameter ng input sa mga field ng isang stream object, sa punto
// paggawa ng instance kapag hindi pa ito tumatakbo.
// Sa personal, mas gusto kong gawin ito sa pamamagitan ng mga parameter ng na-override
// constructor (tMyThread.Create)
MyThread.Param2:= StrToInt(Form1.Edit2.Text);

MyThread.Stopped:= False; // isa ring uri ng parameter, ngunit nagbabago depende sa
// tagal ng pagtakbo ng thread
maliban sa
// dahil hindi pa nagsisimula ang thread at hindi kayang sirain ang sarili, sirain natin ito "manual"
FreeAndNil(MyThread);
// at pagkatapos ay hayaang maproseso ang exception gaya ng dati
itaas;
wakas;

// Dahil matagumpay na nalikha at na-configure ang thread object, oras na para patakbuhin ito
MyThread.Resume;

ShowMessage("Nagsimula ang stream");
wakas;

pamamaraan TForm1.btnStopClick(Sender: TObject);
magsimula
// Kung umiiral pa rin ang instance ng thread, hilingin itong huminto
// At saka, "magtatanong" lang tayo. Sa prinsipyo, maaari rin nating "puwersa" ito, ngunit ito ay magiging
// eksklusibong opsyong pang-emergency, na nangangailangan ng malinaw na pag-unawa sa lahat ng ito
// batis kusina. Samakatuwid, hindi ito isinasaalang-alang dito.
kung Nakatalaga(MyThread) noon
MyThread.Stopped:= Totoo
iba pa
ShowMessage("Hindi tumatakbo ang thread!");
wakas;

procedure TForm1.EventOnSendMessageMetod(var Msg: TMessage);
magsimula
// paraan para sa pagproseso ng kasabay na mensahe
// sa WParam ang address ng tMyThread object, sa LParam ang kasalukuyang LastRandom value ng thread
sa tMyThread(Msg.WParam) magsisimula
Form1.Label3.Caption:= Format("%d %d %d",);
wakas;
wakas;

procedure TForm1.EventOnPostMessageMetod(var Msg: TMessage);
magsimula
// paraan para sa pagproseso ng isang asynchronous na mensahe
// sa WParam ang kasalukuyang halaga ng IterationNo, sa LParam ang kasalukuyang halaga ng LastRandom ng thread
Form1.Label4.Caption:= Format("%d %d",);
wakas;

pamamaraan TForm1.EventMyThreadOnTerminate(Sender:tObject);
magsimula
// MAHALAGA!
// Ang OnTerminate event handling method ay palaging tinatawag sa konteksto ng pangunahing
// thread - ito ay ginagarantiyahan ng pagpapatupad ng tThread. Samakatuwid, maaari mong malaya
// gumamit ng anumang mga katangian at pamamaraan ng anumang mga bagay

// Kung sakali, siguraduhing umiiral pa rin ang object instance
kung hindi Nakatalaga(MyThread) pagkatapos ay Lumabas; // kung wala ito, wala nang magagawa

// pagkuha ng mga resulta ng gawain ng isang thread ng isang halimbawa ng isang thread object
Form1.Memo1.Lines.Add(Format("The thread ended with result %d",));
Form1.Memo1.Lines.AddStrings((Sender as tMyThread).ResultList);

// Wasakin ang reference sa thread object instance.
// Dahil ang aming thread ay nakakasira sa sarili (FreeOnTerminate:= True)
// pagkatapos matapos makumpleto ang OnTerminate handler, ang thread object instance ay magiging
// nawasak (Libre), at lahat ng reference dito ay magiging invalid.
// Para maiwasan ang aksidenteng pagpasok sa ganoong link, tanggalin ang MyThread
// Hayaan akong tandaan muli - hindi namin sirain ang bagay, ngunit tatanggalin lamang ang link. Bagay
// sisirain ang sarili!
MyThread:= Nil;
wakas;

constructor tMyThread.Create(aParam1:String);
magsimula
// Lumikha ng isang instance ng isang SUSPENDED thread (tingnan ang komento kapag gumagawa ng isang instance)
minana ang Lumikha(True);

// Lumikha ng mga panloob na bagay (kung kinakailangan)
ResultList:= tStringList.Create;

// Pagkuha ng paunang data.

// Kinokopya ang input data na ipinasa sa parameter
Param1:= aParam1;

// Isang halimbawa ng pagtanggap ng data ng input mula sa mga bahagi ng VCL sa constructor ng isang thread object
// Ito ay katanggap-tanggap sa kasong ito, dahil ang constructor ay tinatawag sa konteksto
// pangunahing thread. Samakatuwid, ang mga bahagi ng VCL ay maaaring ma-access dito.
// Ngunit hindi ko gusto ito, dahil sa tingin ko ay masama kapag may alam ang isang thread
// tungkol sa ilang anyo. Ngunit ano ang hindi mo magagawa para sa pagpapakita.
Param3: = Form1.CheckBox1.Checked;
wakas;

destructor tMyThread.Destroy;
magsimula
// pagkasira ng mga panloob na bagay
FreeAndNil(ResultList);
// sinisira ang base tThread
minana;
wakas;

pamamaraan tMyThread.Ipatupad;
var
t:Kardinal;
s:String;
magsimula
Hindi Pag-ulit:= 0; // resulta counter (numero ng ikot)

// Sa aking halimbawa, ang katawan ng thread ay isang loop na nagtatapos
// o sa isang panlabas na "kahilingan" ang Huminto na parameter na dumaan sa variable ay makukumpleto,
// o sa pamamagitan lamang ng pagkumpleto ng 5 cycle
// Mas kaaya-aya para sa akin na isulat ito sa pamamagitan ng "walang hanggan" na loop.

Habang ang True ay nagsisimula

Inc(IterationNo); // susunod na cycle number

LastRandom:= Random(1000); // random na numero - upang ipakita ang pagpasa ng mga parameter mula sa stream patungo sa form

T:= Random(5)+1; // oras na kung saan tayo matutulog kung hindi tayo matatapos

// Mapurol na operasyon (depende sa input parameter)
kung hindi Param3 noon
Inc(Param2)
iba pa
Dis(Param2);

// Bumuo ng isang intermediate na resulta
s:= Format("%s %5d %s %d %d",
);

// Idagdag ang intermediate na resulta sa listahan ng mga resulta
ResultList.Add(s);

//// Mga halimbawa ng pagpasa ng mga intermediate na resulta sa form

//// Pagpasa sa isang naka-synchronize na paraan - ang klasikong paraan
//// Mga Kapintasan:
//// - ang naka-synchronize na paraan ay karaniwang isang paraan ng thread class (para sa pag-access
//// sa mga field ng stream object), ngunit para ma-access ang form field, dapat
//// "alam" tungkol dito at sa mga patlang nito (mga bagay), na kadalasang hindi masyadong maganda
//// mga punto ng view ng organisasyon ng programa.
//// - ang kasalukuyang thread ay masususpindi hanggang sa makumpleto ang pagpapatupad
//// naka-synchronize na paraan.

//// Mga Bentahe:
//// - pamantayan at unibersal
//// - sa isang naka-synchronize na paraan na magagamit mo
//// lahat ng field ng stream object.
// una, kung kinakailangan, kailangan mong i-save ang ipinadalang data sa
// mga espesyal na field ng object object.
SyncDataN:= IterationNo;
SyncDataS: = "Sync"+s;
// at pagkatapos ay magbigay ng naka-synchronize na paraan ng tawag
I-synchronize(SyncMetod1);

//// Pagpapadala sa pamamagitan ng kasabay na pagpapadala ng mensahe (SendMessage)
//// sa kasong ito, maaaring maipasa ang data sa mga parameter ng mensahe (LastRandom),
//// at sa pamamagitan ng mga field ng object, na ipinapasa ang address ng instance sa parameter ng mensahe
//// stream object - Integer(Self).
//// Mga Kapintasan:
//// - dapat alam ng thread ang handle ng form window
//// - tulad ng sa Synchronize, ang kasalukuyang thread ay masususpinde hanggang
//// kumpletong pagproseso ng mensahe ng pangunahing thread
//// - nangangailangan ng makabuluhang oras ng CPU para sa bawat tawag
//// (para sa pagpapalit ng mga thread) kaya hindi kanais-nais ang napakadalas na tawag
//// Mga Bentahe:
//// - tulad ng sa Synchronize, kapag nagpoproseso ng isang mensahe na magagamit mo
//// lahat ng field ng stream object (kung, siyempre, naipasa ang address nito)


//// simulan ang thread.
SendMessage(Form1.Handle,WM_USER_SendMessageMetod,Integer(Self),LastRandom);

//// Pagpapadala sa pamamagitan ng asynchronous na pagpapadala ng mensahe (PostMessage)
//// Dahil sa kasong ito, sa oras na matanggap ng pangunahing thread ang mensahe,
//// maaaring nakumpleto na ang pagpapadala ng thread, na ipinapasa ang address ng halimbawa
//// thread object ay hindi wasto!
//// Mga Kapintasan:
//// - dapat alam ng thread ang handle ng form window;
//// - dahil sa asynchrony, ang paglipat ng data ay posible lamang sa pamamagitan ng mga parameter
//// mga mensahe, na makabuluhang nagpapalubha sa paglipat ng data ng laki
//// higit sa dalawang machine words. Maginhawang gamitin para sa paglilipat ng Integer, atbp.
//// Mga Bentahe:
//// - hindi tulad ng mga naunang pamamaraan, ang kasalukuyang thread ay HINDI
//// sinuspinde, ngunit agad na ipagpapatuloy ang pagpapatupad
//// - hindi tulad ng isang naka-synchronize na tawag, isang message handler
//// ay isang paraan ng form na dapat magkaroon ng kaalaman sa thread object,
//// o walang alam tungkol sa stream kung ang data ay ipinadala lamang
//// sa pamamagitan ng mga parameter ng mensahe. Iyon ay, maaaring walang alam ang thread tungkol sa form
//// sa pangkalahatan - Tanging ang Handle nito, na maaaring maipasa bilang isang parameter bago
//// simulan ang thread.
PostMessage(Form1.Handle,WM_USER_PostMessageMetod,IterationNo,LastRandom);

//// Suriin para sa posibleng pagkumpleto

// Suriin ang pagkumpleto ayon sa parameter
kung Huminto pagkatapos ay Break;

// Suriin ang pagkumpleto minsan
kung IterationNo >= 10 pagkatapos ay Break;

Matulog(t*1000); // Matutulog ng t segundo
wakas;
wakas;

pamamaraan tMyThread.SyncMetod1;
magsimula
// ang pamamaraang ito ay tinatawag gamit ang paraan ng Pag-synchronize.
// Iyon ay, sa kabila ng katotohanan na ito ay isang paraan ng tMyThread thread,
// tumatakbo ito sa konteksto ng pangunahing thread ng application.
// Samakatuwid, kaya niyang gawin ang lahat, o halos lahat :)
// Ngunit tandaan, hindi na kailangang mag-“tinker” dito nang mahabang panahon

// Naipasa ang mga parameter, maaari naming i-extract ang mga ito mula sa espesyal na field kung saan kami
// na-save bago tumawag.
Form1.Label1.Caption:= SyncDataS;

// o mula sa iba pang mga field ng flow object, halimbawa, na sumasalamin sa kasalukuyang estado nito
Form1.Label2.Caption:= Format("%d %d",);
wakas;

Sa pangkalahatan, ang halimbawa ay nauna sa aking mga sumusunod na kaisipan sa paksa....

una:
ANG PINAKAMAHALAGANG tuntunin ng multi-threaded programming sa Delphi:
Sa konteksto ng isang hindi pangunahing thread, hindi mo maa-access ang mga katangian at pamamaraan ng mga form, at sa katunayan ang lahat ng mga bahagi na "lumago" mula sa tWinControl.

Nangangahulugan ito (medyo pinasimple) na wala sa paraan ng Execute na minana mula sa TThread, o sa iba pang mga pamamaraan/procedure/function na tinatawag mula sa Execute, ito ay ipinagbabawal huwag direktang i-access ang anumang mga katangian o pamamaraan ng mga visual na bahagi.

Paano ito gagawin ng tama.
Walang mga karaniwang recipe dito. Mas tiyak, napakarami at iba't ibang mga opsyon na kailangan mong piliin depende sa partikular na kaso. Iyon ang dahilan kung bakit nire-refer ka nila sa artikulo. Ang pagkakaroon ng basahin at naunawaan ito, ang programmer ay magagawang maunawaan kung paano pinakamahusay na gawin ito sa isang partikular na kaso.

Sa maikling salita:

Kadalasan, ang isang application ay nagiging multi-threaded alinman kapag ito ay kinakailangan upang gawin ang ilang mga pang-matagalang trabaho, o kapag ito ay posible na gawin ang ilang mga bagay sa parehong oras na hindi mabigat na load ang processor.

Sa unang kaso, ang pagpapatupad ng trabaho sa loob ng pangunahing thread ay humahantong sa "pagbagal" ng interface ng gumagamit - habang ginagawa ang trabaho, ang loop sa pagproseso ng mensahe ay hindi naisakatuparan. Bilang resulta, ang programa ay hindi tumutugon sa mga aksyon ng gumagamit, at ang form ay hindi iginuhit, halimbawa, pagkatapos na ilipat ito ng gumagamit.

Sa pangalawang kaso, kapag ang trabaho ay nagsasangkot ng aktibong pakikipagpalitan sa labas ng mundo, pagkatapos ay sa panahon ng sapilitang "downtime". Habang naghihintay na matanggap/mapadala ang data, maaari kang gumawa ng ibang bagay nang magkatulad, halimbawa, muling magpadala/makatanggap ng ibang data.

May iba pang mga kaso, ngunit hindi gaanong karaniwan. Gayunpaman, hindi ito mahalaga. Hindi tungkol diyan ngayon.

Ngayon, paano nasusulat ang lahat ng ito? Naturally, ang isang tiyak na pinakakaraniwang kaso, medyo pangkalahatan, ay isinasaalang-alang. Kaya.

Ang gawaing isinasagawa sa isang hiwalay na thread, sa pangkalahatan, ay may apat na entity (hindi ko alam kung ano ang mas tiyak na tawag dito):
1. Paunang datos
2. Ang aktwal na gawain mismo (maaaring depende ito sa pinagmulang data)
3. Intermediate data (halimbawa, impormasyon tungkol sa kasalukuyang estado ng trabaho)
4. Output (resulta)

Kadalasan, ginagamit ang mga visual na bahagi upang basahin at ipakita ang karamihan sa data. Ngunit, tulad ng nabanggit sa itaas, hindi mo maaaring direktang ma-access ang mga visual na bahagi mula sa isang stream. Paano ito mangyayari?
Iminumungkahi ng mga developer ng Delphi ang paggamit ng paraan ng Pag-synchronize ng klase ng TThread. Dito hindi ko ilalarawan kung paano gamitin ito - mayroong nabanggit na artikulo para doon. Sasabihin ko lang na ang paggamit nito, kahit na tama, ay hindi palaging makatwiran. Mayroong dalawang mga problema:

Una, ang katawan ng isang pamamaraan na tinatawag na sa pamamagitan ng Synchronize ay palaging isinasagawa sa konteksto ng pangunahing thread, at samakatuwid, habang ito ay isinasagawa, ang window message processing loop ay muling hindi naisasagawa. Samakatuwid, dapat itong maisakatuparan nang mabilis, kung hindi, makakakuha tayo ng lahat ng parehong mga problema tulad ng sa isang solong sinulid na pagpapatupad. Sa isip, ang pamamaraang tinatawag sa pamamagitan ng Synchronize ay dapat lamang gamitin sa pangkalahatan upang ma-access ang mga katangian at pamamaraan ng mga visual na bagay.

Pangalawa, ang pagsasagawa ng isang paraan sa pamamagitan ng Synchronize ay isang "mahal" na kasiyahan na dulot ng pangangailangan para sa dalawang switch sa pagitan ng mga thread.

Bukod dito, ang parehong mga problema ay magkakaugnay at nagiging sanhi ng isang pagkakasalungatan: sa isang banda, upang malutas ang una, ito ay kinakailangan upang "hiwain" ang mga pamamaraan na tinatawag sa pamamagitan ng Pag-synchronize, at sa kabilang banda, sila ay kailangang tawagan nang mas madalas, nawawala ang mahalagang mapagkukunan ng processor.

Samakatuwid, tulad ng dati, kailangan mong lapitan ito nang matalino, at para sa iba't ibang mga kaso, gumamit ng iba't ibang paraan ng pakikipag-ugnayan sa labas ng mundo:

Paunang data
Ang lahat ng data na ipinadala sa stream at hindi nagbabago sa panahon ng operasyon nito ay dapat ipadala bago ito ilunsad, i.e. kapag gumagawa ng thread. Upang magamit ang mga ito sa katawan ng isang thread, kailangan mong gumawa ng lokal na kopya ng mga ito (karaniwan ay nasa mga field ng isang bata sa TThread).
Kung mayroong source data na maaaring magbago habang tumatakbo ang thread, ang pag-access sa naturang data ay dapat gawin alinman sa pamamagitan ng mga naka-synchronize na pamamaraan (mga pamamaraan na tinatawag sa pamamagitan ng Synchronize) o sa pamamagitan ng mga field ng isang thread object (isang descendant ng TThread). Ang huli ay nangangailangan ng ilang pag-iingat.

Intermediate at output na data
Narito muli mayroong ilang mga paraan (ayon sa aking kagustuhan):
- Isang paraan para sa asynchronous na pagpapadala ng mga mensahe sa pangunahing window ng application.
Karaniwang ginagamit upang magpadala ng mga mensahe sa pangunahing window ng aplikasyon tungkol sa katayuan ng isang proseso, pagpapadala ng kaunting data (halimbawa, porsyento ng pagkumpleto)
- Isang paraan para sa sabay-sabay na pagpapadala ng mga mensahe sa pangunahing window ng application.
Ito ay karaniwang ginagamit para sa parehong mga layunin tulad ng asynchronous na pagpapadala, ngunit pinapayagan kang maglipat ng mas malaking halaga ng data nang hindi gumagawa ng hiwalay na kopya.
- Mga naka-synchronize na pamamaraan, kung posible, pagsasama-sama ng paglipat ng mas maraming data hangga't maaari sa isang paraan.
Magagamit din para makatanggap ng data mula sa isang form.
- Sa pamamagitan ng mga field ng isang stream object, na tinitiyak ang parehong eksklusibong pag-access.
Maaari mong basahin ang higit pa sa artikulo.

Eh. Hindi na ito naging maayos muli

dulo ng file. Kaya, ang mga log entry na isinagawa ng iba't ibang proseso ay hindi kailanman pinaghalo. Ang mas modernong Unix system ay nagbibigay ng espesyal na serbisyo ng syslog(3C) para sa pag-log.

Mga kalamangan:

  1. Dali ng pag-unlad. Sa katunayan, nagpapatakbo kami ng maraming kopya ng iisang sinulid na application at tumatakbo ang mga ito nang hiwalay sa isa't isa. Hindi mo kailangang gumamit ng anumang partikular na multi-threaded na mga API at interprocess na mga kasangkapan sa komunikasyon.
  2. Mataas na pagiging maaasahan. Ang hindi normal na pagwawakas ng anumang proseso ay hindi nakakaapekto sa iba pang mga proseso sa anumang paraan.
  3. Magandang pagpaparaya. Ang application ay gagana sa anumang multitasking OS
  4. Mataas na seguridad. Maaaring tumakbo ang iba't ibang proseso ng application bilang magkakaibang mga user. Sa ganitong paraan, posibleng ipatupad ang prinsipyo ng hindi bababa sa pribilehiyo, kapag ang bawat proseso ay may mga karapatan lamang na kailangan nitong gumana. Kahit na may natuklasang bug sa isa sa mga prosesong nagbibigay-daan sa pagpapatupad ng malayuang code, makukuha lang ng attacker ang antas ng pag-access kung saan isinagawa ang prosesong ito.

Mga kapintasan:

  1. Hindi lahat ng inilapat na gawain ay maibibigay sa ganitong paraan. Halimbawa, ang arkitektura na ito ay angkop para sa isang server na naghahatid ng mga static na HTML na pahina, ngunit ganap na hindi angkop para sa isang database server at maraming mga application server.
  2. Ang paglikha at pagsira ng mga proseso ay isang mamahaling operasyon, kaya ang arkitektura na ito ay hindi pinakamainam para sa maraming mga gawain.

Ang mga sistema ng Unix ay nagsasagawa ng isang buong hanay ng mga hakbang upang makagawa ng paglikha ng isang proseso at pagpapatakbo ng isang bagong programa sa isang proseso nang mura hangga't maaari. Gayunpaman, kailangan mong maunawaan na ang paglikha ng isang thread sa loob ng isang kasalukuyang proseso ay palaging magiging mas mura kaysa sa paglikha ng isang bagong proseso.

Mga halimbawa: apache 1.x (HTTP server)

Mga multiprocess na application na nakikipag-ugnayan sa pamamagitan ng System V IPC sockets, pipes, at message queues

Ang nakalistang mga tool ng IPC (Interprocess communication) ay nabibilang sa tinatawag na harmonic interprocess communication tools. Pinapayagan ka nitong ayusin ang pakikipag-ugnayan ng mga proseso at mga thread nang hindi gumagamit ng nakabahaging memorya. Ang mga programming theorist ay labis na mahilig sa arkitektura na ito dahil halos tinatanggal nito ang maraming uri ng mga error sa kumpetisyon.

Mga kalamangan:

  1. Relatibong kadalian ng pag-unlad.
  2. Mataas na pagiging maaasahan. Ang hindi normal na pagwawakas ng isa sa mga proseso ay nagiging sanhi ng pagsara ng pipe o socket, at sa kaso ng mga pila ng mensahe, sa katotohanan na ang mga mensahe ay hindi na pumapasok o nakuha mula sa pila. Ang natitirang mga proseso ng application ay madaling matukoy ang error na ito at mabawi mula dito, posibleng (ngunit hindi kinakailangan) sa pamamagitan lamang ng pag-restart ng nabigong proseso.
  3. Maraming mga naturang application (lalo na ang mga nakabatay sa mga socket) ay madaling muling idisenyo upang tumakbo sa isang distributed na kapaligiran, kung saan ang iba't ibang bahagi ng application ay tumatakbo sa iba't ibang machine.
  4. Magandang pagpaparaya. Tatakbo ang application sa karamihan ng mga multitasking operating system, kabilang ang mga mas lumang Unix system.
  5. Mataas na seguridad. Maaaring tumakbo ang iba't ibang proseso ng application bilang magkakaibang mga user. Sa ganitong paraan, posibleng ipatupad ang prinsipyo ng hindi bababa sa pribilehiyo, kapag ang bawat proseso ay may mga karapatan lamang na kailangan nitong gumana.

Kahit na may natuklasang bug sa isa sa mga prosesong nagbibigay-daan sa pagpapatupad ng malayuang code, makukuha lang ng attacker ang antas ng pag-access kung saan isinagawa ang prosesong ito.

Mga kapintasan:

  1. Ang ganitong arkitektura ay hindi madaling bumuo at ipatupad para sa lahat ng mga problema sa aplikasyon.
  2. Ang lahat ng nakalistang uri ng mga tool ng IPC ay nangangailangan ng serial data transmission. Kung kinakailangan ang random na pag-access sa nakabahaging data, ang arkitektura na ito ay hindi maginhawa.
  3. Ang paglilipat ng data sa pamamagitan ng pipe, socket, at message queue ay nangangailangan ng pagsasagawa ng mga system call at pagkopya ng data nang dalawang beses—una mula sa address space ng source process hanggang sa address space ng kernel, pagkatapos ay mula sa address space ng kernel hanggang sa memory target na proseso. Ito ay mga mamahaling operasyon. Kapag naglilipat ng malaking halaga ng data, maaari itong maging isang seryosong problema.
  4. Karamihan sa mga system ay may mga limitasyon sa kabuuang bilang ng mga tubo, socket, at mga pasilidad ng IPC. Kaya, sa Solaris, bilang default, hindi hihigit sa 1024 na bukas na mga tubo, socket at mga file ang pinapayagan sa bawat proseso (ito ay dahil sa mga limitasyon ng piniling tawag sa system). Ang limitasyon sa arkitektura ng Solaris ay 65536 na mga tubo, socket at mga file sa bawat proseso.

    Ang limitasyon sa kabuuang bilang ng mga TCP/IP socket ay hindi hihigit sa 65536 bawat network interface (dahil sa TCP header format). Ang mga pila ng mensahe ng System V IPC ay matatagpuan sa puwang ng address ng kernel, kaya mayroong mahigpit na mga paghihigpit sa bilang ng mga pila sa system at sa dami at bilang ng mga mensahe na maaaring i-queue nang sabay-sabay.

  5. Ang paglikha at pagsira ng isang proseso, at ang paglipat sa pagitan ng mga proseso ay mamahaling operasyon. Ang arkitektura na ito ay hindi pinakamainam sa lahat ng kaso.

Multiprocess na mga application na nakikipag-ugnayan sa pamamagitan ng shared memory

Ang shared memory ay maaaring System V IPC shared memory at file-to-memory mapping. Upang i-synchronize ang access, maaari mong gamitin ang System V IPC semaphores, mutexes at POSIX semaphores, at kapag nagmamapa ng mga file sa memorya, kumuha ng mga seksyon ng file.

Mga kalamangan:

  1. Mahusay na random na pag-access sa nakabahaging data. Ang arkitektura na ito ay angkop para sa pagpapatupad ng mga server ng database.
  2. Mataas na pagpaparaya. Maaaring i-port sa anumang operating system na sumusuporta o tumutulad sa System V IPC.
  3. Medyo mataas na seguridad. Maaaring patakbuhin ang iba't ibang proseso ng aplikasyon sa ngalan ng iba't ibang user. Sa ganitong paraan, posibleng ipatupad ang prinsipyo ng hindi bababa sa pribilehiyo, kapag ang bawat proseso ay may mga karapatan lamang na kailangan nitong gumana. Gayunpaman, ang paghihiwalay ng mga antas ng pag-access ay hindi kasing higpit ng mga naunang tinalakay na arkitektura.

Mga kapintasan:

  1. Relatibong pagiging kumplikado ng pag-unlad. Ang mga error sa pag-synchronize ng pag-access - tinatawag na mga error sa lahi - ay napakahirap matukoy sa panahon ng pagsubok.

    Maaari itong magresulta sa 3 hanggang 5 beses na mas mataas na kabuuang gastos sa pagpapaunlad kumpara sa single-threaded o mas simpleng multi-tasking na mga arkitektura.

  2. Mababang pagiging maaasahan. Ang abortive na pagwawakas ng anuman sa mga proseso ng aplikasyon ay maaaring (at kadalasan ay) mag-iwan ng nakabahaging memorya sa isang hindi pare-parehong estado.

    Madalas itong nagiging sanhi ng pag-crash ng iba pang mga gawain sa application. Ang ilang mga application, tulad ng Lotus Domino, ay partikular na pumapatay sa mga proseso sa buong server kung alinman sa mga ito ay hindi normal na nagwawakas.

  3. Ang paglikha at pagsira ng isang proseso at paglipat sa pagitan ng mga ito ay mamahaling operasyon.

    Samakatuwid, ang arkitektura na ito ay hindi pinakamainam para sa lahat ng mga aplikasyon.

  4. Sa ilang partikular na sitwasyon, ang paggamit ng shared memory ay maaaring humantong sa pagtaas ng pribilehiyo. Kung may nakitang error sa isa sa mga proseso na humahantong sa malayuang pagpapatupad ng code, may mataas na posibilidad na magagamit ito ng isang umaatake upang malayuang magsagawa ng code sa iba pang mga proseso ng application.

    Iyon ay, sa pinakamasamang kaso, ang isang umaatake ay maaaring makakuha ng antas ng pag-access na naaayon sa pinakamataas na antas ng pag-access ng mga proseso ng aplikasyon.

  5. Ang mga application na gumagamit ng shared memory ay dapat tumakbo sa parehong pisikal na computer, o hindi bababa sa mga machine na may shared RAM. Sa katotohanan, ang limitasyong ito ay maaaring iwasan, halimbawa sa pamamagitan ng paggamit ng memory-mapped shared files, ngunit ito ay nagpapakilala ng makabuluhang overhead

Sa katunayan, pinagsasama ng arkitektura na ito ang mga disadvantages ng multi-process at multi-threaded na mga application. Gayunpaman, maraming sikat na application na binuo noong 80s at unang bahagi ng 90s, bago na-standardize ang multithreading API ng Unix, gamitin ang arkitektura na ito. Ito ay maraming mga database server, parehong komersyal (Oracle, DB2, Lotus Domino), freeware, modernong bersyon ng Sendmail at ilang iba pang mga mail server.

Talagang mga multi-threaded na application

Ang mga thread o thread ng application ay tumatakbo sa loob ng isang proseso. Ang buong puwang ng address ng proseso ay ibinabahagi sa pagitan ng mga thread. Sa unang tingin, tila nagbibigay-daan ito sa iyo na ayusin ang pakikipag-ugnayan sa pagitan ng mga thread nang walang anumang espesyal na API. Sa katotohanan, hindi ito totoo - kung maraming mga thread ang gumagana sa isang nakabahaging istraktura ng data o mapagkukunan ng system, at kahit isa sa mga thread ay nagbabago sa istrukturang ito, kung gayon sa ilang mga punto sa oras, ang data ay hindi magkatugma.

Samakatuwid, ang mga thread ay dapat gumamit ng mga espesyal na paraan upang makipag-usap. Ang pinakamahalagang feature ay ang mutual exclusion primitives (mutexes at read-write lock). Gamit ang mga primitive na ito, masisiguro ng programmer na walang thread ang nag-a-access ng mga nakabahaging mapagkukunan habang sila ay nasa isang hindi pare-parehong estado (ito ay tinatawag na mutual exclusion). System V IPC, tanging ang mga istrukturang matatagpuan sa shared memory segment ang ibinabahagi. Ang mga regular na variable at dynamic na istruktura ng data na inilalaan sa karaniwang paraan ay natatangi sa bawat proseso). Ang mga error sa pag-access sa nakabahaging data - mga error sa lahi - ay napakahirap makita sa pagsubok.

  • Ang mataas na halaga ng pagbuo at pag-debug ng mga application, dahil sa punto 1.
  • Mababang pagiging maaasahan. Ang pagkasira ng mga istruktura ng data, halimbawa dahil sa mga buffer overflow o mga error sa pointer, ay nakakaapekto sa lahat ng mga thread ng proseso at kadalasang humahantong sa isang abnormal na pagwawakas ng buong proseso. Ang iba pang nakamamatay na mga error, tulad ng paghahati ng zero sa isa sa mga thread, ay kadalasang nagiging sanhi ng pag-crash ng lahat ng mga thread sa proseso.
  • Mababang seguridad. Ang lahat ng mga thread ng application ay isinasagawa sa parehong proseso, iyon ay, sa ilalim ng parehong user name at may parehong mga karapatan sa pag-access. Hindi posibleng ipatupad ang prinsipyo ng hindi bababa sa pribilehiyo;
  • Ang paglikha ng isang thread ay isang medyo mahal na operasyon. Ang bawat thread ay kinakailangang maglaan ng sarili nitong stack, na bilang default ay tumatagal ng 1 megabyte ng RAM sa 32-bit na mga arkitektura at 2 megabytes sa 64-bit na mga arkitektura, at ilang iba pang mapagkukunan. Samakatuwid, ang arkitektura na ito ay hindi pinakamainam para sa lahat ng mga aplikasyon.
  • Kawalan ng kakayahan na patakbuhin ang application sa isang multi-machine computing system. Ang mga diskarteng binanggit sa nakaraang seksyon, tulad ng memory mapping ng mga nakabahaging file, ay hindi naaangkop sa isang multi-threaded program.
  • Sa pangkalahatan, masasabi na ang mga multi-threaded na application ay may halos parehong mga pakinabang at disadvantages gaya ng mga multi-process na application gamit ang shared memory.

    Gayunpaman, ang halaga ng pagpapatakbo ng isang multithreaded na application ay mas mababa, at ang pagbuo ng naturang application ay sa ilang aspeto ay mas madali kaysa sa isang shared memory application. Samakatuwid, sa mga nakaraang taon, ang mga multi-threaded na application ay naging mas at mas popular.