Mifumo yenye nyuzi nyingi hutumiwa kwa madhumuni gani? Michakato na nyuzi. Multitasking na multithreading. Shida za kukuza programu kwa mazingira yenye nyuzi nyingi. Multithreading ni nini

Mfano wa kujenga programu rahisi yenye nyuzi nyingi.

Alizaliwa kuhusu sababu ya idadi kubwa ya maswali kuhusu kujenga programu zenye nyuzi nyingi huko Delphi.

Madhumuni ya mfano huu ni kuonyesha jinsi ya kuunda vizuri programu yenye nyuzi nyingi, na kazi ya muda mrefu ikihamishwa kwa uzi tofauti. Na jinsi katika programu hiyo ili kuhakikisha mwingiliano kati ya thread kuu na thread ya mfanyakazi kuhamisha data kutoka kwa fomu (vipengele vya kuona) hadi thread na nyuma.

Mfano haudai kuwa kamili; inaonyesha tu njia rahisi zaidi za mwingiliano kati ya nyuzi. Kuruhusu mtumiaji "kuunda haraka" (ambaye anajua ni kiasi gani siipendi hii) programu ya nyuzi nyingi inayofanya kazi kwa usahihi.
Kila kitu kinatolewa maoni kwa undani (kwa maoni yangu), lakini ikiwa una maswali yoyote, uulize.
Lakini nakuonya tena: Mitiririko si rahisi. Ikiwa haujui jinsi yote yanavyofanya kazi, basi kuna hatari kubwa kwamba mara nyingi kila kitu kitafanya kazi vizuri kwako, na wakati mwingine programu itafanya zaidi ya kushangaza. Tabia ya programu iliyoandikwa kwa njia isiyo sahihi sana inategemea idadi kubwa ya mambo ambayo wakati mwingine haiwezekani kuzaliana wakati wa kurekebisha.

Hivyo mfano. Kwa urahisi, nimejumuisha msimbo na kuambatisha kumbukumbu na moduli na msimbo wa fomu

kitengo cha ExThreadForm;

matumizi
Windows, Messages, SysUtils, Lahaja, Madarasa, Michoro, Vidhibiti, Fomu,
Maongezi, StdCtrls;

// vidhibiti vinavyotumika wakati wa kuhamisha data kutoka kwa mkondo hadi kwa fomu inayotumia
// tuma ujumbe wa dirisha
const
WM_USER_SendMessageMetod = WM_USER+10;
WM_USER_PostMessageMetod = WM_USER+11;

aina
// maelezo ya darasa la nyuzi, kizazi cha tThread
tMyThread = darasa(tThread)
Privat
SyncDataN:Nambari kamili;
SyncDataS:String;
utaratibu SyncMetod1;
kulindwa
kutekeleza utaratibu; kubatilisha;
umma
Param1:Kamba;
Param2: Nambari kamili;
Param3:Boolean;
Imesimamishwa:Boolean;
LastRandom:Nambari kamili;
Nambari ya Kurudia: Nambari kamili;
Orodha ya matokeo:tStringList;

Unda Mjenzi(aParam1:Kamba);
mharibifu Kuharibu; kubatilisha;
mwisho;

// maelezo ya darasa la fomu kwa kutumia mkondo
TForm1 = darasa (TForm)
Lebo1: TLabel;
Memo1: TMemo;
btnStart: TButton;
btnStop: TButton;
Hariri1: Thariri;
Hariri2: Thariri;
Kisanduku cha kuteua1: TCheckBox;
Lebo2: TLabel;
Lebo3: TLabel;
Lebo4: TLabel;
utaratibu btnStartClick(Mtumaji: TObject);
utaratibu btnStopClick(Mtumaji: TObject);
Privat
(Matangazo ya kibinafsi)
MyThread:tMyThread;
utaratibu EventMyThreadOnTerminate(Sender:tObject);
utaratibu EventOnSendMessageMetod (var Msg: TMessage);ujumbe WM_USER_SendMessageMetod;
utaratibu EventOnPostMessageMetod(var Msg: TMessage); ujumbe WM_USER_PostMessageMetod;

Hadharani
(Matangazo ya umma)
mwisho;

var
Fomu1: TForm1;

{
Imesimamishwa - inaonyesha uhamisho wa data kutoka kwa fomu hadi thread.
Haihitaji maingiliano ya ziada kwa kuwa ni rahisi
aina ya neno moja, na imeandikwa na uzi mmoja tu.
}

utaratibu TForm1.btnStartClick(Mtumaji: TObject);
kuanza
Nasibu (); // kuhakikisha ubadhirifu katika mlolongo kwa kutumia Random() - haina uhusiano wowote na mtiririko

// Unda mfano wa kitu cha thread, ukipitisha parameter ya pembejeo
{
TAZAMA!
Mjenzi wa thread ameandikwa kwa namna ambayo thread inaundwa
kusimamishwa kwa sababu inaruhusu:
1. Dhibiti wakati wa uzinduzi wake. Hii ni karibu kila wakati rahisi zaidi, kwa sababu ...
hukuruhusu kusanidi mtiririko hata kabla ya kuzindua, kupitisha ingizo
vigezo, nk.
2. Kwa sababu kiungo kwa kitu kilichoundwa kitahifadhiwa kwenye uwanja wa fomu, basi
baada ya uharibifu wa kujitegemea wa thread (tazama hapa chini) ambayo hutokea wakati thread inaendesha
inaweza kutokea wakati wowote, kiungo hiki kitakuwa batili.
}
MyThread:= tMyThread.Create(Form1.Edit1.Text);

// Hata hivyo, tangu thread iliundwa kusimamishwa, makosa yoyote
// wakati wa kuanzishwa kwake (kabla ya kuzinduliwa), lazima tuiharibu sisi wenyewe
// kwa nini utumie kujaribu / isipokuwa kuzuia
jaribu

// Kukabidhi kidhibiti cha kukamilisha uzi ambamo tutapokea
// matokeo ya thread, na "batilisha" kiungo kwake
MyThread.OnTerminate:= EventMyThreadOnTerminate;

// Kwa kuwa tutachukua matokeo katika OnTerminate, i.e. kwa kujiangamiza
// mkondo, basi tutajiondolea wasiwasi wa kuiharibu
MyThread.FreeOnTerminate:= Kweli;

// Mfano wa kupitisha vigezo vya pembejeo kupitia mashamba ya kitu cha mkondo, kwa uhakika
// kuunda mfano wakati bado haujaanza.
// Binafsi, napendelea kufanya hivi kupitia vigezo vya yaliyoidhinishwa
// mjenzi (tMyThread.Create)
MyThread.Param2:= StrToInt(Form1.Edit2.Text);

MyThread.Stopped:= Uongo; // pia aina ya parameta, lakini kubadilisha kulingana na
// thread inayoendesha wakati
isipokuwa
// kwa kuwa thread bado haijaanza na haiwezi kujiharibu, tuiharibu "manually"
FreeAndNil(MyThread);
// na kisha acha ubaguzi kuchakatwa kama kawaida
kuinua;
mwisho;

// Kwa kuwa kitu cha thread kimeundwa kwa ufanisi na kusanidiwa, ni wakati wa kuiendesha
MyThread.Rejea;

ShowMessage("Mkondo umeanza");
mwisho;

utaratibu TForm1.btnStopClick(Mtumaji: TObject);
kuanza
// Ikiwa mfano wa thread bado upo, basi uulize kuacha
// Zaidi ya hayo, "tutauliza" tu. Kimsingi, tunaweza pia "kulazimisha", lakini itakuwa
// chaguo la dharura pekee, linalohitaji ufahamu wazi wa haya yote
// jikoni mkondo. Kwa hivyo, haijazingatiwa hapa.
ikiwa Imetumwa(MyThread) basi
MyThread.Imesimamishwa:= Kweli
mwingine
ShowMessage("Uzi haufanyiki!");
mwisho;

utaratibu TForm1.EventOnSendMessageMetod(var Msg: TMessage);
kuanza
// mbinu ya kuchakata ujumbe unaolingana
// katika WParam anwani ya kitu cha tMyThread, katika LParam thamani ya sasa ya LastRandom ya uzi.
na tMyThread(Msg.WParam) huanza
Form1.Label3.Caption:= Umbizo("%d %d %d",);
mwisho;
mwisho;

utaratibu TForm1.EventOnPostMessageMetod(var Msg: TMessage);
kuanza
// njia ya kusindika ujumbe usiolingana
// katika WParam thamani ya sasa ya IterationNo, katika LParam thamani ya sasa ya LastRandom ya thread
Form1.Label4.Caption:= Umbizo("%d %d",);
mwisho;

utaratibu TForm1.EventMyThreadOnTerminate(Sender:tObject);
kuanza
// MUHIMU!
// Njia ya kushughulikia tukio la OnTerminate daima huitwa katika muktadha wa kuu
// thread - hii imehakikishwa na utekelezaji wa tThread. Kwa hiyo, unaweza kwa uhuru
// tumia mali na mbinu za vitu vyovyote

// Ikiwezekana, hakikisha kuwa mfano wa kitu bado upo
ikiwa haijawekwa (MyThread) basi Toka; // ikiwa haipo, basi hakuna cha kufanya

// kupata matokeo ya kazi ya thread ya mfano wa kitu thread
Form1.Memo1.Lines.Add(Format("Uzi uliishia na tokeo %d",));
Form1.Memo1.Lines.AddStrings((Tuma kama tMyThread).ResultList);

// Vunja rejeleo la mfano wa kitu cha nyuzi.
// Kwa kuwa uzi wetu unajiharibu (FreeOnTerminate:= Kweli)
// kisha baada ya kidhibiti cha OnTerminate kukamilika, mfano wa kitu cha nyuzi utakuwa
// kuharibiwa (Bure), na marejeleo yote kwake yatakuwa batili.
// Ili kuzuia kuingia kwenye kiunga kama hicho kwa bahati mbaya, futa MyThread
// Hebu kumbuka tena - hatutaharibu kitu, lakini tu kufuta kiungo. Kitu
// itajiangamiza yenyewe!
MyThread:= Nil;
mwisho;

mjenzi tMyThread.Create(aParam1:String);
kuanza
// Unda mfano wa nyuzi ILIYOSIMAMA (tazama maoni wakati wa kuunda mfano)
kurithi Unda(Kweli);

// Unda vitu vya ndani (ikiwa ni lazima)
ResultList:= tStringList.Create;

// Kupata data ya awali.

// Kunakili data ya pembejeo iliyopitishwa kupitia parameta
Param1:= aParam1;

// Mfano wa kupokea data ya pembejeo kutoka kwa vipengele vya VCL katika mjenzi wa kitu cha thread
// Hii inakubalika katika kesi hii, kwani mjenzi anaitwa katika muktadha
// thread kuu. Kwa hiyo, vipengele vya VCL vinaweza kupatikana hapa.
// Lakini siipendi hii, kwa sababu nadhani ni mbaya wakati thread inajua kitu
// kuhusu aina fulani. Lakini huwezi kufanya nini kwa maandamano.
Param3:= Form1.CheckBox1.Checked;
mwisho;

mharibifu tMyThread.Kuharibu;
kuanza
// uharibifu wa vitu vya ndani
FreeAndNil(Orodha ya Matokeo);
// kuharibu msingi tTread
kurithi;
mwisho;

utaratibu tMyThread.Execute;
var
t:Kadinali;
s:Kamba;
kuanza
IterationNo:= 0; // kihesabu matokeo (nambari ya mzunguko)

// Katika mfano wangu, mwili wa thread ni kitanzi kinachoisha
// au kwa "ombi" la nje la kukamilisha kigezo kilichopitishwa kupitia kibadilishaji Kimesimamishwa,
// au tu kwa kukamilisha mizunguko 5
// Inapendeza zaidi kwangu kuandika hili kupitia kitanzi cha "milele".

Wakati Kweli inaanza

Inc(IterationNo); // nambari ya mzunguko unaofuata

LastRandom:= Nasibu(1000); // nambari ya nasibu - kuonyesha vigezo vinavyopita kutoka kwa mkondo hadi kwenye fomu

T:= Nasibu(5)+1; // wakati ambao tutalala ikiwa hatujasitishwa

// Operesheni nyepesi (kulingana na paramu ya kuingiza)
ikiwa sio Param3 basi
Inc(Param2)
mwingine
Desemba(Param2);

// Toa matokeo ya kati
s:= Umbizo("%s %5d %s %d %d",
);

// Ongeza matokeo ya kati kwenye orodha ya matokeo
Orodha ya matokeo.Ongeza;

//// Mifano ya kupitisha matokeo ya kati kwa fomu

//// Kupitia njia iliyosawazishwa - njia ya classic
//// Makosa:
//// - njia iliyosawazishwa kawaida ni njia ya darasa la nyuzi (kwa ufikiaji
//// kwa mashamba ya kitu cha mkondo), lakini ili kufikia mashamba ya fomu, ni lazima
//// "jua" juu yake na uwanja wake (vitu), ambayo kwa kawaida sio nzuri sana nayo
//// pointi za mtazamo wa shirika la programu.
//// - thread ya sasa itasimamishwa hadi utekelezaji ukamilike
//// mbinu iliyosawazishwa.

//// Manufaa:
//// - kiwango na zima
//// - kwa njia iliyosawazishwa unaweza kutumia
//// nyanja zote za kitu cha mkondo.
// kwanza, ikiwa ni lazima, unahitaji kuhifadhi data iliyopitishwa
// nyanja maalum za kitu cha kitu.
SyncDataN:= IterationNo;
SyncDataS:= "Sawazisha"+s;
// na kisha toa simu ya njia iliyosawazishwa
Sawazisha(SyncMetod1);

//// Usambazaji kupitia utumaji ujumbe unaolandana (SendMessage)
//// katika kesi hii, data inaweza kupitishwa kupitia vigezo vya ujumbe (LastRandom),
//// na kupitia nyanja za kitu, kupitisha anwani ya mfano kwenye parameta ya ujumbe
//// kitu cha mkondo - Nambari (Self).
//// Makosa:
//// - thread lazima ijue kushughulikia dirisha la fomu
//// - kama ilivyo kwa Sawazisha, uzi wa sasa utasimamishwa hadi
//// usindikaji kamili wa ujumbe na uzi kuu
//// - inahitaji muda muhimu wa CPU kwa kila simu
//// (kwa kubadili nyuzi) kwa hivyo simu ya mara kwa mara haifai
//// Manufaa:
//// - kama ilivyo kwa Sawazisha, unapochakata ujumbe unaoweza kutumia
//// nyanja zote za kitu cha mkondo (ikiwa, bila shaka, anwani yake ilipitishwa)


//// anzisha thread.
SendMessage(Form1.Handle,WM_USER_SendMessageMetod,Iteger(Self),LastRandom);

//// Usambazaji kupitia utumaji ujumbe usiolingana (PostMessage)
//// Kwa sababu katika kesi hii, wakati thread kuu inapokea ujumbe,
//// thread ya kutuma inaweza kuwa tayari imekamilika, kupitisha anwani ya mfano
//// kitu cha uzi sio halali!
//// Makosa:
//// - thread lazima ijue kushughulikia dirisha la fomu;
//// - kutokana na asynchrony, uhamisho wa data unawezekana tu kupitia vigezo
//// ujumbe, ambayo inachanganya sana uhamishaji wa data ya saizi
//// zaidi ya maneno mawili ya mashine. Rahisi kutumia kwa kuhamisha Nambari, nk.
//// Manufaa:
//// - tofauti na njia za awali, thread ya sasa HAITAKUWA
//// imesimamishwa, lakini itaanza tena utekelezaji
//// - tofauti na simu iliyosawazishwa, kidhibiti cha ujumbe
//// ni njia ya fomu ambayo lazima iwe na ujuzi wa kitu cha thread,
//// au hujui chochote kuhusu mtiririko ikiwa data inasambazwa pekee
//// kupitia vigezo vya ujumbe. Hiyo ni, thread inaweza kuwa haijui chochote kuhusu fomu
//// kwa ujumla - Hushughulikia yake tu, ambayo inaweza kupitishwa kama parameta hapo awali
//// anzisha thread.
Ujumbe wa Post(Form1.Handle,WM_USER_PostMessageMetod,IterationNo,LastRandom);

//// Angalia kukamilika iwezekanavyo

// Angalia kukamilika kwa parameta
ikiwa Imesimamishwa basi Vunja;

// Angalia kukamilika mara kwa mara
ikiwa IterationNo >= 10 basi Vunja;

Kulala(t*1000); // Kulala kwa sekunde t
mwisho;
mwisho;

utaratibu tMyThread.SyncMetod1;
kuanza
// njia hii inaitwa kutumia njia ya Sawazisha.
// Hiyo ni, licha ya ukweli kwamba ni njia ya thread ya tMyThread,
// inaendesha katika muktadha wa nyuzi kuu ya programu.
// Kwa hiyo, anaweza kufanya kila kitu, au karibu kila kitu :)
// Lakini kumbuka, hakuna haja ya "kuchezea" hapa kwa muda mrefu

// Vigezo vilivyopitishwa, tunaweza kuziondoa kutoka kwenye uwanja maalum ambapo sisi
// imehifadhiwa kabla ya kupiga simu.
Form1.Label1.Caption:= SyncDataS;

// au kutoka kwa nyanja zingine za kitu cha mtiririko, kwa mfano, kuonyesha hali yake ya sasa
Form1.Label2.Caption:= Umbizo("%d %d",);
mwisho;

Kwa ujumla, mfano huo ulitanguliwa na mawazo yangu yafuatayo juu ya mada....

Kwanza:
SHERIA MUHIMU ZAIDI ya utayarishaji wa nyuzi nyingi huko Delphi:
Katika muktadha wa uzi usio kuu, huwezi kufikia mali na mbinu za fomu, na kwa hakika vipengele vyote "vinavyokua" kutoka kwa tWinControl.

Hii inamaanisha (iliyorahisishwa kwa kiasi fulani) kwamba sio katika Njia ya Tekeleza iliyorithiwa kutoka kwa TThread, au kwa njia/taratibu/kazi zingine zinazoitwa kutoka kwa Kutekeleza, ni haramu usifikie moja kwa moja mali yoyote au mbinu za vipengele vya kuona.

Jinsi ya kufanya hivyo kwa haki.
Hakuna mapishi ya kawaida hapa. Kwa usahihi, kuna chaguo nyingi na tofauti ambazo unahitaji kuchagua kulingana na kesi maalum. Ndiyo sababu wanakuelekeza kwenye makala. Baada ya kuisoma na kuielewa, mpangaji programu ataweza kuelewa jinsi bora ya kuifanya katika kesi fulani.

Kwa kifupi:

Mara nyingi, programu inakuwa ya nyuzi nyingi ama wakati inahitajika kufanya kazi ya muda mrefu, au wakati unaweza kufanya mambo kadhaa kwa wakati mmoja ambayo haipakia sana processor.

Katika kesi ya kwanza, kutekeleza kazi ndani ya uzi kuu husababisha "kupunguza kasi" ya kiolesura cha mtumiaji - wakati kazi inafanywa, kitanzi cha usindikaji wa ujumbe hakijatekelezwa. Matokeo yake, programu haijibu kwa vitendo vya mtumiaji, na fomu haijatolewa, kwa mfano, baada ya mtumiaji kuihamisha.

Katika kesi ya pili, wakati kazi inahusisha kubadilishana kazi na ulimwengu wa nje, basi wakati wa "kupungua" kwa kulazimishwa. Wakati wa kusubiri kupokea / kutumwa kwa data, unaweza kufanya kitu kingine kwa sambamba, kwa mfano, tena kutuma / kupokea data nyingine.

Kuna matukio mengine, lakini chini ya kawaida. Walakini, hii haijalishi. Si kuhusu hilo sasa.

Sasa, haya yote yameandikwaje? Kwa kawaida, kesi fulani ya kawaida, kiasi fulani ya jumla, inazingatiwa. Hivyo.

Kazi iliyofanywa kwa uzi tofauti, kwa ujumla, ina vyombo vinne (sijui nini cha kuiita kwa usahihi zaidi):
1. Data ya awali
2. Kazi halisi yenyewe (inaweza kutegemea data chanzo)
3. Data ya kati (kwa mfano, taarifa kuhusu hali ya sasa ya kazi)
4. Pato (matokeo)

Mara nyingi, vipengele vya kuona hutumiwa kusoma na kuonyesha data nyingi. Lakini, kama ilivyotajwa hapo juu, huwezi kupata moja kwa moja vipengee vya kuona kutoka kwa mkondo. Jinsi ya kuwa?
Watengenezaji wa Delphi wanapendekeza kutumia njia ya Sawazisha ya darasa la TThread. Hapa sitaelezea jinsi ya kuitumia - kuna nakala iliyotajwa hapo juu kwa hiyo. Nitasema tu kwamba matumizi yake, hata sahihi, sio haki kila wakati. Kuna matatizo mawili:

Kwanza, mwili wa njia inayoitwa kupitia Synchronize inatekelezwa kila wakati katika muktadha wa uzi kuu, na kwa hivyo, wakati inatekelezwa, kitanzi cha usindikaji wa ujumbe wa dirisha hakijatekelezwa tena. Kwa hiyo, lazima ifanyike haraka, vinginevyo tutapata matatizo yote sawa na utekelezaji wa thread moja. Kwa kweli, njia inayoitwa kupitia Synchronize inapaswa kutumiwa tu kufikia sifa na mbinu za vitu vinavyoonekana.

Pili, kutekeleza njia kupitia Synchronize ni raha "ya gharama kubwa" inayosababishwa na hitaji la swichi mbili kati ya nyuzi.

Kwa kuongezea, shida zote mbili zimeunganishwa na kusababisha mkanganyiko: kwa upande mmoja, ili kutatua ya kwanza, inahitajika "kupasua" njia zinazoitwa Synchronize, na kwa upande mwingine, lazima ziitwe mara nyingi zaidi, zikipoteza thamani. rasilimali za processor.

Kwa hivyo, kama kawaida, unahitaji kukaribia kwa busara, na kwa kesi tofauti, tumia njia tofauti za kuingiliana na ulimwengu wa nje:

Data ya awali
Data zote zinazopitishwa kwenye mkondo na hazibadilika wakati wa uendeshaji wake lazima zipelekwe kabla ya kuzinduliwa, i.e. wakati wa kuunda thread. Ili kuzitumia kwenye mwili wa thread, unahitaji kufanya nakala ya ndani yao (kawaida katika mashamba ya mtoto wa TThread).
Ikiwa kuna data ya chanzo inayoweza kubadilika wakati nyuzi inaendesha, basi ufikiaji wa data kama hiyo lazima ufanywe kupitia njia zilizosawazishwa (mbinu zinazoitwa kupitia Synchronize) au kupitia sehemu za kitu cha nyuzi (kizazi cha TThread). Mwisho unahitaji tahadhari fulani.

Data ya kati na ya pato
Hapa tena kuna njia kadhaa (kwa mpangilio wa upendeleo wangu):
- Njia ya kutuma ujumbe kwa asynchronously kwa dirisha kuu la programu.
Kawaida hutumika kutuma ujumbe kwa dirisha kuu la programu kuhusu hali ya mchakato, kutuma kiasi kidogo cha data (kwa mfano, asilimia ya kukamilika)
- Njia ya kutuma ujumbe kwa usawaziko kwa dirisha kuu la programu.
Kawaida hutumiwa kwa madhumuni sawa na kutuma kwa asynchronous, lakini inakuwezesha kuhamisha kiasi kikubwa cha data bila kuunda nakala tofauti.
- Mbinu zilizosawazishwa, inapowezekana, kuchanganya uhamisho wa data nyingi iwezekanavyo katika njia moja.
Inaweza pia kutumika kupokea data kutoka kwa fomu.
- Kupitia sehemu za kitu cha kutiririsha, kuhakikisha ufikiaji wa kipekee.
Unaweza kusoma zaidi katika makala.

Mh. Haikufanya kazi vizuri tena

E Nakala hii si ya wafugaji wa Python waliobobea, ambao kung'oa msongamano huu wa nyoka ni mchezo wa watoto, lakini ni muhtasari wa juu juu wa uwezo wenye nyuzi nyingi kwa wale ambao wamenaswa na Chatu hivi karibuni.

Kwa bahati mbaya, hakuna nyenzo nyingi kwa Kirusi juu ya mada ya kusoma nyingi huko Python, na nilianza kukutana na Pythoners ambao walikuwa hawajasikia chochote, kwa mfano, juu ya GIL, kwa ukawaida unaowezekana. Katika makala hii nitajaribu kuelezea vipengele vya msingi zaidi vya Python yenye nyuzi nyingi, kukuambia ni nini GIL na jinsi ya kuishi nayo (au bila hiyo), na mengi zaidi.


Python ni lugha ya programu ya kuvutia. Inachanganya kikamilifu dhana nyingi za programu. Shida nyingi ambazo mtunga programu anaweza kukutana nazo hutatuliwa hapa kwa urahisi, kifahari na kwa ufupi. Lakini kwa kazi hizi zote, suluhisho la nyuzi moja mara nyingi linatosha, na programu zenye uzi mmoja kawaida hutabirika na ni rahisi kurekebisha. Vile vile haziwezi kusema juu ya programu nyingi za nyuzi na za michakato mingi.

Programu zenye nyuzi nyingi


Python ina moduli kunyoosha , na ina kila kitu unachohitaji kwa programu ya nyuzi nyingi: kuna aina mbalimbali za kufuli, semaphore, na utaratibu wa tukio. Kwa neno moja - kila kitu unachohitaji kwa idadi kubwa ya programu zenye nyuzi nyingi. Kwa kuongeza, kutumia zana hizi zote ni rahisi sana. Wacha tuangalie mfano wa programu inayoendesha nyuzi 2. Kamba moja inaandika "0" kumi, nyingine - kumi "1", na madhubuti kwa zamu.

kuagiza threading

mwandishi wa def

kwa mimi katika xrange(10 ):

chapa x

Event_for_set.set()

Matukio # ya mwanzo

e1 = threading.Tukio()

e2 = threading.Tukio()

nyuzi # za init

0, e1, e2))

1, e2, e1))

# anzisha mazungumzo

t1.start()

t2.anza()

t1.jiunge()

t2.jiunge()


Hakuna uchawi au msimbo wa voodoo. Kanuni ni wazi na thabiti. Zaidi ya hayo, kama unaweza kuona, tuliunda thread kutoka kwa chaguo la kukokotoa. Hii ni rahisi sana kwa kazi ndogo. Nambari hii pia ni rahisi kubadilika. Wacha tuseme tuna mchakato wa 3 ambao unaandika "2", basi nambari itaonekana kama hii:

kuagiza threading

mwandishi wa def (x, tukio_la_ngoja, tukio_la_seti):

kwa mimi katika xrange(10 ):

Event_for_wait.wait() # subiri tukio

Tukio_la_wait.clear() # tukio safi kwa siku zijazo

chapa x

Event_for_set.set() # weka tukio kwa mazungumzo ya jirani

Matukio # ya mwanzo

e1 = threading.Tukio()

e2 = threading.Tukio()

e3 = threading.Tukio()

nyuzi # za init

t1 = threading. Thread(lengo=mwandishi, args=( 0, e1, e2))

t2 = threading. Thread(lengo=mwandishi, args=( 1, e2, e3))

t3 = threading. Thread(lengo=mwandishi, args=( 2, e3, e1))

# anzisha mazungumzo

t1.start()

t2.anza()

t3.start()

e1.set() # anzisha tukio la kwanza

# jiunge na thread kuu

t1.jiunge()

t2.jiunge()

t3.jiunge()


Tuliongeza tukio jipya, thread mpya na kubadilisha kidogo vigezo ambavyo
threads kuanza (unaweza, bila shaka, kuandika ufumbuzi wa jumla zaidi kwa kutumia, kwa mfano, MapReduce, lakini hii ni zaidi ya upeo wa makala hii).
Kama unaweza kuona, bado hakuna uchawi. Kila kitu ni rahisi na wazi. Hebu tuendelee.

Kufuli ya Mkalimani wa Kimataifa


Kuna sababu mbili za kawaida za kutumia nyuzi: kwanza, kuongeza ufanisi wa kutumia usanifu wa msingi wa wasindikaji wa kisasa, na hivyo utendaji wa programu;
pili, ikiwa tunahitaji kugawanya mantiki ya programu katika sehemu zinazofanana, kikamilifu au sehemu ya asynchronous (kwa mfano, kuwa na uwezo wa kuweka seva kadhaa kwa wakati mmoja).

Katika kisa cha kwanza, tunakabiliwa na kizuizi kama hicho cha Python (au tuseme utekelezaji wake kuu wa CPython) kama Lock ya Ukalimani wa Ulimwenguni (au GIL kwa kifupi). Wazo la GIL ni kwamba nyuzi moja tu inaweza kutekelezwa na processor kwa wakati mmoja. Hii inafanywa ili hakuna ushindani kati ya nyuzi kwa vigezo vya mtu binafsi. Uzi wa utekelezaji unaweza kufikia katika mazingira yote. Kipengele hiki cha utekelezaji wa nyuzi katika Python hurahisisha sana kufanya kazi na nyuzi na hutoa usalama fulani wa nyuzi.

Lakini kuna jambo la hila hapa: inaweza kuonekana kuwa programu iliyo na nyuzi nyingi itaendesha wakati sawa na programu iliyo na nyuzi moja inayofanya jambo lile lile, au kwa jumla ya wakati wa utekelezaji wa kila uzi kwenye CPU. . Lakini hapa athari moja isiyofurahi inangojea. Wacha tuangalie programu:

with open("test1.txt" , "w") kama fout:

kwa mimi katika xrange(1000000):

chapisha >> fout, 1


Programu hii huandika mistari milioni moja ya "1" kwa faili na kuifanya kwa sekunde ~0.35 kwenye kompyuta yangu.

Wacha tuangalie programu nyingine:

kutoka kwa threading import Thread

mwandishi wa def (jina la faili, n):

na open(filename, "w" ) kama fout:

kwa mimi katika xrange(n):

chapisha >> fout, 1

t1 = Thread(lengo=mwandishi, args=("test2.txt", 500000 ,))

t2 = Thread(lengo=mwandishi, args=("test3.txt", 500000 ,))

t1.start()

t2.anza()

t1.jiunge()

t2.jiunge()


Programu hii inaunda nyuzi 2. Katika kila mkondo, huandika mistari nusu milioni ya "1" kwa faili tofauti. Kimsingi kiasi cha kazi ni sawa na mpango uliopita. Lakini baada ya muda, athari ya kuvutia inaonekana. Programu inaweza kukimbia kutoka sekunde 0.7 hadi sekunde 7. Kwa nini hii inatokea?

Hii hutokea kwa sababu wakati thread haihitaji rasilimali ya CPU, inafungua GIL, na wakati huo wote yenyewe, thread nyingine, na pia thread kuu inaweza kujaribu kuipata. Wakati huo huo, mfumo wa uendeshaji, ukijua kwamba kuna cores nyingi, unaweza kuimarisha kila kitu kwa kujaribu kusambaza threads kati ya cores.

UPD: kwa sasa, katika Python 3.2 kuna utekelezaji bora wa GIL, ambayo tatizo hili linatatuliwa kwa sehemu, hasa, kutokana na ukweli kwamba kila thread, baada ya kupoteza udhibiti, inasubiri muda mfupi kabla ya kuanza tena. kunyakua GIL (kuna thread juu ya mada hii presentation nzuri katika Kiingereza)

"Inabadilika kuwa huwezi kuandika programu zenye nyuzi nyingi kwenye Python?" unauliza. Hapana, bila shaka, kuna njia ya nje, na hata kadhaa.

Programu za Michakato mingi


Ili kutatua shida iliyoelezewa katika aya iliyotangulia, Python ina moduli mchakato mdogo . Tunaweza kuandika programu ambayo tunataka kutekeleza katika uzi sambamba (kwa kweli tayari ni mchakato). Na uiendeshe kwa nyuzi moja au zaidi katika programu nyingine. Njia hii ingeharakisha utendakazi wa programu yetu, kwa sababu nyuzi zilizoundwa kwenye kizindua cha GIL hazichukui nafasi, lakini subiri tu kukamilika kwa mchakato wa kukimbia. Hata hivyo, kuna matatizo mengi na njia hii. Tatizo kuu ni kwamba inakuwa vigumu kuhamisha data kati ya taratibu. Tungelazimika kwa njia fulani kusawazisha vitu, kuanzisha mawasiliano kupitia PIPE au zana zingine, lakini haya yote bila shaka hubeba juu na nambari inakuwa ngumu kuelewa.

Mbinu tofauti inaweza kutusaidia hapa. Python ina moduli ya usindikaji nyingi . Katika utendakazi moduli hii inafanana kunyoosha . Kwa mfano, taratibu zinaweza kuundwa kwa njia sawa kutoka kwa kazi za kawaida. Njia za kufanya kazi na michakato ni karibu sawa na kwa nyuzi kutoka kwa moduli ya kuunganisha. Lakini kusawazisha michakato na kubadilishana data, ni kawaida kutumia zana zingine. Tunazungumza juu ya foleni (Foleni) na njia (Bomba). Walakini, analogi za kufuli, hafla na semaphores ambazo zilikuwa kwenye nyuzi pia zipo hapa.

Kwa kuongeza, moduli ya multiprocessing ina utaratibu wa kufanya kazi na kumbukumbu iliyoshirikiwa. Kwa kusudi hili, moduli ina madarasa ya kutofautiana (Thamani) na safu (Array), ambayo inaweza "kushirikiwa" kati ya taratibu. Ili kurahisisha kufanya kazi na vigeu vilivyoshirikiwa, unaweza kutumia madarasa ya Meneja. Wao ni rahisi zaidi na rahisi kutumia, lakini polepole. Inastahili kuzingatia uwezo mzuri wa kutengeneza aina za kawaida kutoka kwa moduli ya ctypes kwa kutumia moduli ya multiprocessing.sharedctypes.

Moduli ya usindikaji wa aina nyingi pia ina utaratibu wa kuunda mabwawa ya mchakato. Utaratibu huu ni rahisi sana kutumia kwa ajili ya kutekeleza muundo wa Mwalimu-Mfanyakazi au kwa kutekeleza Ramani inayofanana (ambayo kwa maana fulani ni kesi maalum ya Mwalimu-Mfanyakazi).

Miongoni mwa matatizo makuu ya kufanya kazi na moduli ya multiprocessing, ni muhimu kuzingatia utegemezi wa jukwaa la jamaa la moduli hii. Kwa kuwa kazi na taratibu hupangwa tofauti katika mifumo tofauti ya uendeshaji, vikwazo vingine vinawekwa kwenye kanuni. Kwa mfano, Windows haina utaratibu wa uma, kwa hivyo hatua ya kutenganisha mchakato lazima iwekwe ndani:

kama __name__ == "__main__" :


Hata hivyo, kubuni hii tayari iko katika fomu nzuri.

Nini kingine...


Kuna maktaba zingine na njia za kuandika maombi sambamba katika Python. Kwa mfano, unaweza kutumia Hadoop+Python au utekelezaji mbalimbali wa MPI katika Python (pyMPI, mpi4py). Unaweza kutumia viboreshaji vya maktaba zilizopo za C++ au Fortran. Hapa tunaweza kutaja mifumo/maktaba kama vile Pyro, Twisted, Tornado na nyinginezo nyingi. Lakini yote haya ni zaidi ya upeo wa makala hii.

Ikiwa ulipenda mtindo wangu, basi katika makala inayofuata nitajaribu kukuambia jinsi ya kuandika wakalimani rahisi katika PLY na nini wanaweza kutumika.

Machapisho ya hapo awali yalizungumza kuhusu usomaji mwingi katika Windows kwa kutumia CreateThread na WinAPI nyingine, na pia usomaji mwingi katika Linux na mifumo mingine ya *nix kwa kutumia nyuzi. Ukiandika katika C++11 au matoleo mapya zaidi, unaweza kufikia std::thread na viasili vingine vya kuunganisha vilivyoletwa katika kiwango hicho cha lugha. Ifuatayo, tutakuonyesha jinsi ya kufanya kazi nao. Tofauti na WinAPI na pthreads, nambari iliyoandikwa kwa std::thread ni jukwaa la msalaba.

Kumbuka: Nambari iliyo hapo juu ilijaribiwa kwenye GCC 7.1 na Clang 4.0 chini ya Arch Linux, GCC 5.4 na Clang 3.8 chini ya Ubuntu 16.04 LTS, GCC 5.4 na Clang 3.8 chini ya FreeBSD 11, pamoja na Visual Studio Community 2017 chini ya Windows 10. CMake kabla ya toleo la 3.8. zungumza mkusanyaji atumie kiwango cha C++17 kilichobainishwa katika sifa za mradi. Jinsi ya kufunga CMake 3.8 kwenye Ubuntu 16.04. Ili msimbo utungwe kwa kutumia Clang, kifurushi cha libc++ lazima kisakinishwe kwenye mifumo ya *nix. Kwa Arch Linux kifurushi kinapatikana kwenye AUR. Ubuntu ana kifurushi cha libc++-dev, lakini unaweza kukutana na tatizo ambalo linazuia msimbo kujenga kwa urahisi. Workround inaelezewa kwenye StackOverflow. Kwenye FreeBSD, ili kuunda mradi unahitaji kusakinisha kifurushi cha moduli za cmake.

Vibubu

Chini ni mfano rahisi wa kutumia nyuzi na bubu:

#pamoja na
#pamoja na
#pamoja na
#pamoja na

Std::mutex mtx;
tuli int counter = 0;


kwa (;;) (
{
std::lock_guard< std:: mutex >kufuli (mtx);

mapumziko;
int ctr_val = ++ counter;
std::cout<< "Thread " << tnum << ": counter = " <<
ctr_val<< std:: endl ;
}

}
}

int kuu() (
std::vekta< std:: thread >nyuzi;
kwa ( in i = 0 ; i< 10 ; i++ ) {


}

// haiwezi kutumia const auto& hapa kwani .join() haijawekwa alama const

thr.join();
}

Std::cout<< "Done!" << std:: endl ;
kurudi 0;
}

Kumbuka kufungwa kwa std::mutex katika std::lock_guard kwa mujibu wa nahau ya RAII. Njia hii inahakikisha kwamba bubu itatolewa wakati wa kuondoka kwa upeo kwa hali yoyote, ikiwa ni pamoja na wakati isipokuwa hutokea. Ili kunasa bubu kadhaa kwa wakati mmoja ili kuzuia kufuli, kuna std::scoped_lock class. Walakini, ilionekana tu katika C++17 na kwa hivyo inaweza isifanye kazi kila mahali. Kwa matoleo ya awali ya C++, kuna template std::lock ambayo ni sawa katika utendakazi, ingawa inahitaji kuandika msimbo wa ziada ili kutoa kufuli kwa usahihi kwa kutumia RAII.

RWLock

Hali mara nyingi hutokea ambapo kitu hupatikana mara nyingi kwa kusoma kuliko kuandika. Katika kesi hii, badala ya bubu ya kawaida, ni bora zaidi kutumia kufuli ya kusoma-kuandika, inayojulikana pia kama RWLock. RWLock inaweza kushikiliwa na nyuzi kadhaa za kusoma mara moja, au kwa uzi mmoja tu wa uandishi. RWLock katika C++ inalingana na madarasa std::shared_mutex na std::shared_timed_mutex:

#pamoja na
#pamoja na
#pamoja na
#pamoja na

// std::shared_mutex mtx; // haitafanya kazi na GCC 5.4
std::shared_timed_mutex mtx;

tuli int counter = 0;
tuli const int MAX_COUNTER_VAL = 100 ;

thread_proc batili (int tnum) (
kwa (;;) (
{
// tazama pia std::shared_lock
std::kipekee_lock< std:: shared_timed_mutex >kufuli (mtx);
ikiwa (kaunta == MAX_COUNTER_VAL)
mapumziko;
int ctr_val = ++ counter;
std::cout<< "Thread " << tnum << ": counter = " <<
ctr_val<< std:: endl ;
}
std::uzi_huu::lala_kwa(std::chrono::milliseconds(10));
}
}

int kuu() (
std::vekta< std:: thread >nyuzi;
kwa ( in i = 0 ; i< 10 ; i++ ) {
std:: thread thr(thread_proc, i);
threads.emplace_back(std::move(thr));
}

kwa (otomatiki & thr: nyuzi) (
thr.join();
}

Std::cout<< "Done!" << std:: endl ;
kurudi 0;
}

Kwa mlinganisho na std::lock_guard, madarasa std::kipekee_kufuli na std::shared_lock hutumiwa kunasa RWLock, kulingana na jinsi tunavyotaka kunasa kufuli. Darasa la std::shared_timed_mutex lilionekana katika C++14 na linafanya kazi kwenye mifumo yote* ya kisasa (bila kusahau vifaa vya rununu, vidhibiti vya mchezo, na kadhalika). Tofauti na std::shared_mutex, ina mbinu try_lock_for, try_lock_unti na zingine zinazojaribu kufunga bubu ndani ya muda fulani. Ninashuku sana kwamba std::shared_mutex lazima iwe nafuu kuliko std::shared_timed_mutex. Hata hivyo, std::shared_mutex ilionekana katika C++17 pekee, ambayo ina maana kwamba haitumiki kila mahali. Hasa, GCC 5.4 ambayo bado inatumika sana haijui kuihusu.

Hifadhi ya Ndani ya Thread

Wakati mwingine unahitaji kuunda tofauti, kama ya kimataifa, lakini ambayo thread moja tu inaweza kuona. Nyuzi zingine pia huona kutofautisha, lakini kwao ina maana yake ya ndani. Kwa hili walikuja na Thread Local Storage, au TLS (haina uhusiano wowote na Transport Layer Security!). Miongoni mwa mambo mengine, TLS inaweza kutumika kuongeza kasi ya uzalishaji wa nambari za pseudorandom. Mfano wa kutumia TLS katika C++:

#pamoja na
#pamoja na
#pamoja na
#pamoja na

Std::mutex io_mtx;
thread_local int counter = 0;
tuli const int MAX_COUNTER_VAL = 10 ;

thread_proc batili (int tnum) (
kwa (;;) (
counter++;
ikiwa (kaunta == MAX_COUNTER_VAL)
mapumziko;
{
std::lock_guard< std:: mutex >kufuli (io_mtx);
std::cout<< "Thread " << tnum << ": counter = " <<
kaunta<< std:: endl ;
}
std::uzi_huu::lala_kwa(std::chrono::milliseconds(10));
}
}

int kuu() (
std::vekta< std:: thread >nyuzi;
kwa ( in i = 0 ; i< 10 ; i++ ) {
std:: thread thr(thread_proc, i);
threads.emplace_back(std::move(thr));
}

kwa (otomatiki & thr: nyuzi) (
thr.join();
}

Std::cout<< "Done!" << std:: endl ;
kurudi 0;
}

Kibubu hapa kinatumika tu kusawazisha pato kwa koni. Hakuna ulandanishi unaohitajika ili kufikia vigeuzo vya thread_local.

Vigezo vya atomiki

Vigezo vya atomiki mara nyingi hutumiwa kufanya shughuli rahisi bila matumizi ya bubu. Kwa mfano, unahitaji kuongeza kihesabu kutoka kwa nyuzi nyingi. Badala ya kufunga int katika std::mutex, ni bora zaidi kutumia std::atomic_int. C++ pia inatoa aina std::atomic_char, std::atomic_bool na zingine nyingi. Algorithms zisizo na kufuli na miundo ya data pia hutekelezwa kwa kutumia vigeu vya atomiki. Ni vyema kutambua kwamba ni vigumu sana kuendeleza na kurekebisha, na haifanyi kazi kwa kasi zaidi kuliko algorithms sawa na miundo ya data yenye kufuli kwenye mifumo yote.

Msimbo wa sampuli:

#pamoja na
#pamoja na
#pamoja na
#pamoja na
#pamoja na

tuli std:: atomic_int atomic_counter(0) ;
tuli const int MAX_COUNTER_VAL = 100 ;

Std::mutex io_mtx;

thread_proc batili (int tnum) (
kwa (;;) (
{
int ctr_val = ++ atomic_counter;
ikiwa (ctr_val >= MAX_COUNTER_VAL)
mapumziko;

{
std::lock_guard< std:: mutex >kufuli (io_mtx);
std::cout<< "Thread " << tnum << ": counter = " <<
ctr_val<< std:: endl ;
}
}
std::uzi_huu::lala_kwa(std::chrono::milliseconds(10));
}
}

int kuu() (
std::vekta< std:: thread >nyuzi;

int nthreads = std::thread::hardware_concurrency();
ikiwa (nyuzi == 0) nyuzi = 2;

kwa ( in i = 0 ; i< nthreads; i++ ) {
std:: thread thr(thread_proc, i);
threads.emplace_back(std::move(thr));
}

kwa (otomatiki & thr: nyuzi) (
thr.join();
}

Std::cout<< "Done!" << std:: endl ;
kurudi 0;
}

Kumbuka matumizi ya utaratibu wa hardware_concurrency. Hurejesha makadirio ya idadi ya nyuzi zinazoweza kutekelezwa sambamba na mfumo wa sasa. Kwa mfano, kwenye mashine iliyo na kichakataji cha quad-core kinachoauni hyper threading, utaratibu hurejesha nambari 8. Utaratibu unaweza pia kurejesha sifuri ikiwa tathmini haikuweza kufanywa au utaratibu haujatekelezwa.

Taarifa fulani kuhusu uendeshaji wa vigezo vya atomiki katika ngazi ya mkusanyiko inaweza kupatikana katika makala Karatasi ya Kudanganya kwa Maagizo ya Mkutano wa Msingi x86/x64.

Hitimisho

Kwa kadiri ninavyoona, hii yote inafanya kazi vizuri. Hiyo ni, wakati wa kuandika maombi ya jukwaa la msalaba katika C ++, unaweza kusahau salama kuhusu WinAPI na pthreads. Katika C safi, tangu C11, pia kuna nyuzi za jukwaa la msalaba. Lakini bado haziungwi mkono na Visual Studio (niliangalia), na hakuna uwezekano wa kuungwa mkono. Sio siri kwamba Microsoft haioni nia yoyote ya kuendeleza usaidizi wa lugha ya C katika mkusanyaji wake, ikipendelea kuzingatia C++.

Bado kuna mambo mengi ya zamani yaliyoachwa nyuma ya pazia: std::condition_variable(_yoyote), std::(shared_)future, std::promise, std::sync na zingine. Ninapendekeza cppreference.com kuziangalia. Huenda ikafaa pia kusoma kitabu C++ Concurrency in Action. Lakini lazima nikuonye kwamba sio mpya tena, ina maji mengi, na kwa asili inaelezea nakala kadhaa kutoka kwa cppreference.com.

Toleo kamili la msimbo wa chanzo wa dokezo hili, kama kawaida, liko kwenye GitHub. Je, kwa sasa unaandikaje programu zenye nyuzi nyingi katika C++?

Threads na taratibu ni dhana zinazohusiana katika kompyuta. Zote mbili ni mlolongo wa maagizo ambayo lazima yatekelezwe kwa mpangilio maalum. Maagizo katika nyuzi tofauti au michakato, hata hivyo, inaweza kutekelezwa kwa sambamba.

Taratibu zipo katika mfumo wa uendeshaji na zinalingana na kile watumiaji wanaona kama programu au programu. Thread, kwa upande mwingine, ipo ndani ya mchakato. Kwa sababu hii, nyuzi wakati mwingine huitwa "michakato nyepesi". Kila mchakato una nyuzi moja au zaidi. Uwepo wa michakato mingi inaruhusu kompyuta kufanya kazi nyingi "wakati huo huo." Kuwepo kwa nyuzi nyingi huruhusu mchakato wa kushiriki kazi kwa ajili ya utekelezaji sambamba. Kwenye kompyuta nyingi, michakato au nyuzi zinaweza kukimbia kwenye wasindikaji tofauti. Hii inaruhusu kazi sambamba kweli.

Usindikaji sambamba kabisa hauwezekani kila wakati. Nyuzi wakati mwingine zinahitaji kusawazishwa. Uzi mmoja unaweza kuwa unangojea matokeo ya uzi mwingine, au uzi mmoja unaweza kuhitaji ufikiaji wa kipekee kwa rasilimali ambayo inatumiwa na uzi mwingine. Masuala ya ulandanishi ni sababu ya kawaida ya makosa katika programu zenye nyuzi nyingi. Wakati mwingine thread inaweza kuishia kusubiri rasilimali ambayo haitapatikana kamwe. Hii inaisha katika hali inayoitwa deadlock.

Jambo la kwanza unahitaji kujifunza ni mchakato lina angalau thread moja. Katika OS, kila mchakato una nafasi ya anwani na thread moja ya kudhibiti. Kwa kweli, hii ndiyo inafafanua mchakato.

Kwa upande mmoja, mchakato unaweza kutazamwa kama njia ya kuchanganya rasilimali zinazohusiana katika kundi moja. Mchakato una nafasi ya anwani iliyo na maandishi ya programu na data, pamoja na rasilimali zingine. Rasilimali ni pamoja na faili zilizofunguliwa, michakato ya watoto, jumbe za kengele ambazo hazijashughulikiwa, vidhibiti vya mawimbi, maelezo ya uhasibu na zaidi. Ni rahisi zaidi kusimamia rasilimali kwa kuzichanganya katika mfumo wa mchakato.

Upande mwingine, mchakato unaweza kutazamwa kama mtiririko wa amri zinazoweza kutekelezeka au uzi tu. Mfululizo una kihesabu programu ambacho hufuatilia mpangilio ambao vitendo vinatekelezwa. Ina rejista zinazohifadhi vigezo vya sasa. Ina safu iliyo na kumbukumbu ya utekelezaji wa mchakato, ambapo fremu tofauti imetengwa kwa kila utaratibu ambao umeitwa lakini haujarejeshwa. Ingawa uzi lazima utekeleze ndani ya mchakato, tofauti lazima ifanywe kati ya dhana ya uzi na mchakato. Michakato hutumiwa kupanga rasilimali, na nyuzi ni vitu vinavyotekeleza kwa zamu kwenye CPU.

Dhana ya nyuzi inaongeza kwa mfano wa mchakato uwezo wa kutekeleza wakati huo huo programu kadhaa katika mazingira sawa ya mchakato, kujitegemea vya kutosha. Nyuzi nyingi zinazoendana sambamba katika mchakato mmoja ni sawa na michakato mingi inayoendesha sambamba kwenye kompyuta moja. Katika kesi ya kwanza, nyuzi hushiriki nafasi ya anwani, faili wazi na rasilimali nyingine. Katika kesi ya pili, michakato inashiriki kumbukumbu ya kimwili, disks, printers, na rasilimali nyingine. Threads zina baadhi ya mali ya taratibu, hivyo wakati mwingine huitwa taratibu nyepesi. Muda multithreading pia hutumika kuelezea matumizi ya nyuzi nyingi katika mchakato mmoja.

Yoyote mkondo unajumuisha vipengele viwili:

kitu cha kernel, kwa njia ambayo mfumo wa uendeshaji unadhibiti mtiririko. Taarifa za takwimu kuhusu thread pia zimehifadhiwa huko (nyuzi za ziada pia zinaundwa na kernel);
safu ya thread, ambayo ina vigezo vya kazi zote na vigezo vya ndani vinavyohitajika na thread ili kutekeleza msimbo.

Wacha tuchore mstari: Tofauti kuu kati ya michakato na nyuzi, ni kwamba michakato imetengwa kutoka kwa kila mmoja, kwa hivyo hutumia nafasi tofauti za anwani, na nyuzi zinaweza kutumia nafasi sawa (ndani ya mchakato) wakati wa kufanya vitendo bila kuingiliana. Hii ni nini ni wote kuhusu urahisi wa programu ya nyuzi nyingi: kwa kugawa programu katika nyuzi kadhaa zinazofuatana, tunaweza kuongeza utendaji, kurahisisha kiolesura cha mtumiaji na kufikia uboreshaji (ikiwa programu yako imesakinishwa kwenye mfumo wa vichakataji vingi, kutekeleza nyuzi kwenye vichakataji tofauti, programu yako itafanya kazi kwa kasi ya ajabu =)).

1. Thread huamua mlolongo wa utekelezaji wa kanuni katika mchakato.

2. Mchakato hautekeleze chochote, hutumika tu kama chombo cha nyuzi.

3. Threads daima huundwa katika mazingira ya mchakato, na maisha yao yote hupita tu ndani ya mipaka yake.

4. Threads zinaweza kutekeleza msimbo sawa na kuendesha data sawa, na pia kushiriki vipini kwa vitu vya kernel, kwani meza ya kushughulikia imeundwa si kwa nyuzi tofauti, lakini katika taratibu.

5. Kwa kuwa nyuzi hutumia rasilimali kidogo sana kuliko michakato, jaribu kutatua shida zako kwa kutumia nyuzi za ziada na uepuke kuunda michakato mpya (lakini shughulikia hii kwa busara).

Kufanya kazi nyingi(Kiingereza) kufanya kazi nyingi) - mali ya mfumo wa uendeshaji au mazingira ya programu ili kutoa uwezekano wa usindikaji sambamba (au pseudo-parallel) wa michakato kadhaa. Multitasking ya kweli ya mfumo wa uendeshaji inawezekana tu katika mifumo ya kompyuta iliyosambazwa.

Faili:Picha ya skrini ya Debian (Toa 7.1, "Wheezy") inayoendesha mazingira ya eneo-kazi la GNOME, Firefox, Tor, na VLC Player.jpg

Desktop ya mfumo wa uendeshaji wa kisasa, inayoonyesha shughuli za michakato kadhaa.

Kuna aina 2 za multitasking:

· Mchakato wa kufanya kazi nyingi(kulingana na taratibu - mipango inayoendesha wakati huo huo). Hapa, programu ni kipande kidogo zaidi cha msimbo ambacho kinaweza kudhibitiwa na kipanga ratiba cha mfumo wa uendeshaji. Inajulikana zaidi kwa watumiaji wengi (kufanya kazi katika kihariri cha maandishi na kusikiliza muziki).

· Kufanya kazi nyingi kwa nyuzi(kwa msingi wa nyuzi). Kipengele kidogo zaidi cha msimbo unaosimamiwa ni thread (programu moja inaweza kufanya kazi 2 au zaidi kwa wakati mmoja).

Multithreading ni aina maalumu ya multitasking.

· 1 Sifa za mazingira ya kufanya kazi nyingi

· 2 Ugumu katika kutekeleza mazingira ya kufanya kazi nyingi

· 3 Historia ya mifumo ya uendeshaji ya multitasking

· Aina 4 za shughuli nyingi bandia za uwongo

o 4.1 Kufanya shughuli nyingi zisizo za mapema

o 4.2 Kufanya kazi nyingi kwa ushirikiano au ushirika

o 4.3 Kufanya shughuli nyingi za mapema au za kipaumbele (muda halisi)

· Hali 5 za matatizo katika mifumo ya kufanya kazi nyingi

o 5.1 njaa

o 5.2 Hali ya mbio

· Vidokezo 7

Sifa za mazingira ya kufanya kazi nyingi[hariri | hariri maandishi chanzo]

Mazingira ya awali ya kufanya kazi nyingi hutoa "kushiriki rasilimali" safi, wakati kila kazi imekabidhiwa eneo fulani la kumbukumbu, na kazi hiyo inawashwa kwa vipindi maalum vya wakati.

Mifumo ya hali ya juu zaidi ya kufanya kazi nyingi hutenga rasilimali kwa nguvu, huku kazi ikianzia kwenye kumbukumbu au kuacha kumbukumbu kulingana na kipaumbele chake na mkakati wa mfumo. Mazingira haya ya kufanya kazi nyingi yana sifa zifuatazo:

· Kila kazi ina kipaumbele chake, kulingana na ambayo inapokea muda wa processor na kumbukumbu

· Mfumo hupanga foleni za kazi ili kazi zote zipokee rasilimali, kulingana na vipaumbele na mkakati wa mfumo.

· Mfumo hupanga usindikaji wa kukatiza, ambao kazi zinaweza kuamilishwa, kuzimwa na kufutwa

· Mwishoni mwa kipande cha wakati maalum, punje huhamisha kazi kwa muda kutoka kwa hali inayoendesha hadi hali iliyo tayari, ikitoa rasilimali kwa kazi zingine. Ikiwa hakuna kumbukumbu ya kutosha, kurasa za kazi ambazo hazijatekelezwa zinaweza kusukumwa kwenye diski (kubadilishana), na kisha baada ya muda uliowekwa na mfumo, kurejeshwa kwenye kumbukumbu.

· Mfumo hulinda nafasi ya anwani ya kazi dhidi ya kuingiliwa bila ruhusa ya kazi zingine

· Mfumo hulinda nafasi ya anwani ya kerneli yake dhidi ya kuingiliwa kwa kazi isiyoidhinishwa

· Mfumo unatambua kushindwa na kusimamisha kazi za mtu binafsi na kuzisimamisha

· Mfumo husuluhisha mizozo ya ufikiaji wa rasilimali na vifaa, kuzuia hali za msuguano za kufungia kwa jumla kutoka kwa kungojea rasilimali zilizozuiwa.

· Mfumo huhakikisha kila kazi kwamba mapema au baadaye itaamilishwa

· Mfumo huchakata maombi ya wakati halisi

· Mfumo hutoa mawasiliano kati ya michakato

Ugumu katika kutekeleza mazingira ya kufanya kazi nyingi[hariri | hariri maandishi chanzo]

Ugumu kuu katika kutekeleza mazingira ya kufanya kazi nyingi ni kuegemea kwake, iliyoonyeshwa katika ulinzi wa kumbukumbu, kushughulikia mapungufu na usumbufu, ulinzi dhidi ya kufungia na kufuli.

Mbali na kuaminika, mazingira ya kufanya kazi nyingi lazima yawe na ufanisi. Matumizi ya rasilimali juu ya matengenezo yake haipaswi: kuingilia kati na taratibu, kupunguza kasi ya kazi zao, au kupunguza kasi ya kumbukumbu.

Usomaji mwingi- mali ya jukwaa (kwa mfano, mfumo wa uendeshaji, mashine ya kawaida, nk) au programu, inayojumuisha ukweli kwamba mchakato unaotokana na mfumo wa uendeshaji unaweza kuwa na kadhaa. vijito, kutekelezwa “sambamba,” yaani, bila utaratibu uliowekwa kwa wakati. Wakati wa kufanya kazi fulani, mgawanyiko huo unaweza kufikia matumizi bora zaidi ya rasilimali za kompyuta.

Vile vijito pia inaitwa nyuzi za utekelezaji(kutoka Kiingereza thread ya utekelezaji); wakati mwingine huitwa "nyuzi" (tafsiri halisi ya Kiingereza. uzi) au "nyuzi" zisizo rasmi.

Kiini cha usomaji mwingi ni quasi-multitasking katika kiwango cha mchakato mmoja unaoweza kutekelezwa, yaani, nyuzi zote zinatekelezwa katika nafasi ya anwani ya mchakato. Kwa kuongeza, nyuzi zote za mchakato hazina tu nafasi ya anwani ya kawaida, lakini pia maelezo ya faili ya kawaida. Mchakato unaoendesha una angalau uzi mmoja (kuu).

Kusoma nyingi (kama fundisho la programu) haipaswi kuchanganyikiwa na kufanya kazi nyingi au kuchakata, licha ya ukweli kwamba mifumo ya uendeshaji inayotekeleza kazi nyingi kwa kawaida pia hutekeleza usomaji mwingi.

Faida za multithreading katika programu ni pamoja na zifuatazo:

· Kurahisisha programu katika baadhi ya matukio kutokana na matumizi ya nafasi ya kawaida ya anwani.

· Muda kidogo uliotumika kuunda thread ikilinganishwa na mchakato.

· Kuongeza utendakazi wa mchakato kwa kusawazisha hesabu za kichakataji na utendakazi wa I/O.

· 1 Aina za utekelezaji wa nyuzi

· 2 Mwingiliano wa nyuzi

· 3 Uhakiki wa istilahi

· Vidokezo 6

Aina za utekelezaji wa nyuzi[hariri | hariri maandishi chanzo]

· Mtiririko katika nafasi ya mtumiaji. Kila mchakato una jedwali la nyuzi sawa na jedwali la mchakato wa kernel.

Faida na hasara za aina hii ni kama ifuatavyo: Hasara

1. Hakuna kipima muda kati ya mchakato mmoja

2. Unapotumia ombi la mfumo wa kuzuia kwa mchakato, nyuzi zake zote zimezuiwa.

3. Utata wa utekelezaji

· Tiririka katika nafasi ya punje. Pamoja na jedwali la mchakato, kuna meza ya nyuzi kwenye nafasi ya kernel.

· "nyuzi" nyuzi) Nyuzi nyingi za modi ya mtumiaji zinazoendeshwa katika mnyororo wa modi ya kernel moja. Uzi wa nafasi ya kernel hutumia rasilimali zinazoonekana, haswa kumbukumbu halisi na safu ya anwani ya modi ya kernel kwa safu ya modi ya kernel. Kwa hivyo, wazo la "nyuzi" lilianzishwa - nyuzi nyepesi ambayo inaendesha peke katika hali ya mtumiaji. Kila thread inaweza kuwa na "nyuzi" nyingi.

Mwingiliano wa nyuzi[hariri | hariri maandishi chanzo]

Katika mazingira yenye nyuzi nyingi, mara nyingi matatizo hutokea wakati nyuzi zinazofanana zinashiriki data au vifaa sawa. Ili kutatua shida kama hizo, njia za mwingiliano wa nyuzi kama vile kutengwa kwa pande zote (mutexes), semaphores, sehemu muhimu na matukio hutumiwa.

· Mutex (mutex) ni kitu cha ulandanishi ambacho kimewekwa kwa hali maalum ya ishara wakati haijakaliwa na uzi wowote. Uzi mmoja tu ndio unamiliki kitu hiki wakati wowote, kwa hivyo jina la vitu kama hivyo (kutoka Kiingereza mut kawaida mfano ufikiaji wa pamoja - ufikiaji wa kipekee) - ufikiaji wa wakati huo huo wa rasilimali iliyoshirikiwa haujajumuishwa. Baada ya vitendo vyote muhimu kukamilika, bubu hutolewa, kuruhusu nyuzi nyingine kufikia rasilimali iliyoshirikiwa. Kifaa kinaweza kudumisha upataji wa kujirudia kwa mara ya pili kwa uzi ule ule, kikiongeza kihesabu bila kuzuia uzi, na kuhitaji kutolewa mara nyingi. Hii ni, kwa mfano, sehemu muhimu katika Win32. Hata hivyo, kuna pia utekelezwaji ambao hauungi mkono hili na kusababisha kukwama kwa nyuzi wakati wa kujaribu kunasa tena. Hii ni FAST_MUTEX kwenye kernel ya Windows.

· Semaphores ni rasilimali zinazopatikana ambazo zinaweza kupatikana kwa nyuzi nyingi kwa wakati mmoja hadi bwawa la rasilimali liwe tupu. Kisha nyuzi za ziada zinapaswa kusubiri hadi kiasi kinachohitajika cha rasilimali kinapatikana tena. Semaphores ni nzuri sana kwa sababu huruhusu ufikiaji wa wakati huo huo wa rasilimali. Semaphore ni kiendelezi cha kimantiki cha bubu - semaphore yenye hesabu ya 1 ni sawa na bubu, lakini hesabu inaweza kuwa kubwa kuliko 1.

· Matukio. Kitu ambacho huhifadhi biti 1 ya habari "yenye saini au la", ambayo shughuli "ishara", "rejesha hali ambayo haijatiwa alama" na "kusubiri" hufafanuliwa. Kungoja tukio lililotiwa alama ni kutokuwepo kwa operesheni na mwendelezo wa mara moja wa utekelezaji wa nyuzi. Kungoja tukio lisilo na saini husababisha uzi kusimamisha utekelezaji hadi nyuzi nyingine (au awamu ya pili ya kidhibiti cha kukatiza kwenye kernel ya OS) iashirie tukio hilo. Inawezekana kusubiri matukio kadhaa katika "yoyote" au "wote" modes. Inawezekana pia kuunda tukio ambalo huwekwa upya kiotomatiki hadi hali ambayo haijatiwa alama baada ya kuamsha uzi wa kwanza - na pekee - unaosubiri (kitu kama hicho kinatumika kama msingi wa kutekeleza kitu cha "sehemu muhimu"). Zinatumika kikamilifu katika MS Windows, katika hali ya mtumiaji na katika hali ya kernel. Kuna kitu kama hicho kwenye kinu cha Linux kinachoitwa kwait_queue.

· Sehemu muhimu hutoa ulandanishi sawa na bubu, isipokuwa kwamba vitu vinavyowakilisha sehemu muhimu vinaweza kufikiwa ndani ya mchakato sawa. Matukio, vibubu na semaphores pia vinaweza kutumika katika programu ya mchakato mmoja, lakini utekelezaji wa sehemu muhimu katika baadhi ya mifumo ya uendeshaji (kama vile Windows NT) hutoa utaratibu wa upatanishi wa haraka na bora zaidi wa kipekee—"pata" na "kutolewa" shughuli kwenye sehemu muhimu zimeboreshwa kwa kesi ya nyuzi moja (hakuna ushindani) ili kuzuia simu zozote za mfumo zinazoongoza kwa kernel ya OS. Kama vile bubu, kitu ambacho kinawakilisha sehemu muhimu kinaweza tu kutumiwa na thread moja kwa wakati mmoja, na kuifanya iwe muhimu sana kwa kuweka mipaka ya ufikiaji wa rasilimali zilizoshirikiwa.

· Vigezo vya masharti (condvars). Ni sawa na matukio, lakini sio vitu ambavyo huchukua kumbukumbu - anwani ya kutofautisha hutumiwa tu, wazo la "yaliyomo kigeugeu" haipo, anwani ya kitu cha kiholela inaweza kutumika kama hali ya kutofautisha. Tofauti na matukio, kuweka kigezo cha hali kwa hali iliyotiwa saini hakuna matokeo ikiwa hakuna nyuzi zinazongoja kwa sasa kwenye kigezo. Kuweka tukio katika hali kama hiyo kunajumuisha kuhifadhi hali "iliyo na ishara" ndani ya tukio lenyewe, baada ya hapo nyuzi zinazofuata zinazotaka kusubiri tukio huendelea kutekeleza mara moja bila kukoma. Ili kutumia kikamilifu kitu kama hicho, operesheni ya "kutoa bubu na kusubiri hali ya kutofautiana kwa atomi" pia ni muhimu. Inatumika kikamilifu katika mifumo ya uendeshaji inayofanana na UNIX. Majadiliano kuhusu faida na hasara za matukio na vigezo vya hali ni sehemu muhimu ya majadiliano kuhusu faida na hasara za Windows na UNIX.

· Bandari ya kukamilika kwa IO (IOCP). Imetekelezwa kwenye kernel ya OS na kupatikana kupitia simu za mfumo, kitu cha "foleni" na shughuli "kuweka muundo kwenye mkia wa foleni" na "kuchukua muundo unaofuata kutoka kwa kichwa cha foleni" - simu ya mwisho inasimamisha utekelezaji. ya thread ikiwa foleni ni tupu na mpaka thread nyingine haitapiga simu "kuweka". Kipengele muhimu zaidi cha IOCP ni kwamba miundo inaweza kuwekwa ndani yake sio tu kwa simu ya wazi ya mfumo kutoka kwa hali ya mtumiaji, lakini pia kwa uwazi ndani ya kernel ya OS kama matokeo ya kukamilisha operesheni isiyo ya kawaida ya I/O kwenye mojawapo ya maelezo ya faili. Ili kufikia athari hii, lazima utumie "maelezo ya faili shirikishi na simu ya mfumo ya IOCP". Katika kesi hii, muundo uliowekwa kwenye foleni una msimbo wa hitilafu wa operesheni ya I / O, na pia, ikiwa operesheni hii imefanikiwa, idadi ya byte iliingia au pato. Utekelezaji wa lango la kukamilika pia huweka kikomo idadi ya nyuzi zinazotekelezwa kwenye kichakataji/msingi mmoja baada ya kupokea muundo kutoka kwa foleni. Kitu ni maalum kwa MS Windows, na inaruhusu usindikaji wa maombi ya uunganisho unaoingia na vipande vya data katika programu ya seva katika usanifu ambapo idadi ya nyuzi inaweza kuwa chini ya idadi ya wateja (hakuna hitaji la kuunda thread tofauti na rasilimali. gharama kwa kila mteja mpya).

· ERESOURCE. Bubu inayoauni upataji wa kujirudia, na semantiki za upataji zinazoshirikiwa au za kipekee. Semantiki: kitu kinaweza kuwa cha bure, au kumilikiwa na idadi kiholela ya nyuzi kwa njia ya pamoja, au kumilikiwa na uzi mmoja tu kwa njia ya kipekee. Majaribio yoyote ya kunyakua ambayo yanakiuka sheria hii husababisha kuzuia uzi hadi kitu kitolewe ili kunyakua kuruhusiwa. Pia kuna shughuli kama TryToAcquire - haizuii kamwe nyuzi, inaipata, au (ikiwa kufunga inahitajika) inarudisha FALSE bila kufanya chochote. Inatumika kwenye kernel ya Windows, haswa katika mifumo ya faili - kwa mfano, faili yoyote ya diski iliyofunguliwa na mtu inahusishwa na muundo wa FCB, ambayo kuna vitu 2 kama hivyo vya kusawazisha ufikiaji wa saizi ya faili. Mojawapo, rasilimali ya IO ya paging, inanaswa pekee katika njia iliyopunguzwa ya faili, na inahakikisha kuwa hakuna kache amilifu au I/O iliyopangwa kwa kumbukumbu kwenye faili wakati wa kukatwa.

· Ulinzi wa muhtasari. Kitu kilicho na kumbukumbu (simu zipo kwenye faili za vichwa, lakini sio kwenye hati) kwenye kernel ya Windows. Kaunta yenye shughuli za "ongeza", "punguza" na "subiri". Kusubiri huzuia uzi hadi shughuli za kupunguza zipunguze kaunta hadi sifuri. Zaidi ya hayo, operesheni ya kuongeza inaweza kushindwa, na kuwa na muda wa kuisha kwa sasa husababisha shughuli zote za nyongeza kushindwa.

Sura ya 10.

Programu zenye nyuzi nyingi

Kufanya kazi nyingi katika mifumo ya uendeshaji ya kisasa kunachukuliwa kuwa rahisi [ Kabla ya ujio wa Apple OS X, kompyuta za Macintosh hazikuwa na mifumo ya uendeshaji ya kisasa ya multitasking. Ni vigumu sana kubuni vizuri mfumo wa uendeshaji na multitasking kamili, hivyo OS X ilibidi iwe msingi wa Unix.]. Mtumiaji anatarajia kwamba wakati wa kuendesha mhariri wa maandishi na mteja wa barua pepe kwa wakati mmoja, programu hizi hazitapingana, na wakati wa kupokea barua pepe, mhariri hataacha kufanya kazi. Wakati programu kadhaa zinafanya kazi wakati huo huo, mfumo wa uendeshaji hubadilika haraka kati ya programu, kuwapa processor kwa upande wake (isipokuwa, bila shaka, kompyuta ina wasindikaji wengi imewekwa). Matokeo yake, imeundwa udanganyifu kuendesha programu nyingi wakati huo huo, kwa kuwa hata mpiga chapa bora (na muunganisho wa haraka wa Mtandao) hautashikamana na processor ya kisasa.

Kusoma nyingi, kwa maana, kunaweza kuonekana kama kiwango kinachofuata cha kufanya kazi nyingi: badala ya kubadili kati ya tofauti. programu, mfumo wa uendeshaji hubadilika kati ya sehemu tofauti za programu moja. Kwa mfano, mteja wa barua pepe yenye nyuzi nyingi hukuruhusu kukubali barua pepe mpya unaposoma au kutunga ujumbe mpya. Siku hizi, multithreading pia inachukuliwa kuwa ya kawaida na watumiaji wengi.

VB haijawahi kuwa na usaidizi ufaao wa usomaji mwingi. Kweli, moja ya aina zake zilionekana katika VB5 - mtindo wa utiririshaji shirikishi(kuunganisha ghorofa). Kama utaona hivi punde, muundo shirikishi humpa kitengeneza programu baadhi ya faida za usomaji mwingi, lakini haichukui faida kamili ya vipengele vyote. Hivi karibuni au baadaye unapaswa kubadili kutoka kwa mashine ya mafunzo hadi kwa kweli, na VB .NET ilikuwa toleo la kwanza la VB ili kusaidia mfano wa bure wa nyuzi nyingi.

Walakini, usomaji mwingi sio kipengele ambacho kinatekelezwa kwa urahisi katika lugha za programu au kujifunza kwa urahisi na watengeneza programu. Kwa nini?

Kwa sababu programu zenye nyuzi nyingi zinaweza kuwa na hitilafu za hila ambazo huonekana na kutoweka bila kutabirika (na hizi ndizo hitilafu ngumu zaidi za kutatua).

Onyo la haki: usomaji mwingi ni mojawapo ya maeneo magumu zaidi ya upangaji programu. Kutojali kidogo kunasababisha kuonekana kwa makosa ya hila, marekebisho ambayo yanagharimu hesabu za angani. Kwa sababu hii, sura hii ina mengi mbaya mifano - tuliiandika kwa makusudi kwa njia ya kuonyesha makosa ya kawaida. Hii ndiyo njia salama zaidi ya kujifunza utayarishaji wa nyuzi nyingi: unapaswa kuwa na uwezo wa kuona matatizo yanayoweza kutokea wakati kila kitu kinaonekana kufanya kazi vizuri, na kujua jinsi ya kuyatatua. Ikiwa unataka kutumia mbinu za programu za multithreaded, huwezi kufanya bila hiyo.

Sura hii itaweka msingi thabiti wa kazi huru zaidi, lakini hatutaweza kuelezea upangaji wa nyuzi nyingi katika ugumu wake wote - hati zilizochapishwa kwenye madarasa ya nafasi ya majina ya Threading pekee huchukua zaidi ya kurasa 100. Ikiwa unataka kujua upangaji wa nyuzi nyingi katika kiwango cha juu, rejelea vitabu maalum.

Lakini haijalishi ni hatari gani ya programu ya nyuzi nyingi, haiwezi kubadilishwa kwa suluhisho la kitaalam la shida fulani. Ikiwa programu zako hazitumii usomaji wa maandishi mengi pale inapofaa, watumiaji watasikitishwa sana na watachagua bidhaa nyingine. Kwa mfano, toleo la nne tu la programu maarufu ya barua pepe Eudora ilianzisha uwezo wa nyuzi nyingi, bila ambayo haiwezekani kufikiria mpango wowote wa kisasa wa kufanya kazi na barua pepe. Kufikia wakati Eudora alianzisha usaidizi wa usomaji mwingi, watumiaji wengi (ikiwa ni pamoja na mmoja wa waandishi wa kitabu hiki) walikuwa wamebadilisha na kutumia bidhaa nyingine.

Hatimaye, programu moja zilizounganishwa hazipo kwenye .NET. Wote Programu za NET zina nyuzi nyingi kwa sababu kikusanya takataka huendeshwa kama mchakato wa usuli wa kipaumbele cha chini. Kama inavyoonyeshwa hapa chini, unapofanya upangaji mchoro wa kina katika .NET, mawasiliano sahihi ya nyuzi husaidia kuzuia GUI kuzuia programu inapofanya shughuli za muda mrefu.

Tunakuletea Usomaji mwingi

Kila programu inafanya kazi kwa njia maalum muktadha, kuelezea usambazaji wa nambari na data kwenye kumbukumbu. Kuhifadhi muktadha kwa kweli huokoa hali ya mazungumzo ya programu, ikiruhusu kurejeshwa katika siku zijazo na programu kuendelea kutekeleza.

Kuhifadhi muktadha kunahusisha kiasi fulani cha wakati na kumbukumbu. Mfumo wa uendeshaji unakumbuka hali ya thread ya programu na kuhamisha udhibiti kwenye thread nyingine. Wakati programu inataka kuendelea kutekeleza thread iliyosimamishwa, muktadha uliohifadhiwa lazima urejeshwe, ambayo inachukua muda zaidi. Kwa hivyo, usomaji mwingi unapaswa kutumika tu wakati faida zinazidi gharama. Baadhi ya mifano ya kawaida imeorodheshwa hapa chini.

  • Utendaji wa programu umegawanywa kwa uwazi na kwa asili katika shughuli nyingi tofauti, kama katika mfano wa kupokea barua pepe na kuandaa ujumbe mpya.
  • Programu inafanya hesabu ndefu na ngumu, na hutaki GUI izuiwe inapofanya hesabu.
  • Programu hiyo inaendesha kwenye kompyuta ya multiprocessor na mfumo wa uendeshaji unaounga mkono utumiaji wa wasindikaji wengi (kwa muda mrefu kama idadi ya nyuzi haizidi idadi ya wasindikaji, utekelezaji sambamba hugharimu karibu hakuna gharama zinazohusiana na kubadili nyuzi).

Kabla ya kuendelea na mitambo ya programu nyingi, ni muhimu kutaja hali moja ambayo mara nyingi husababisha kutokuelewana kati ya wageni kwenye uwanja wa programu ya multithreaded.

Kamba ya programu itafanya utaratibu, sio kitu.

Ni vigumu kusema tunamaanisha nini kwa "kitu kinatekelezwa," lakini mmoja wa waandishi mara nyingi hufundisha semina juu ya programu ya multithreaded na swali hili huulizwa mara nyingi zaidi kuliko wengine. Mtu anaweza kufikiria kuwa nyuzi huanza kwa kuita Njia Mpya ya darasa, baada ya hapo nyuzi huchakata ujumbe wote uliopitishwa kwa kitu kinacholingana. Mawazo kama hayo kabisa sio sahihi. Kitu kimoja kinaweza kuwa na nyuzi kadhaa zinazofanya njia tofauti (na wakati mwingine hata zile zile), wakati ujumbe kutoka kwa kitu hutumwa na kupokelewa na nyuzi kadhaa tofauti (kwa njia, hii ni moja ya sababu zinazofanya programu ya nyuzi nyingi kuwa ngumu: ili kurekebisha programu, unahitaji kujua ni nyuzi gani iliyo katika wakati fulani hufanya utaratibu mmoja au mwingine!).

Kwa sababu nyuzi huundwa kulingana na njia za kitu, kitu chenyewe kawaida huundwa kabla ya uzi. Baada ya kuunda kitu kwa mafanikio, mpango huunda thread, kupitisha anwani ya njia ya kitu, na tu baada ya hapo inaagiza thread kuanza utekelezaji. Utaratibu ambao thread iliundwa, kama taratibu zote, inaweza kuunda vitu vipya, kufanya shughuli kwenye vitu vilivyopo, na kupiga taratibu nyingine na kazi ambazo ziko katika upeo wake.

Threads pia inaweza kutekeleza mbinu za kawaida za darasa. Katika kesi hii, pia kumbuka hali nyingine muhimu: thread inaisha kwa kuondoka kwa utaratibu ambao iliundwa. Kabla ya kuondoka kwa utaratibu, kukomesha kawaida kwa thread ya programu haiwezekani.

Threads inaweza kusitisha si tu kwa kawaida, lakini pia isiyo ya kawaida. Hii kwa ujumla haifai. Kwa maelezo zaidi, angalia sehemu ya Kukomesha na Kuacha Mizizi.

Nyenzo kuu za NET zinazohusiana na matumizi ya nyuzi za programu zimejilimbikizia katika nafasi ya majina ya Uzio. Kwa hivyo, programu nyingi zenye nyuzi nyingi zinapaswa kuanza na safu ifuatayo:

Uingizaji wa Mfumo.Uzi

Kuleta nafasi ya majina hurahisisha uingiaji wa programu na hukuruhusu kutumia teknolojia ya IntelliSense.

Uunganisho wa moja kwa moja kati ya nyuzi na taratibu zinaonyesha kuwa nyuzi zina jukumu muhimu katika picha hii. wajumbe(tazama sura ya 6). Hasa, nafasi ya majina ya Threading inajumuisha mjumbe wa ThreadStart, ambayo hutumiwa sana wakati wa kuanzisha nyuzi za programu. Sintaksia ya kutumia mjumbe huyu ni:

Mjumbe wa Umma Sub ThreadStart()

Msimbo unaoitwa kwa kutumia mjumbe wa ThreadStart lazima usiwe na vigezo na thamani ya kurejesha, kwa hivyo nyuzi haziwezi kuundwa kwa vitendakazi (ambavyo vinarejesha thamani) au taratibu zinazochukua vigezo. Ili kupitisha habari kutoka kwa mkondo, lazima pia utafute njia mbadala, kwani njia za kutekeleza hazirudishi maadili na haziwezi kutumia rejeleo la kupita. Kwa mfano, ikiwa utaratibu wa ThreadMethod uko katika darasa la WilluseThread, basi ThreadMethod inaweza kuwasiliana habari kwa kubadilisha sifa za matukio ya darasa la WillUseThread.

Vikoa vya Maombi

Mazungumzo ya programu ya .NET huendeshwa katika kile kinachoitwa vikoa vya programu, vinavyofafanuliwa katika hati kama "mazingira ya pekee ambayo programu hutumika." Kikoa cha programu kinaweza kuzingatiwa kama toleo jepesi la michakato ya Win32; mchakato mmoja wa Win32 unaweza kuwa na vikoa vingi vya programu. Tofauti kuu kati ya vikoa vya programu na michakato ni kwamba mchakato wa Win32 una nafasi yake ya anwani (hati pia inalinganisha vikoa vya programu na michakato ya kimantiki inayoendesha ndani ya mchakato wa kimwili). Katika .NET, usimamizi wote wa kumbukumbu unashughulikiwa na wakati wa utekelezaji, kwa hivyo vikoa vingi vya programu vinaweza kufanya kazi katika mchakato mmoja wa Win32. Moja ya faida za mpango huu ni kuboreshwa kwa uwezo wa kuongeza programu. Zana za kufanya kazi na vikoa vya programu zinapatikana katika darasa la AppDomain. Tunapendekeza ukague hati za darasa hili. Inaweza kutumika kupata taarifa kuhusu mazingira ambayo programu yako inaendesha. Hasa, darasa la AppDomain hutumiwa wakati wa kufanya kutafakari kwenye madarasa ya mfumo wa NET. Programu ifuatayo inaonyesha orodha ya makusanyiko yaliyopakiwa.

Uagizaji wa Mfumo.Tafakari

Moduli ya moduli

Sub Main()

Dim theDomain Kama AppDomain

theDomain = AppDomain.CurrentDomain

DimAssemblies()Kama

Assemblies = theDomain.GetAssemblies

Dim anAssemblyxAs

Kwa Kila Kusanyiko Katika Mikusanyiko

Console.AndikaLinetanAssembly.Jina Kamili) Inayofuata

Console.ReadLine()

Maliza Sub

Mwisho wa Moduli

Kutengeneza Mizizi

Hebu tuanze na mfano wa msingi. Wacha tuseme unataka kutekeleza utaratibu katika uzi tofauti ambao unapunguza kaunta katika kitanzi kisicho na kikomo. Utaratibu unafafanuliwa kama sehemu ya darasa:

Darasa la Umma litatumia Threads

Public SubtractFromCounter()

Dim count Kama Nambari kamili

Fanya Wakati Kweli Hesabu -= 1

Console.WriteLlne("Niko kwenye uzi mwingine na kaunta ="

& hesabu)

Kitanzi

Maliza Sub

Darasa la Mwisho

Kwa kuwa hali ya kitanzi cha Do inabaki kuwa kweli wakati wote, unaweza kufikiria kuwa hakuna kitakachozuia utaratibu wa SubtractFromCounter kutekelezwa. Walakini, hii sio wakati wote katika programu yenye nyuzi nyingi.

Kijisehemu kifuatacho kinaonyesha utaratibu wa Sub Main ambao huanzisha uzi na amri ya Uagizaji:

Chaguo Madhubuti kwenye Mfumo wa Uagizaji. Moduli ya Kuunganisha

Sub Main()

Punguza Jaribio langu 1 kama Wimbo Mpya wa WillUseThreads()

2 Dim bThreadStart Kama New ThreadStart(AnwaniYa _

myTest.SubtractFromCounter)

3 Dim bThread as New Thread (bThreadStart)

4 " bThread.Start()

Punguza Kama Nambari kamili

5 Fanya Ingawa Kweli

Console.WriteLine("Katika uzi na hesabu kuu ni " & i) i += 1

Kitanzi

Maliza Sub

Mwisho wa Moduli

Hebu tuangalie pointi muhimu zaidi moja baada ya nyingine. Kwanza kabisa, utaratibu wa Sub Man n hufanya kazi kila wakati thread kuu( thread kuu). Katika programu za NET daima kuna angalau nyuzi mbili zinazoendesha: thread kuu na thread ya kukusanya takataka. Mstari wa 1 huunda mfano mpya wa darasa la mtihani. Kwenye mstari wa 2, tunaunda mjumbe wa ThreadStart na kupitisha anwani ya utaratibu wa SubtractFromCounter kwa mfano wa darasa la mtihani lililoundwa kwenye mstari wa 1 (utaratibu huu unaitwa bila vigezo). NzuriKwa kuleta nafasi ya majina ya Uzi, jina refu linaweza kuachwa. Kitu kipya cha nyuzi kimeundwa kwenye mstari wa 3. Ona kwamba mjumbe wa ThreadStart anapitishwa wakati mjenzi wa darasa la Thread anaitwa. Watayarishaji wengine wa programu wanapendelea kuchanganya mistari hii miwili kuwa mstari mmoja wa kimantiki:

Dim bThread Kama Uzi Mpya(New ThreadStarttAddressOf _

myTest.SubtractFromCounter))

Mwishowe, mstari wa 4 "huanzisha" uzi kwa kuita Njia ya Anza ya mfano wa darasa la Thread iliyoundwa kwa mjumbe wa ThreadStart. Kwa kupiga njia hii, tunaonyesha kwa mfumo wa uendeshaji kwamba utaratibu wa Ondoa unapaswa kukimbia kwenye thread tofauti.

Neno "kuanza" katika aya iliyotangulia liko katika alama za nukuu kwa sababu linaonyesha moja ya mambo yasiyo ya kawaida ya upangaji wa nyuzi nyingi: kupiga simu Anzisha hakuanzishi uzi! Inakuambia tu kwamba mfumo wa uendeshaji unapaswa kupanga thread maalum ili kukimbia, lakini kwamba utekelezaji halisi uko nje ya udhibiti wa programu. Hutaweza kuanza kutekeleza nyuzi peke yako kwa sababu mfumo wa uendeshaji hudhibiti utekelezwaji wa nyuzi kila wakati. Katika sehemu inayofuata, utajifunza jinsi ya kutumia kipaumbele ili kulazimisha mfumo wa uendeshaji kuanza thread yako kwa kasi zaidi.

Katika Mtini. Mchoro 10.1 unaonyesha mfano wa kile kinachoweza kutokea baada ya kuendesha programu na kisha kukatiza kwa kitufe cha Ctrl+Break. Kwa upande wetu, thread mpya ilianza tu baada ya kukabiliana na thread kuu iliongezeka hadi 341!

Mchele. 10.1. Muda rahisi wa uendeshaji wa programu wenye nyuzi nyingi

Ikiwa programu itaendesha kwa muda mrefu zaidi, matokeo yataonekana kama yale yaliyoonyeshwa kwenye Mtini. 10.2. Tunaona kwamba weweThread inayoendesha imesimamishwa na udhibiti huhamishiwa kwenye uzi kuu. Katika kesi hii, kuna udhihirisho uwekaji nyuzi nyingi kabla ya kukatwa kwa wakati. Maana ya neno hili la kutisha imeelezwa hapa chini.

Mchele. 10.2. Kubadilisha kati ya nyuzi katika programu rahisi yenye nyuzi nyingi

Wakati wa kukatiza nyuzi na kuhamisha udhibiti kwa nyuzi zingine, mfumo wa uendeshaji hutumia kanuni ya utangulizi wa masomo mengi kupitia kukatwa kwa wakati. Kukata wakati pia husuluhisha moja ya shida za kawaida ambazo zilikuwa zikitokea katika programu zenye nyuzi nyingi - nyuzi moja ikichukua wakati wote wa CPU na haitoi udhibiti kwa nyuzi zingine (kawaida hii hufanyika kwa vitanzi vikali kama ile iliyo hapo juu). Ili kuzuia kuruka kwa CPU, nyuzi zako zinapaswa kupitisha udhibiti kwa nyuzi zingine mara kwa mara. Ikiwa programu inageuka kuwa "haijui", kuna suluhisho lingine, lisilohitajika kidogo: mfumo wa uendeshaji daima hutangulia thread inayoendesha, bila kujali kiwango chake cha kipaumbele, ili upatikanaji wa processor upewe kwa kila thread katika mfumo.

Kwa sababu mipango ya kukadiria matoleo yote ya Windows ambayo huendesha .NET hutenga kipande cha muda cha chini kwa kila uzi, matatizo ya CPU hogging si makali sana katika upangaji programu wa .NET. Kwa upande mwingine, ikiwa mfumo wa NET utawahi kubadilishwa kwa mifumo mingine, hali inaweza kubadilika.

Ikiwa tutajumuisha laini ifuatayo katika programu yetu kabla ya kupiga simu Anza, hata nyuzi ambazo hazipewa kipaumbele kidogo zitapokea muda wa CPU:

bThread.Priority = ThreadPriority.Juu

Mchele. 10.3. Uzi uliopewa kipaumbele cha juu kwa kawaida huanza kukimbia haraka

Mchele. 10.4. CPU pia inatolewa kwa nyuzi za kipaumbele cha chini

Amri inapeana uzi mpya kipaumbele cha juu zaidi na inapunguza kipaumbele cha uzi kuu. Kutoka Mtini. 10.3 inaonyesha kuwa uzi mpya huanza kufanya kazi haraka kuliko hapo awali, lakini, kama Mtini. 10.4, thread kuu pia inapata udhibitition (ingawa kwa ufupi sana na tu baada ya uzi kuwa unaendelea kwa muda mrefu na kutoa). Unapoendesha programu kwenye kompyuta yako, utapata matokeo sawa na yale yaliyoonyeshwa kwenye Mtini. 10.3 na 10.4, lakini kutokana na tofauti kati ya mifumo yetu hakutakuwa na ulinganifu kamili.

Aina ya hesabu ya ThreadPrlority ina thamani za viwango vitano vya kipaumbele:

ThreadPriority.Juu

ThreadPriority.Juu yaKawaida

ThreadPrlority.Kawaida

ThreadPriority.Chini ya Kawaida

ThreadPriority.Chini

Mbinu ya kujiunga

Wakati mwingine thread ya programu inahitaji kusimamishwa hadi thread nyingine ikamilike. Tuseme unataka kusimamisha thread 1 hadi thread 2 imalize mahesabu yake. Kwa hii; kwa hili kutoka mkondo 1 Njia ya Kujiunga inaitwa kwa thread 2. Kwa maneno mengine, amri

thread2.Jiunge ()

husimamisha uzi wa sasa na kusubiri hadi uzi wa 2 ukamilike. Mzigo wa 1 unaingia hali iliyofungwa.

Ukiunganisha thread 1 hadi thread 2 kwa kutumia njia ya Kuunganisha, mfumo wa uendeshaji utaanzisha thread 1 kiotomatiki baada ya thread 2 kukamilika. Kumbuka kuwa mchakato wa kuanzisha ni isiyo ya kuamua: Haiwezekani kusema ni muda gani hasa baada ya uzi wa 2 kumalizia uzi wa 1 utaanza. Kuna toleo lingine la Jiunge ambalo hurejesha thamani ya boolean:

thread2. Jiunge (Nambari kamili)

Njia hii husubiri hadi uzi wa 2 ukamilike au kufungua uzi wa 1 baada ya muda maalum kupita, na kusababisha kipanga ratiba cha mfumo wa uendeshaji kutenga muda wa kichakataji kwenye uzi tena. Mbinu inarejesha Kweli ikiwa thread 2 itakoma kabla ya muda uliobainishwa kuisha, na Sivyo vinginevyo.

Kumbuka kanuni ya msingi: iwe thread 2 imetoka au muda umeisha, huna udhibiti wa wakati thread 1 imeamilishwa.

Majina ya nyuzi, CurrentThread na ThreadState

Sifa ya Thread.CurrentThread inarejesha rejeleo kwa kitu cha thread ambacho kinaendeshwa kwa sasa.

Ingawa kuna kidirisha cha ajabu cha uzi wa kurekebisha programu zenye nyuzi nyingi katika VB .NET, ambayo imefafanuliwa hapa chini, amri mara nyingi ilitusaidia.

MsgBox(Thread.CurrentThread.Name)

Mara nyingi iliibuka kuwa nambari hiyo ilikuwa ikitekelezwa kwa uzi tofauti kabisa na ile ambayo ilitakiwa kutekelezwa.

Wacha tukumbuke kwamba neno "ratiba isiyo ya kuamua ya nyuzi za programu" inamaanisha jambo rahisi sana: mpangaji programu hana njia yoyote ya kushawishi kazi ya mpangaji. Kwa sababu hii, programu mara nyingi hutumia mali ya ThreadState kurejesha habari kuhusu hali ya sasa ya thread.

Dirisha la Mipasho

Dirisha la Threads katika Visual Studio .NET hutoa usaidizi wa thamani sana katika kutatua programu zenye nyuzi nyingi. Imewashwa na Debug > Windows submenu amri katika hali ya kukatiza. Wacha tuseme umeweka jina kwa bThread na amri ifuatayo:

bThread.Name = "Inaondoa uzi"

Mtazamo wa takriban wa dirisha la thread baada ya kukatiza programu kwa kutumia mchanganyiko wa Ctrl + Break (au njia nyingine) inavyoonekana kwenye Mtini. 10.5.

Mchele. 10.5. Dirisha la Mipasho

Mshale katika safu wima ya kwanza unaonyesha uzi amilifu uliorejeshwa na kipengele cha Thread.CurrentThread. Safu wima ya kitambulisho ina vitambulisho vya nyuzi za nambari. Safu ifuatayo inaorodhesha majina ya uzi (ikiwa yamepewa). Safu wima ya Mahali inaonyesha utaratibu unaotekelezwa (kwa mfano, utaratibu wa AndikaLine wa darasa la Console kwenye Mchoro 10.5). Safu wima zilizosalia zina habari kuhusu vipaumbele na nyuzi zilizosimamishwa (tazama sehemu inayofuata).

Dirisha la Nyuzi (sio mfumo wa uendeshaji!) hukuruhusu kudhibiti nyuzi za programu yako kwa kutumia menyu za muktadha. Kwa mfano, unaweza kusimamisha uzi wa sasa kwa kubofya kulia kwenye mstari unaolingana na kuchagua Kufungia (uzi uliosimamishwa unaweza kurejeshwa baadaye). Kusimamisha nyuzi mara nyingi hutumiwa katika utatuzi ili kuzuia uzi unaofanya vibaya kuingilia kati programu. Kwa kuongeza, dirisha la Threads inakuwezesha kuamsha thread nyingine (haijasimamishwa); Ili kufanya hivyo, bonyeza-click kwenye mstari unaohitajika na uchague amri ya Kubadili kwa Thread kwenye orodha ya muktadha (au bonyeza mara mbili tu kwenye mstari wa thread). Kama tutakavyoonyesha baadaye, hii ni muhimu sana kwa kugundua mikwamo inayoweza kutokea.

Kusitisha uzi

Nyuzi ambazo hazijatumika kwa muda zinaweza kuwekwa katika hali ya utulivu kwa kutumia mbinu ya Slever. Thread passiv pia inachukuliwa kuwa imefungwa. Bila shaka, wakati thread inawekwa katika hali ya passive, nyuzi zilizobaki zitapokea rasilimali zaidi za CPU. Sintaksia ya kawaida ya mbinu ya Kulala ni kama ifuatavyo: Thread.Sleep(interval_in_milliseconds)

Kupiga Simu Usingizi husababisha uzi amilifu kutofanya kazi kwa angalau idadi iliyobainishwa ya milisekunde (hata hivyo, hakuna hakikisho la kuamka mara tu baada ya muda uliobainishwa kupita). Tafadhali kumbuka: wakati wa kupiga njia, kumbukumbu ya thread maalum haipitishwa - njia ya Kulala inaitwa tu kwa thread inayofanya kazi.

Toleo jingine la Kulala husababisha uzi wa sasa kughairi salio la wakati wake wa CPU uliotengwa:

Thread.Lala(0)

Chaguo lifuatalo linaweka uzi wa sasa katika hali ya utulivu kwa muda usio na kikomo (uwezeshaji hutokea tu wakati Kukatiza kunapoitwa):

Thread.Slayer(Timeout.Infinite)

Kwa sababu nyuzi tulivu (hata kwa kuisha kwa muda usio na kikomo) zinaweza kukatizwa na Mbinu ya Kukatiza, na kusababisha ThreadInterruptException kutupwa, simu ya Slayer inafungwa kila wakati kwenye kizuizi cha Jaribu-Catch, kama katika kijisehemu kifuatacho:

Jaribu

Uzi.Kulala(200)

"Hali ya mtiririko tulivu imekatizwa

Catch e Kama Isipokuwa

"Vighairi vingine

Maliza Jaribu

Kila programu ya NET inaendeshwa kwenye uzi wa programu, kwa hivyo njia ya Kulala pia inatumika kusimamisha programu (ikiwa nafasi ya jina ya Threadipg haijaagizwa na programu, lazima utumie jina lililohitimu kikamilifu Threading.Thread. Sleep).

Kusitisha au kukatiza nyuzi za programu

Uzi huisha kiatomati wakati njia iliyobainishwa wakati mjumbe wa ThreadStart ameundwa inatoka, lakini wakati mwingine unataka kusitisha njia (na kwa hivyo uzi) wakati sababu fulani zinatokea. Katika hali kama hizi, nyuzi kawaida huangalia kutofautiana kwa masharti, kulingana na hali ambayouamuzi unafanywa kuhusu kuondoka kwa dharura kutoka kwa mtiririko. Kwa kawaida, kitanzi cha Do-While kinajumuishwa katika utaratibu wa kufanya hivi:

Njia ndogo ya Threaded()

"Mpango lazima utoe njia za kuhoji

"kutofautisha kwa masharti.

" Kwa mfano, hali ya kutofautiana inaweza kuandikwa kama mali

Fanya Wakati conditionVariable = False na MoreWorkToDo

"Kanuni kuu

Kitanzi Mwisho Sub

Inachukua muda kupigia kura kigezo cha masharti. Upigaji kura unaoendelea katika hali ya kitanzi unapaswa kutumika tu ikiwa unatarajia uzi kuisha mapema.

Ikiwa mabadiliko ya hali lazima yajaribiwe katika eneo mahususi, tumia amri ya If-Basi pamoja na Exit Sub ndani ya kitanzi kisicho na kikomo.

Ufikiaji wa mabadiliko ya hali lazima ulandanishwe ili mwingiliano kutoka kwa nyuzi zingine usiingiliane na matumizi yake ya kawaida. Mada hii muhimu inajadiliwa katika sehemu ya "Kutatua Matatizo: Usawazishaji".

Kwa bahati mbaya, msimbo wa nyuzi za passiv (au zilizozuiwa vinginevyo) hazitekelezwi, kwa hivyo chaguo la kupigia kura kigezo cha hali haifai kwao. Katika kesi hii, unapaswa kupiga simu Njia ya Kukatiza kwenye utofauti wa kitu kilicho na kumbukumbu ya uzi unaotaka.

Mbinu ya Kukatiza inaweza tu kuitwa kwenye minyororo iliyo katika hali ya Kusubiri, Kulala au Kujiunga. Ikiwa utaita Kukatiza kwenye thread ambayo iko katika mojawapo ya majimbo yaliyoorodheshwa, basi baada ya muda thread itaanza kukimbia tena, na wakati wa kukimbia utatupa ThreadlnterruptedException kwenye thread. Hii hutokea hata kama thread imewekwa katika hali ya passiv kwa muda usiojulikana kwa kupiga Thread.Sleepdimeout. Isiyo na mwisho). Tunasema "baada ya muda" kwa sababu upangaji wa nyuzi sio wa kuamua kwa asili. ThreadlnterruptedExcepti isipokuwa inanaswa na sehemu ya Catch, ambayo ina msimbo wa kuondoka katika hali ya kusubiri. Hata hivyo, sehemu ya Catch haihitajiki kusitisha mazungumzo kwa kupiga simu "Katisha"; thread inashughulikia ubaguzi kwa hiari yake yenyewe.

Katika .NET, njia ya Kukatiza inaweza kuitwa hata kwenye nyuzi ambazo hazijazuiwa. Katika kesi hii, thread inaingiliwa kwenye block inayofuata.

Kusimamisha na kuua nyuzi

Nafasi ya majina ya Uzio ina njia zingine ambazo husumbua utendakazi wa kawaida wa nyuzi:

  • kusimamisha;
  • Utoaji mimba

Ni vigumu kusema ni kwa nini .NET ilijumuisha usaidizi wa mbinu hizi - kupiga simu Sitisha na Kotisha kuna uwezekano mkubwa kusababisha programu kutokuwa thabiti. Hakuna njia yoyote inayokuruhusu kufuta uzi kawaida. Zaidi ya hayo, wakati wa kupiga simu Sitisha au Toa mimba, hakuna njia ya kutabiri ni hali gani nyuzi itaacha vitu baada ya kusimamisha au kutoa mimba.

Simu ya Kufuta mimba inatupa ThreadAbortException. Ili kukusaidia kuelewa ni kwa nini ubaguzi huu wa ajabu haupaswi kushughulikiwa katika programu, hapa kuna dondoo kutoka kwa hati za .NET SDK:

"... Wakati thread inaharibiwa kwa kupiga Angaza, wakati wa kukimbia hutupa ThreadAbortException. Hii ni aina maalum ya ubaguzi ambayo haiwezi kupatikana na programu. Wakati ubaguzi huu umeinuliwa, wakati wa kukimbia hutekeleza vizuizi vyote kabla ya kuua uzi. Kwa kuwa hatimaye vizuizi vinaweza kufanya kitendo chochote, piga simu Jiunge ili kuhakikisha kuwa uzi umeharibiwa."

Maadili: Kuahirisha na Kuahirisha haipendekezwi (na ikiwa bado huwezi kufanya bila Kusimamisha, anzisha tena uzi uliosimamishwa kwa kutumia mbinu ya Rejesha). Njia pekee ya kusitisha mazungumzo kwa usalama ni kwa kupiga kura kigeugeu cha hali iliyosawazishwa au kwa kupiga njia ya Kukatiza iliyojadiliwa hapo juu.

Nyuzi za usuli (daemoni)

Baadhi ya minyororo ya usuli huacha kufanya kazi kiotomatiki vipengele vingine vya programu vinapokoma. Hasa, mtoza takataka huendesha kwenye moja ya nyuzi za nyuma. Kawaida, nyuzi za mandharinyuma huundwa ili kupokea data, lakini hii inafanywa tu ikiwa kuna msimbo unaoendesha kwenye nyuzi zingine ambazo zinaweza kusindika data iliyopokelewa. Sintaksia: jina la mazungumzo.IsBackGround = Kweli

Ikiwa kuna nyuzi za usuli pekee zilizosalia kwenye programu, programu itakatishwa kiotomatiki.

Mfano mbaya zaidi: kutoa data kutoka kwa msimbo wa HTML

Tunapendekeza kutumia nyuzi tu wakati utendakazi wa programu umegawanywa wazi katika shughuli kadhaa. Mfano mzuri ni kichuna data cha HTML kutoka Sura ya 9. Darasa letu hufanya mambo mawili: kuchota data kutoka Amazon na kuichakata. Huu ni mfano kamili wa hali ambapo programu ya multithreaded inafaa kweli. Tunaunda madarasa kwa vitabu kadhaa tofauti vya kazi na kisha kuchanganua data katika nyuzi tofauti. Kuunda thread mpya kwa kila kitabu cha kazi huboresha ufanisi wa programu kwa sababu wakati thread moja inapokea data (ambayo inaweza kuhitaji kusubiri kwenye seva ya Amazon), thread nyingine itakuwa na shughuli nyingi kuchakata data ambayo tayari imepokelewa.

Toleo la nyuzi nyingi za programu hii hufanya kazi kwa ufanisi zaidi kuliko toleo la thread moja tu kwenye kompyuta yenye wasindikaji kadhaa au ikiwa mapokezi ya data ya ziada yanaweza kuunganishwa kwa ufanisi na uchambuzi wake.

Kama ilivyoelezwa hapo juu, taratibu tu ambazo hazina vigezo zinaweza kuendeshwa kwa nyuzi, kwa hivyo itabidi ufanye mabadiliko madogo kwenye programu. Ifuatayo ni utaratibu kuu, ulioandikwa upya ili kuwatenga vigezo:

Kiwango Ndogo cha Umma cha Tafuta()

m_Rank = ScrapeAmazon()

Console.WriteLine("cheo cha " & m_Name & "Ni" & GetRank)

Maliza Sub

Kwa kuwa hatutaweza kutumia kisanduku cha kuchana kuhifadhi na kupata taarifa (kuandika programu za GUI zenye nyuzi nyingi kunashughulikiwa katika sehemu ya mwisho ya sura hii), programu huhifadhi data ya vitabu vinne katika safu ambayo ufafanuzi wake huanza. kama hii:

Dim theBook(3.1) As String theBook(0.0) = "1893115992"

theBook(0.l) = "Programming VB .NET" " Nk.

Nyuzi nne zimeundwa kwa kitanzi kimoja ambacho huunda vitu vya AmazonRanker:

Kwa i= 0 Kisha 3

Jaribu

theRanker = New AmazonRanker(Kitabu(i.0). theBookd.1))

aThreadStart = Nyota Mpya (AddressOf theRanker.FindRan()

aThread = Uzi Mpya(aThreadStart)

aThread.Name = theBook(i.l)

aThread.Start() Catch na Kama Isipokuwa

Console.WriteLine(e.Message)

Maliza Jaribu

Inayofuata

Hapo chini kuna maandishi kamili ya programu:

Chaguo Madhubuti kwenye Uagizaji System.IO Inaagiza System.Net

Uingizaji wa Mfumo.Uzi

Moduli ya moduli

Sub Main()

Dim theBook(3.1) Kama Kamba

theBook(0.0) = "1893115992"

theBook(0.l) = "Kupanga VB .NET"

theBook(l.0) = "1893115291"

theBook(l.l) = "Upangaji Hifadhidata VB .NET"

theBook(2,0) = "1893115623"

theBook(2.1) = "Utangulizi wa Msanidi programu kwa C#."

theBook(3.0) = "1893115593"

theBook(3.1) = "Gland the .Net Platform"

Punguza Kama Nambari kamili

Dim theRanker As =AmazonRanker

Dim aThreadStart As Threading.ThreadStart

Dim a Thread As Threading.Thread

Kwa i = 0 hadi 3

Jaribu

theRanker = New AmazonRankerttheBook(i.0). Kitabu (i.1))

aThreadStart = New ThreadStart(AddressOf theRanker. FindRank)

aThread = Uzi Mpya(aThreadStart)

aThread.Name= Kitabu(i.l)

Thread.Start()

Catch e Kama Isipokuwa

Console.WriteLlnete.Message)

Maliza Jaribu Inayofuata

Console.ReadLine()

Maliza Sub

Mwisho wa Moduli

Darasa la Umma la AmazonRanker

M_URL ya Faragha Kama Mfuatano

M_Cheo cha Binafsi Kama Nambari kamili

M_Jina la Kibinafsi Kama Mfuatano

Mada Mpya ya Umma(ByVal ISBN Kama Mfuatano. ByVal theName As String)

m_URL = "http://www.amazon.com/exec/obidos/ASIN/" & ISBN

m_Name = theName End Sub

Public Sub FindRank() m_Rank = ScrapeAmazon()

Console.Writeline("cheo cha " & m_Name & "ni "

& GetRank) Maliza Sub

GetRank() ya Mali ya Kusoma Pekee ya Umma Kama Upataji wa Kamba

Ikiwa m_Rank<>0 Kisha

Rudisha CStr(m_Rank) Vinginevyo

"Matatizo

Mwisho Kama

Maliza Kupata

Maliza Mali

GetName() ya Mali ya Kusoma Pekee ya Umma Kama Upataji wa Kamba

Rudisha m_Name

Maliza Kupata

Maliza Mali

Kazi ya Kibinafsi ScrapeAmazon() Kama Jaribu Nambari

Punguza URL Kama Uri Mpya(m_URL)

PunguzaOmbi Kama Ombi la Wavuti

theRequest =WebRequest.Create(theURL)

Punguza Majibu Kama Majibu ya Wavuti

theResponse = theRequest.GetResponse

Punguza Kisomaji Kama Kisomaji Kipya (theResponse.GetResponseStream())

Punguza Data Kama Kamba

theData = aReader.ReadToEnd

Rudisha Uchambuzi (Data)

Catch E Kama Isipokuwa

Console.WriteLine(E.Message)

Console.WriteLine(E.StackTrace)

Console. Soma Line()

Maliza Jaribu Komesha Chaguo

Uchambuzi wa Shughuli za Kibinafsi(ByVal theData As String) Kama Nambari kamili

Dim Location As.Integer Location = theData.IndexOf(" Amazon.com

Kiwango cha mauzo:") _

+ "Kiwango cha mauzo cha Amazon.com:".Urefu

Dim temp Kama Kamba

Fanya Mpaka theData.Substring(Location.l) = "<" temp = temp

&theData.Substring(Location.l) Mahali += Kitanzi 1

ReturnClnt(temp)

Mwisho wa Kazi

Darasa la Mwisho

Usomaji mwingi ni wa kawaida katika nafasi za majina za NET na I/O, kwa hivyo maktaba ya NET Framework hutoa mbinu maalum zisizolingana kwao. Kwa maelezo zaidi kuhusu kutumia mbinu zisizolingana wakati wa kuandika programu zenye nyuzi nyingi, angalia njia za BeginGetResponse na EndGetResponse za darasa la HTTPWebRequest.

Hatari kuu (data ya jumla)

Hadi sasa, kesi pekee salama ya kutumia nyuzi imezingatiwa - nyuzi zetu hazikubadilisha data iliyoshirikiwa. Ukiruhusu mabadiliko kwenye data iliyoshirikiwa, hitilafu zinazoweza kutokea huanza kuongezeka kwa kasi na inakuwa vigumu zaidi kuziondoa kwenye programu. Kwa upande mwingine, ukizuia nyuzi tofauti kurekebisha data iliyoshirikiwa, programu ya NET multithreaded kimsingi ni sawa na uwezo mdogo wa VB6.

Tunawasilisha kwa mawazo yako mpango mdogo unaoonyesha matatizo yanayotokea bila kuingia kwa undani usiohitajika. Mpango huu unaiga nyumba yenye thermostat iliyowekwa katika kila chumba. Ikiwa halijoto ni nyuzi joto 5 Fahrenheit au zaidi (karibu 2.77 digrii Selsiasi) baridi sana, tunauambia mfumo wa joto uongeze joto la nyuzi 5; vinginevyo joto huongezeka kwa digrii 1 tu. Ikiwa hali ya joto ya sasa ni kubwa kuliko au sawa na joto la kuweka, hakuna mabadiliko yanayofanywa. Udhibiti wa joto katika kila chumba unafanywa na thread tofauti na kuchelewa kwa 200-millisecond. Kazi kuu inafanywa na kipande kifuatacho:

Ikiwa mHouse.HouseTemp< mHouse.MAX_TEMP = 5 Then Try

Uzi.Kulala(200)

Catch tie As ThreadlnterruptedException

"Kungoja tu kulikatizwa

Catch e Kama Isipokuwa

" Vighairi vingine vya Jaribu

mHouse.HouseTemp +- 5" Nk.

Chini ni msimbo kamili wa chanzo wa programu. Matokeo yanaonyeshwa kwenye Mtini. 10.6: Halijoto ndani ya nyumba ilifikia nyuzi joto 105 Selsiasi (nyuzi nyuzi 40.5)!

Chaguo 1 Imewashwa

2 Imports System.Threading

3 Moduli ya moduli

4 Ndogo Kuu()

5 Dim myHouse Kama Nyumba Mpya(l0)

6 Console. Soma Line()

7 Maliza Sub

8 Mwisho wa Moduli

9 Nyumba ya Darasa la Umma

Gharama 10 za Umma MAX_TEMP Kama Nambari kamili = 75

11 mCurTemp ya Kibinafsi Kama Nambari kamili = 55

Vyumba 12 vya Binafsi() Kama Chumba

Mada 13 ya Umma Mpya(ByVal numOfrooms As Integer)

Vyumba 14 vya ReDim(numOfRooms = 1)

15 Dim i Kama Nambari kamili

16 Dim aThreadStart As Threading.ThreadStart

17 Dim A Thread As Thread

18 Kwa i = 0 Kwa numOfRooms -1

19 Jaribu

20 mRooms(i)=NewRoom(Me, mCurTemp,Cstr(i) &"chumba")

21 aThreadStart - New ThreadStart(AnwaniYa _

mRooms(i).CheckTempInRoom)

22 aThread =Uzi Mpya(aThreadStart)

23 aThread.Start()

24 Catch E Kama Isipokuwa

25 Console.WriteLine(E.StackTrace)

26 Maliza Jaribu

27 Inayofuata

28 Mwisho Sub

29 Mali ya Umma HouseTemp()Kama Nambari kamili

thelathini . Pata

31 Rudisha mCurTemp

32 Mwisho Kupata

33 Set(Thamani ByVal Kama Nambari kamili)

34 mCurTemp = Thamani 35 Seti ya Mwisho

36 Maliza Mali

37 Darasa la Mwisho

38 Chumba cha Darasa la Umma

39 mCurTemp ya Kibinafsi Kama Nambari kamili

40 mName Binafsi Kama Kamba

41 mHouse Binafsi Kama Nyumba

42 Ndogo ya Umma Mpya(ByVal theHouse As House,

Joto la ByVal Kama Nambari kamili, ByVal roomName As String)

43 mNyumba = Nyumba

44 mCurTemp = joto

45 mName = roomName

46 Maliza Sub

47Nyumba Ndogo ya Ukaguzi ya Umma()

48 BadilishaJoto()

49 Maliza Sub

50 Binafsi Mabadiliko ya Joto()

51 jaribu

52 Ikiwa mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then

53 Uzi.Kulala(200)

54 mHouse.HouseTemp +- 5

55 Console.WriteLine("Niko kwenye " & Me.mName & _

56 ". Halijoto ya sasa ni "&mHouse.HouseTemp)

57. Elself mHouse.HouseTemp< mHouse.MAX_TEMP Then

58 Uzi.Kulala(200)

59 mHouse.HouseTemp += 1

60 Console.WriteLine("Niko kwenye " & Me.mName & _

61 ". Halijoto ya sasa ni " & mHouse.HouseTemp)

62 Mengine

63 Console.WriteLine("Niko kwenye " & Me.mName & _

64 ". Halijoto ya sasa ni " & mHouse.HouseTemp)

65 Usifanye chochote, halijoto ni ya kawaida

66 Mwisho Kama

67 Pata Kama ThreadlnterruptedException

68 "Kungoja bila mpangilio kulikatizwa

69 Catch e Kama Isipokuwa

70" Vighairi vingine

71 Maliza Jaribu

72 Mwisho Ndogo

73 Darasa la Mwisho

Mchele. 10.6. Masuala ya usomaji mwingi

Utaratibu wa Sub Main (mstari wa 4-7) huunda "nyumba" yenye "vyumba" kumi. Darasa la Nyumba huweka kiwango cha juu cha halijoto cha nyuzi joto 75 Fahrenheit (kama nyuzi 24 Selsiasi). Mstari wa 13-28 unafafanua mjenzi wa nyumba tata. Ufunguo wa kuelewa mpango ni mstari wa 18-27. Mstari wa 20 huunda kitu kingine cha chumba, kupitisha kumbukumbu ya kitu cha nyumba kwa mjenzi ili kitu cha chumba kinaweza kufikia ikiwa ni lazima. Mstari wa 21-23 huanza nyuzi kumi ili kurekebisha hali ya joto katika kila chumba. Darasa la Chumba limefafanuliwa kwenye mistari 38-73. Rejeleo la kitu cha nyumba coxpaimeainishwa katika tofauti ya mHouse katika mjenzi wa darasa la Chumba (mstari wa 43). Nambari ya kuangalia na kurekebisha hali ya joto (mistari 50-66) inaonekana rahisi na ya asili, lakini kama utaona hivi karibuni, muonekano huu unadanganya! Kumbuka kuwa msimbo huu umefungwa kwenye kizuizi cha Jaribu-Catch kwa sababu programu hutumia mbinu ya Kulala.

Haiwezekani kwamba mtu yeyote atakubali kuishi katika halijoto ya nyuzi joto 105 Selsiasi (40.5 24 digrii Selsiasi). Nini kimetokea? Tatizo liko kwenye mstari ufuatao:

Ikiwa mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then

Kinachotokea ni kwamba thread 1 inakagua halijoto kwanza.Inaona halijoto ni ya chini sana na inaiinua kwa nyuzi 5. Kwa bahati mbaya, kabla ya halijoto kupanda, thread 1 inaingiliwa na udhibiti huhamishiwa kwenye thread 2. Thread 2 hukagua tofauti sawa haijabadilishwa bado mkondo 1. Kwa hivyo, mkondo wa 2 pia unatayarisha kuongeza joto kwa digrii 5, lakini hawana muda wa kufanya hivyo na pia huenda katika hali ya kusubiri. Mchakato unaendelea hadi thread 1 iamilishwe na iendelee kwa amri inayofuata - kuongeza joto kwa digrii 5. Kuongezeka kunarudiwa wakati mito yote 10 imeanzishwa, na wakazi wa nyumba watakuwa na wakati mbaya.

Kutatua tatizo: maingiliano

Katika mpango uliopita, hali hutokea ambapo matokeo ya programu inategemea utaratibu ambao threads zinatekelezwa. Ili kuiondoa, unahitaji kuhakikisha kuwa amri kama

Ikiwa mHouse.HouseTemp< mHouse.MAX_TEMP - 5 Then...

huchakatwa kikamilifu na uzi amilifu kabla ya kukatizwa. Mali hii inaitwa aibu ya atomiki - kizuizi cha msimbo lazima kitekelezwe na kila uzi bila kukatizwa, kama kitengo cha atomiki. Kikundi cha amri kilichojumuishwa katika kizuizi cha atomiki hakiwezi kuingiliwa na kipanga ratiba kabla ya kukamilika. Lugha yoyote ya programu yenye nyuzi nyingi ina njia zake za kuhakikisha atomiki. Katika VB .NET, njia rahisi ni kutumia amri ya SyncLock, ambayo inapoitwa, hupitisha kigezo cha kipengee. Fanya mabadiliko madogo kwa utaratibu wa ChangeTemperature kutoka kwa mfano uliopita, na programu itafanya kazi vizuri:

Mabadiliko ya Kibinafsi ya Joto() SyncLock (mHouse)

Jaribu

Ikiwa mHouse.HouseTemp< mHouse.MAXJTEMP -5 Then

Uzi.Kulala(200)

mHouse.HouseTemp += 5

Console.WriteLine("Niko kwenye " & Me.mName & _

". Halijoto ya sasa ni " & mHouse.HouseTemp)

Mengine

mHouse.HouseTemp< mHouse. MAX_TEMP Then

Thread.Sleep(200) mHouse.HouseTemp += 1

Console.WriteLine("Niko " & Me.mName &_ ". Halijoto ya sasa ni " & mHouse.HomeTemp) Vinginevyo

Console.WriteLineC"Niko " & Me.mName & _ ". Halijoto ya sasa ni " & mHouse.HouseTemp)

"Usifanye chochote, hali ya joto ni ya kawaida

Maliza Kama Catch tie Kama ThreadlnterruptedException

" Kusubiri bila mpangilio kulikatizwa na Catch e As Exception

"Vighairi vingine

Maliza Jaribu

Maliza SyncLock

Maliza Sub

Msimbo wa kuzuia wa SyncLock hutekelezwa kwa njia ya atomi. Kuifikia kutoka kwa nyuzi zingine zote kutakataliwa hadi mazungumzo ya kwanza yaachilie kufuli kwa amri ya End SyncLock. Ikiwa thread katika block iliyosawazishwa inaingia katika hali ya kusubiri ya passiv, lock inabakia mpaka thread imeingiliwa au kuanzishwa tena.

Matumizi sahihi ya amri ya SyncLock huhakikisha kuwa programu yako ni salama. Kwa bahati mbaya, kutumia SyncLock kupita kiasi kuna athari mbaya kwa utendakazi. Kusawazisha msimbo katika programu yenye nyuzi nyingi hupunguza kasi yake mara kadhaa. Sawazisha tu msimbo unaohitajika zaidi na uondoe kufuli haraka iwezekanavyo.

Madarasa ya mkusanyo wa msingi si salama katika programu zenye nyuzi nyingi, lakini Mfumo wa NET unajumuisha matoleo salama ya nyuzi za aina nyingi za mkusanyiko. Katika madarasa haya, msimbo wa mbinu zinazoweza kuwa hatari upo katika vizuizi vya SyncLock. Matoleo salama ya nyuzi ya madarasa ya mkusanyiko yanapaswa kutumika katika programu zenye nyuzi nyingi popote ambapo uadilifu wa data uko hatarini.

Inabaki kutajwa kuwa vigeu vya masharti vinaweza kutekelezwa kwa urahisi kwa kutumia amri ya SyncLock. Ili kufanya hivyo, unahitaji tu kusawazisha maandishi kwa mali ya boolean iliyoshirikiwa ambayo inasomwa na kuandikwa, kama kwenye kijisehemu kifuatacho:

Public Class ConditionVariable

Locker ya Pamoja ya Kibinafsi Kama Kitu= Kitu Kipya()

Faragha MOK Kama Boolean Imeshirikiwa

Mali TheConditionVariable()Kama Boolean

Pata

Rudisha mOK

Maliza Kupata

Weka (Thamani ya ByVal Kama Boolean) Sawazisha Lock (kabati)

mOK=Thamani

Maliza SyncLock

Maliza Seti

Maliza Mali

Darasa la Mwisho

SyncLock Amri na Monitor Hatari

Kuna baadhi ya hila zinazohusika katika kutumia amri ya SyncLock ambazo hazikuonekana katika mifano rahisi iliyo hapo juu. Kwa hivyo, uchaguzi wa kitu cha maingiliano una jukumu muhimu sana. Jaribu kuendesha programu iliyotangulia kwa amri ya SyncLock(Me) badala ya SyncLock(mHouse). Joto linaongezeka juu ya kizingiti tena!

Kumbuka kwamba amri ya SyncLock inasawazisha kulingana na kitu, kupitishwa kama parameta, na sio kama sehemu ya nambari. Kigezo cha SyncLock hufanya kama mlango wa kufikia kipande kilichosawazishwa kutoka kwa nyuzi zingine. Amri ya SyncLock(Me) kwa kweli hufungua milango kadhaa tofauti, ambayo ndiyo hasa ulikuwa unajaribu kuepusha na maingiliano. Maadili:

Ili kulinda data iliyoshirikiwa katika programu yenye nyuzi nyingi, amri ya SyncLock lazima ilandanishe kwenye kitu kimoja.

Kwa sababu usawazishaji ni mahususi wa kitu, katika hali zingine unaweza kuzuia vipande vingine bila kukusudia. Wacha tuseme una njia mbili zilizosawazishwa, ya kwanza na ya pili, na njia zote mbili zimesawazishwa kwenye kitu kikubwa cha Lock. Uzi wa 1 unapoingiza mbinu kwanza na kunyakua Kifungio kikubwa, hakuna uzi utakaoweza kuingiza njia ya pili kwa sababu ufikiaji wake tayari umezuiwa kwa uzi 1!

Utendaji wa amri ya SyncLock unaweza kuchukuliwa kuwa sehemu ndogo ya utendakazi wa darasa la Monitor. Darasa la Monitor linaweza kusanidiwa sana na linaweza kutumika kutatua matatizo yasiyo ya maana ya ulandanishi. Amri ya SyncLock ni mlinganisho wa takriban wa mbinu za Ingiza na Toka za darasa la Monitor:

Jaribu

Monitor.Enter(theObject) Hatimaye

Monitor.Toka(TheObject)

Maliza Jaribu

Kwa baadhi ya shughuli za kawaida (kuongeza / kupunguza kutofautiana, kubadilishana yaliyomo ya vigezo viwili), NET Framework hutoa darasa la Interlocked, ambalo mbinu zake hufanya shughuli hizi kwa kiwango cha atomiki. Kwa kutumia darasa lililofungwa, shughuli hizi hufanywa kwa kasi zaidi kuliko kutumia amri ya SyncLock.

Kuzuia pande zote

Wakati wa mchakato wa maingiliano, kufuli hupatikana kwa vitu, sio nyuzi, kwa hivyo wakati wa kutumia tofauti vitu vya kuzuia tofauti vipande vya msimbo katika programu wakati mwingine husababisha makosa yasiyo ya maana sana. Kwa bahati mbaya, katika hali nyingi, kusawazisha kwenye kitu kimoja haikubaliki kwa sababu itasababisha nyuzi kuzuia mara nyingi sana.

Hebu fikiria hali hiyo kuingiliana(deadlock) katika umbo lake rahisi zaidi. Hebu fikiria waandaaji programu wawili kwenye meza ya chakula cha jioni. Kwa bahati mbaya, kati ya hao wawili wana kisu kimoja tu na uma moja. Ikiwa tunadhania kwamba kisu na uma zinahitajika kwa kula, hali mbili zinawezekana:

  • Mtayarishaji programu mmoja anafanikiwa kunyakua kisu na uma na kuanza kula. Baada ya kutosha, anaweka kando vyombo vya chakula cha jioni, na kisha programu nyingine inaweza kuchukua.
  • Mtayarishaji mmoja huchukua kisu, na mwingine huchukua uma. Wala hawataweza kuanza kula isipokuwa mikono mingine iwe juu ya chombo chao.

Katika mpango wa thread nyingi, hali hii inaitwa kuzuia pande zote. Njia hizi mbili zimesawazishwa kwa vitu tofauti. Thread A hupata kitu 1 na huingiza kipande cha programu kinacholindwa na kitu hiki. Kwa bahati mbaya, ili kufanya kazi, inahitaji ufikiaji wa msimbo unaolindwa na kizuizi kingine cha Lock Lock na kitu kingine cha ulandanishi. Lakini kabla ya kuingiza kipande kinachosawazishwa na kitu kingine, uzi B huingia na kunyakua kitu hicho. Sasa thread A haiwezi kuingia kipande cha pili, thread B haiwezi kuingia kwenye kipande cha kwanza, na nyuzi zote mbili zinapaswa kusubiri milele. Hakuna uzi unaoweza kuendelea kufanya kazi kwa sababu kitu kinachohitajika kufanya hivyo hakitaachiliwa kamwe.

Utambuzi wa vikwazo ni ngumu na ukweli kwamba wanaweza kutokea katika matukio machache sana. Yote inategemea mpangilio ambao mpangaji huwagawia wakati wa processor. Inawezekana kwamba katika hali nyingi, vitu vya usawazishaji vitapatikana kwa mpangilio ambao hautasababisha msuguano.

Ifuatayo ni utekelezaji wa hali ya msuguano iliyoelezwa hivi punde. Baada ya majadiliano mafupi ya mambo muhimu zaidi, tutaonyesha jinsi ya kutambua hali ya msuguano kwenye dirisha la nyuzi:

Chaguo 1 Imewashwa

2 Imports System.Threading

3 Moduli ya moduli

4 Ndogo Kuu()

5 Dim Tom Kama Kipanga Programu Mpya("Tom")

6 Dim Bob Kama Kipanga Programu Mpya("Bob")

7 Dim aThreadStart Kama Mpya ThreadStart(AnwaniYa Tom.Eat)

8 Dim Thread Kama New Thread (aThreadStart)

9 aThread.Name= "Tom"

10 Dim bThreadStart Kama Mpya ThreadStartAddressYa Bob.Eat)

11 Dim bThread as New Thread (bThreadStart)

12 bThread.Name = "Bob"

13 kwenyeThread.Start()

14 bThread.Start()

15 Maliza Sub

16 Mwisho wa Moduli

17 Uma ya Hatari ya Umma

18 Binafsi Inayoshirikiwa mForkAvaiTable Kama Boolean = Kweli

19 Mmiliki wa Mmiliki wa Pamoja Kama String = "Hakuna mtu"

20 Mali ya Kibinafsi ya Kusoma Peke YanayomilikiUtensil() Kama Kamba

21 Pata

22 Rudisha Mmiliki

23 Mwisho Kupata

24 Maliza Mali

25 Public Sub GrabForktByVal a As Programmer)

26 Console.Writel_ine(Thread.CurrentThread.Name &_

"kujaribu kunyakua uma.")

27 Console.WriteLine(Me.OwnsUtensil & "ina uma.") . .

28 Monitor.Enter(Me) "SyncLock (aFork)"

29 Ikiwa mForkInapatikana Basi

30 a.HasFork = Kweli

31 mMmiliki = a.MyName

32 mForkInapatikana = Uongo

33 Console.WriteLine(a.MyName&"nimepata fork.waiting")

34 Jaribu

Thread.Sleep(100) Catch na As Exception Console.WriteLine (e.StackTrace)

Maliza Jaribu

35 Mwisho Kama

36 Monitor.Toka(Mimi)

Maliza SyncLock

37 Maliza Sub

38 Darasa la Mwisho

39 Kisu cha Hatari ya Umma

40 Binafsi Inayoshirikiwa mKnifeInapatikana Kama Boolean = Kweli

41 Mmiliki wa Mmiliki wa Pamoja Kama String = "Hakuna"

42 Mali ya Kibinafsi ya Kusoma Peke YanayomilikiUtensi1() Kama Mfuatano

43 Pata

44 Rudisha Mmiliki

45 Mwisho Pata

46 Maliza Mali

47 Public Sub GrabKnifetByVal a As Programmer)

48 Console.AndikaLaini(Thread.CurrentThread.Name & _

"kujaribu kunyakua kisu.")

49 Console.WriteLine(Me.OwnsUtensil & "ina kisu.")

50 Monitor.Enter(Me) "SyncLock (aKnife)"

51 Kama mKnifeAvailable Basi

52 mKnifeAvailable = Si kweli

53 a.HasKnife = Kweli

54 mMmiliki = a.MyName

55 Console.WriteLine(a.MyName&"nimepata kisu.inasubiri").

56 jaribu

Uzi.Kulala(100)

Catch e Kama Isipokuwa

Console.WriteLine(e.StackTrace)

Maliza Jaribu

57 Mwisho Kama

58 Monitor.Toka(Mimi)

59 Mwisho Ndogo

60 Darasa la Mwisho

61 Kipanga Programu cha Hatari ya Umma

62 mName ya Kibinafsi Kama Kamba

63 Binafsi Inayoshirikiwa mFork Kama Uma

64 Binafsi Inayoshirikiwa mKnife Kama Kisu

65 mHasKnife ya Kibinafsi Kama Boolean

66 mHasFork Binafsi Kama Boolean

67 Inayoshirikiwa Mpya ()

68 mFork = Uma Mpya()

69 mKnife = Kisu Kipya()

70 Mwisho Sub

71 ya Umma Mpya (ByVal theName As String)

72 mName = theName

73 Mwisho Ndogo

74 Mali ya Umma ya Kusoma Pekee MyName() Kama Kamba

75 Pata

76 Rudisha mName

77 Maliza Kupata

78 Maliza Mali

79 Mali ya Umma HasKnife() Kama Boolean

80 Pata

81 Rudisha mHasKnife

82 Mwisho Pata

83 Set(Thamani ya ByVal Kama Boolean)

84 mHasKnife = Thamani

85 Seti ya Mwisho

86 Maliza Mali

87 Mali ya Umma HasFork() Kama Boolean

88 Pata

89 Rudisha mHasFork

90 Mwisho Kupata

91 Set(Thamani ya ByVal Kama Boolean)

92 mHasFork = Thamani

93 Seti ya Mwisho

94 Maliza Mali

95 Sub Eat()

96 Fanya Mpaka Mimi.HasKnife And Me.HasFork

97 Console. Andika(Thread.CurrentThread.Name&"iko kwenye mazungumzo.")

98 Ikiwa Rnd()< 0.5 Then

99 mFork.GrabFork(Me)

100 Mengine

101 mKnife.GrabKnife(Me)

102 Mwisho Kama

103 Kitanzi

104 MsgBox(Me.MyName & "can eat!")

105 mKnife = Kisu Kipya()

106 mFork= Uma Mpya()

107 Maliza Sub

108 Darasa la Mwisho

Utaratibu kuu (mistari ya 4-16) huunda hali mbili za darasa la Mpangaji na kisha kuanza nyuzi mbili kutekeleza njia muhimu ya Kula ya darasa la Mpangaji (mistari ya 95-108), iliyofafanuliwa hapa chini. Utaratibu kuu huweka majina ya nyuzi na kuzianzisha; Pengine kila kitu kinachotokea ni wazi na bila maoni.

Nambari ya darasa la Fork (mistari 17-38) inaonekana ya kuvutia zaidi (darasa la kisu sawa linafafanuliwa katika mistari 39-60). Mstari wa 18 na 19 huweka maadili ya sehemu za jumla, ambazo zinaweza kutumika kujua ikiwa uma inapatikana kwa sasa, na ikiwa sivyo, ni nani anayeitumia. Mali ya ReadOnly OwnUtensi1 (mstari wa 20-24) imekusudiwa kwa uhamishaji rahisi zaidi wa habari. Msingi wa darasa la Fork ni njia ya GrabFork "kunyakua uma", iliyofafanuliwa kwenye mistari ya 25-27.

  1. Mstari wa 26 na 27 huchapisha tu maelezo ya utatuzi kwenye kiweko. Katika nambari kuu ya njia (mistari 28-36), ufikiaji wa uma unasawazishwa na kitu pe.Mkanda Mimi. Kwa kuwa programu yetu hutumia uma moja pekee, ulandanishi wa Me huhakikisha kuwa nyuzi mbili haziwezi kunyakua kwa wakati mmoja. Amri ya Kulala"p (kwenye kizuizi kinachoanzia mstari wa 34) huiga kuchelewa kati ya kunyakua uma/kisu na kuanza kula. Tafadhali kumbuka kuwa amri ya Kulala haiondoi kufuli kutoka kwa vitu na inaharakisha tu kutokea kwa kufuli!
    Walakini, nambari ya kupendeza zaidi ni nambari ya darasa la Mpangaji (mistari 61-108). Mstari wa 67-70 hufafanua mjenzi wa jumla, ambayo inahakikisha kuwa kuna uma na kisu kimoja tu katika programu. Msimbo wa mali (mistari 74-94) ni rahisi na hauhitaji maoni. Mambo muhimu zaidi hutokea katika njia ya Kula, ambayo inatekelezwa na nyuzi mbili tofauti. Mchakato unaendelea kwa kitanzi hadi uzi fulani unashika uma pamoja na kisu. Kwenye mstari wa 98-102, kitu kinanyakua uma/kisu kwa nasibu kwa kutumia simu ya Rnd, ambayo ndiyo husababisha msuguano. Yafuatayo hutokea:
    Uzi unaotumia mbinu ya Kula ya kitu cha Thoth umewashwa na kuingia kwenye kitanzi. Anashika kisu na kwenda katika hali ya kusubiri.
  2. Uzi unaotumia mbinu ya Kula ya Bob umewashwa na kuingia kwenye kitanzi. Haiwezi kunyakua kisu, lakini inachukua uma na kwenda katika hali ya kungojea.
  3. Uzi unaotumia mbinu ya Kula ya kitu cha Thoth umewashwa na kuingia kwenye kitanzi. Inajaribu kunyakua uma, lakini uma tayari umekamatwa na kitu cha Bob; thread inaingia katika hali ya kusubiri.
  4. Uzi unaotumia mbinu ya Kula ya Bob umewashwa na kuingia kwenye kitanzi. Anajaribu kunyakua kisu, lakini kisu tayari kimekamatwa na kitu cha Thoth; thread inaingia katika hali ya kusubiri.

Haya yote yanaendelea ad infinitum - tunakabiliwa na hali ya kawaida ya kutofaulu (jaribu kuendesha programu, na utaona kuwa hakuna mtu anayeweza kula hivyo).
Unaweza pia kuona ikiwa kizuizi kimetokea kwenye dirisha la nyuzi. Endesha programu na uikate na funguo za Ctrl + Break. Jumuisha utofauti wa Me kwenye poti ya kutazama na ufungue dirisha la mitiririko. Matokeo yake yanaonekana kama yale yaliyoonyeshwa kwenye Mtini. 10.7. Kutoka kwenye picha unaweza kuona kwamba mtiririko wa Bob umekamata kisu, lakini hana uma. Bonyeza kulia kwenye dirisha la Threads kwenye mstari wa Thread na uchague Badilisha hadi Thread kutoka kwa menyu ya muktadha. Eneo la kutazama linaonyesha kuwa Thoth ana uma lakini hana kisu. Kwa kweli, huu sio uthibitisho wa 100%, lakini tabia kama hiyo angalau inakufanya ushuku kuwa kuna kitu kibaya.
Ikiwa chaguo la kusawazisha kwa kitu kimoja (kama ilivyo katika mpango na ongezeko la joto ndani ya nyumba) haiwezekani, ili kuzuia kuzuia kuheshimiana, unaweza kuhesabu vitu vya maingiliano na kukamata daima kwa utaratibu wa mara kwa mara. Wacha tuendelee mlinganisho na watengeneza programu wanaokula chakula cha mchana: ikiwa nyuzi kila wakati inachukua kisu kwanza na kisha uma, hakutakuwa na shida za kufunga. Thread ya kwanza ya kunyakua kisu itaweza kula kawaida. Ikitafsiriwa katika lugha ya mtiririko wa programu, hii inamaanisha kuwa kunasa kitu 2 kunawezekana tu ikiwa kitu 1 kimenaswa hapo awali.

Mchele. 10.7. Uchambuzi wa Kudumu kwenye Dirisha la nyuzi

Kwa hiyo, ikiwa tunaondoa simu ya Rnd kwenye mstari wa 98 na kuibadilisha na kipande

mFork.GrabFork(Mimi)

mKnife.GrabKnife(Me)

msuguano unatoweka!

Shirikiana kwenye data inapoundwa

Katika programu zenye nyuzi nyingi, hali ya kawaida ni ambapo nyuzi hazifanyi kazi tu kwenye data iliyoshirikiwa, lakini pia subiri ionekane (yaani, thread 1 lazima itengeneze data kabla ya thread 2 inaweza kuitumia). Kwa sababu data inashirikiwa, ufikiaji wake lazima ulandanishwe. Inahitajika pia kutoa njia ya kuarifu nyuzi zinazosubiri kuwa data tayari inapatikana.

Hali hii kawaida huitwa tatizo la muuzaji/mtumiaji. Mfululizo unajaribu kufikia data ambayo bado haipo, kwa hivyo ni lazima upitishe udhibiti kwa nyuzi nyingine inayounda data inayohitaji. Shida hutatuliwa na nambari kama hii:

  • Thread 1 (mtumiaji) anaamka, anaingia njia iliyosawazishwa, anatafuta data, haipati, na huenda katika hali ya kusubiri. AwaliHatimaye, ni lazima kutolewa lock ili si kuingilia kati na kazi ya thread mtoa huduma.
  • Thread 2 (mtoa huduma) inaingiza njia iliyosawazishwa iliyotolewa na thread 1, huunda data ya thread 1 na kwa namna fulani inaarifu thread 1 kwamba kuna data. Kisha hutoa kufuli ili uzi wa 1 uweze kuchakata data mpya.

Usijaribu kutatua tatizo hili kwa kuamsha thread 1 mara kwa mara na kuangalia hali ya hali ya kutofautiana ambayo thamani yake imewekwa na thread 2. Suluhisho hili litaathiri sana utendaji wa programu yako, kwani katika hali nyingi thread 1 itawashwa bila malipo. sababu; na thread 2 itasubiri mara nyingi sana kwamba haitakuwa na wakati wa kuunda data.

Mahusiano ya wazalishaji/watumiaji ni ya kawaida sana, kwa hivyo maktaba za darasa la programu zenye nyuzi nyingi huunda asili maalum kwa hali kama hizi. Katika .NET, hizi primitives zinaitwa Wait na Pulse-PulseAl 1 na ni sehemu ya darasa la Monitor. Mchoro 10.8 unaelezea hali ambayo tunakaribia kupanga. Mpango huu unapanga foleni tatu za nyuzi: foleni ya kusubiri, foleni ya kuzuia, na foleni ya utekelezaji. Kipanga ratiba hakitengi muda wa CPU kwa nyuzi kwenye foleni ya kusubiri. Ili thread ipewe muda, lazima ihamie kwenye foleni ya kukimbia. Kama matokeo, utendakazi wa programu umepangwa kwa ufanisi zaidi kuliko kwa kura ya kawaida ya kutofautisha kwa masharti.

Katika pseudocode, nahau ya mtumiaji wa data imeundwa kama ifuatavyo:

"Kuingiza kizuizi kilichosawazishwa kama hiki

Wakati hakuna data

Nenda kwenye foleni ya kusubiri

Kitanzi

Ikiwa kuna data, ichakate.

Acha kizuizi kilichosawazishwa

Mara baada ya amri ya Kusubiri kutekelezwa, thread imesimamishwa, lock inatolewa, na thread inaingia kwenye foleni ya kusubiri. Wakati lock inatolewa, thread katika foleni ya kukimbia ni huru kukimbia. Baada ya muda, nyuzi moja au zaidi zilizozuiwa zitaunda data muhimu kwa uzi kwenye foleni ya kusubiri kufanya kazi. Kwa kuwa uthibitishaji wa data unafanywa kwa kitanzi, mpito kwa matumizi ya data (baada ya kitanzi) hutokea tu wakati kuna data tayari kusindika.

Katika pseudocode, nahau ya mtoaji data inaonekana kama hii:

"Inaingiza kizuizi cha kutazama kilichosawazishwa

Ingawa data haihitajiki

Nenda kwenye foleni ya kusubiri

Vinginevyo Tengeneza data

Baada ya data iliyo tayari kuonekana, piga simu Pulse-PulseAll.

kuhamisha nyuzi moja au zaidi kutoka kwa foleni ya kuzuia hadi kwenye foleni ya kukimbia. Acha kizuizi kilichosawazishwa (na urudi kwenye foleni ya kukimbia)

Wacha tuseme programu yetu ni mfano wa familia iliyo na mzazi mmoja anayepata pesa na mtoto anayetumia pesa. Pesa zikiishainapokelewa, mtoto anapaswa kusubiri kiasi kipya ili kufika. Utekelezaji wa programu ya modeli hii inaonekana kama hii:

Chaguo 1 Imewashwa

2 Imports System.Threading

3 Moduli ya moduli

4 Ndogo Kuu()

5 Punguza Familia Kama Familia Mpya()

6 theFamily.StarttsLife()

7 Maliza Sub

8 Mwisho fjodule

9

10 Familia ya Hatari ya Umma

11 mMoney Binafsi Kama Nambari kamili

12 mWiki ya Faragha Kama Nambari = 1

13 Ndogo Ndogo za KuanzishaMaisha()

14 Dim aThreadStart Kama Mpya ThreadStarUAddressOf Me.Produce)

15 Dim bThreadStart Kama Mpya ThreadStarUAddressOf Me.Consume)

16 Dim Thread Kama New Thread (aThreadStart)

17 Dim bThread as New Thread (bThreadStart)

18 aThread.Name = "Zalisha"

19 aThread.Start()

20 bThread.Name = "Tumia"

21 bMzigo. Anza ()

22 Mwisho Sub

23 Mali ya Umma TheWeek() Kama Nambari kamili

24 Pata

25 Mwezi wa kurudi

26 Mwisho Kupata

27 Set(Thamani ya ByVal Kama Nambari kamili)

Wiki 28 - Thamani

29 Seti ya Mwisho

30 Maliza Mali

31 Mali ya Umma OurMoney() Kama Nambari kamili

32 Pata

33 Rudisha mMoney

34 Mwisho Kupata

35 Set(Thamani ByVal Kama Nambari kamili)

36 mMoney =Thamani

37 Seti ya Mwisho

38 Maliza Mali

39 Bidhaa Ndogo ya Umma()

40 Thread.Lala (500)

41 Fanya

42 Monitor.Ingiza(Mimi)

43 Fanya huku Me.PesaYetu > 0

44 Monitor.Wait(Me)

45 Kitanzi

46 Mimi.Pesa Zetu =1000

47 Monitor.PulseAll(Me)

48 Monitor.Toka(Mimi)

49 Kitanzi

50 Mwisho Sub

51 Matumizi Ndogo ya Umma()

52 MsgBox("Niko kwenye thread inayotumia")

53 Fanya

54 Monitor.Ingiza(Mimi)

55 Fanya Wakati Me.Pesa Yetu = 0

56 Monitor.Wait(Me)

57 Kitanzi

58 Console.WriteLine("Mzazi mpendwa nimetumia pesa zako zote" & _

pesa kwa wiki "&TheWeek)

59 TheWeek += 1

60 Ikiwa TheWeek = 21 *52 Kisha System.Environment.Toka(0)

61 Mimi.Pesa Zetu =0

62 Monitor.PulseAll(Me)

63 Monitor.Toka(Mimi)

64 Kitanzi

65 Mwisho Ndogo

66 Darasa la Mwisho

Mbinu ya StartltsLife (mstari wa 13-22) hujitayarisha kuzindua nyuzi za Produce and Consume. Mambo muhimu zaidi hutokea katika mipasho ya Mazao (mstari wa 39-50) na Tumia (mistari ya 51-65). Utaratibu wa Sub Produce hukagua upatikanaji wa pesa, na ikiwa kuna pesa, huenda kwenye foleni ya kusubiri. Vinginevyo, mzazi hutoa pesa (mstari wa 46) na hujulisha vitu kwenye foleni ya kusubiri kwamba hali imebadilika. Kumbuka kuwa simu ya Pulse-Pulse All huanza kutumika tu kufuli inapotolewa kwa amri ya Monitor.Toka. Kinyume chake, utaratibu wa Sub Consume hukagua upatikanaji wa pesa, na ikiwa hakuna pesa, humjulisha mzazi anayesubiri. Mstari wa 60 humaliza programu tu baada ya miaka 21 ya masharti; piga simu System. Environment.Exit(0) ni .NET sawa na amri ya Mwisho (amri ya Mwisho pia inatumika, lakini tofauti na System. Environment. Toka, hairejeshi msimbo wa kuondoka kwenye mfumo wa uendeshaji).

Mazungumzo yaliyowekwa kwenye foleni ya kusubiri lazima yatolewe na sehemu zingine za programu yako. Hii ndio sababu tunapendelea kutumia PulseAll badala ya Pulse. Kwa kuwa haijulikani mapema ni nyuzi gani itaamilishwa wakati Pulse 1 itaitwa, ikiwa idadi ya nyuzi kwenye foleni ni ndogo, unaweza pia kupiga simu PulseAll.

Multithreading katika programu graphics

Majadiliano yetu ya usomaji mwingi katika programu za GUI itaanza na mfano unaoelezea kwa nini usomaji mwingi unahitajika katika programu za GUI. Unda fomu na vitufe viwili Anza (btnStart) na Ghairi (btnCancel), kama inavyoonyeshwa kwenye Mtini. 10.9. Kubofya kitufe cha Anza hutengeneza darasa ambalo lina mfuatano wa nasibu wa herufi milioni 10 na mbinu ya kuhesabu matukio ya herufi "E" katika mfuatano huo mrefu. Kumbuka matumizi ya darasa la StringBuilder, ambalo hufanya kuunda kamba ndefu kwa ufanisi zaidi.

Hatua ya 1

Thread 1 taarifa kwamba hakuna data kwa ajili yake. Inaita Subiri, inatoa kufuli na kwenda kwenye foleni ya kusubiri



Hatua ya 2

Wakati kufuli inatolewa, uzi wa 2 au uzi wa 3 hutoka kwenye foleni ya kufuli na kuingia kwenye kizuizi kilichosawazishwa, kupata kufuli.

Hatua ya 3

Hebu tuseme thread 3 inaingia kwenye kizuizi kilichosawazishwa, inaunda data, na inaita Pulse-Pulse All.

Mara tu baada ya kuondoka kwenye kizuizi na kutoa kufuli, uzi wa 1 huhamishwa hadi kwenye foleni ya kukimbia. Ikiwa thread 3 itaita Pluse, moja tu ndiyo huenda kwenye foleni ya kukimbiathread, Pluse All inapoitwa, nyuzi zote huenda kwenye foleni ya kukimbia.



Mchele. 10.8. Tatizo la msambazaji/mtumiaji

Mchele. 10.9. Multithreading katika maombi rahisi GUI

Maandishi ya Mfumo

Hatari ya Umma NasibuTabia

Binafsi m_Data Kama StringBuilder

Mjength ya kibinafsi, m_count Kama Nambari kamili

Ndogo ya Umma Mpya (ByVal n Kama Nambari kamili)

m_Urefu = n -1

m_Data = New StringBuilder(m_length) MakeString()

Maliza Sub

Private Sub MakeString()

Punguza Kama Nambari kamili

Dim myRnd Kama Mpya Nasibu()

Kwa i = 0 Hadi m_length

"Tengeneza nambari isiyo ya kawaida kutoka 65 hadi 90,

"Ibadilishe kuwa herufi kubwa

" na ambatisha kwa kitu cha StringBuilder

m_Data.Append(Chr(myRnd.Next(65.90))))

Inayofuata

Maliza Sub

Idadi ndogo ya Mwanzo ya Umma()

GetEes()

Maliza Sub

Pembe ndogo za Kibinafsi()

Punguza Kama Nambari kamili

Kwa i = 0 Hadi m_length

Ikiwa m_Data.Chars(i) = CChar("E") Basi

m_hesabu += 1

Mwisho Ikiwa Inayofuata

m_CountDone = Kweli

Maliza Sub

Usomaji wa Umma Pekee

Mali GetCount() Kama Nambari Inayopata

Ikiwa Sio (m_CountDone) Basi

Rudisha m_count

Mwisho Kama

Maliza Pata Mali

Usomaji wa Umma Pekee

Mali Imefanywa()Kama Boolean Get

Rudi

m_Hesabu Imekamilika

Maliza Kupata

Maliza Mali

Darasa la Mwisho

Kuna msimbo rahisi sana unaohusishwa na vifungo viwili kwenye fomu. Utaratibu wa btn-Start_Click unasisitiza darasa la RandomCharacters hapo juu, ikijumuisha mfuatano wenye herufi milioni 10:

Private Sub btnStart_Click(ByVal mtumaji Kama System.Object.

ByVal e As System.EventArgs) Hushughulikia btnStart.Click

Dim RC Kama Herufi Mpya Za Nasibu(10000000)

RC.StartCount()

MsgBox("Nambari ya es ni " & RC.GetCount)

Maliza Sub

Kitufe cha Ghairi kinaonyesha kisanduku cha ujumbe:

Private Sub btnCancel_Click(ByVal sender As System.Object._

ByVal e As System.EventArgs)Hushughulikia btnCancel.Bonyeza

MsgBox("Hesabu Imeingiliwa!")

Maliza Sub

Unapoanza programu na bonyeza kitufe cha Anza, inageuka kuwa kifungo cha Ghairi haijibu kwa vitendo vya mtumiaji, kwani kitanzi kinachoendelea hairuhusu kifungo kusindika tukio lililopokelewa. Hii haikubaliki katika programu za kisasa!

Kuna masuluhisho mawili yanayowezekana. Chaguo la kwanza, linalojulikana sana kutoka kwa matoleo ya awali ya VB, hutoa kwa multithreading: simu ya DoEvents imejumuishwa kwenye kitanzi. Katika .NET amri hii inaonekana kama hii:

Application.DoEvents()

Katika mfano wetu, hii haifai kabisa - ni nani anataka kupunguza kasi ya programu na simu milioni kumi za DoEvents! Ikiwa badala yake utatenganisha kitanzi kwenye uzi tofauti, mfumo wa uendeshaji utabadilisha kati ya nyuzi na kitufe cha Ghairi bado kitafanya kazi. Utekelezaji na thread tofauti umeonyeshwa hapa chini. Ili kuonyesha wazi kuwa kitufe cha Ghairi kinafanya kazi, tunapobonyeza tunamaliza programu tu.

Hatua inayofuata: Onyesha kitufe cha Hesabu

Wacha tuseme unaamua kuwa mbunifu na kuipa sura mwonekano unaoonyeshwa kwenye Mtini. 10.9. Tafadhali kumbuka: kitufe cha Hesabu ya Onyesho bado hakipatikani.

Mchele. 10.10. Fomu na kifungo kilichozuiwa

Thread tofauti inapaswa kufanya kuhesabu na kufungua kifungo kisichopatikana. Bila shaka inaweza kufanyika; Aidha, kazi hiyo hutokea mara nyingi kabisa. Kwa bahati mbaya, hutaweza kufanya jambo lililo dhahiri zaidi - unganisha uzi wa pili kwenye uzi wa GUI kwa kuhifadhi kumbukumbu kwenye kitufe cha ShowCount kwenye kijenzi, au hata kutumia mjumbe wa kawaida. Kwa maneno mengine, kamwe usitumie chaguo hapa chini (msingi makosa mistari iko katika herufi nzito).

Hatari ya Umma NasibuTabia

Binafsi m_0ata Kama StringBuilder

M_Hesabu ya Kibinafsi Imekamilika Kama Boolean

Mjength ya kibinafsi. m_count Kama Nambari kamili

Kitufe cha m_Binafsi Kama Kitufe cha Windows.Forms

Umma Mpya Mpya(ByVa1 n Kama Nambari kamili,_

ByVal b Kama Kitufe cha Windows.Forms)

m_length = n - 1

m_Data = StringBuilder Mpya(mJength)

m_Button = b MakeString()

Maliza Sub

Private Sub MakeString()

Punguza Mimi Kama Nambari kamili

Dim myRnd Kama Mpya Nasibu()

Kwa mimi = 0 Hadi m_length

m_Data.Append(Chr(myRnd.Next(65. 90))))

Inayofuata

Maliza Sub

Idadi ndogo ya Mwanzo ya Umma()

GetEes()

Maliza Sub

Pembe ndogo za Kibinafsi()

Punguza Mimi Kama Nambari kamili

Kwa mimi = 0 Kwa mjength

Ikiwa m_Data.Chars(I) = CChar("E") Basi

m_hesabu += 1

Mwisho Ikiwa Inayofuata

m_CountDone =Kweli

m_Button.Imewezeshwa=Kweli

Maliza Sub

Usomaji wa Umma Pekee

Mali GetCount()Kama Nambari kamili

Pata

Ikiwa Sio (m_CountDone) Basi

Tupa Isipokuwa Mpya("Hesabu bado haijakamilika") Vinginevyo

Rudisha m_count

Mwisho Kama

Maliza Kupata

Maliza Mali

Mali ya Kusoma Pekee ya Umma Imefanywa() Kama Boolean

Pata

Rudisha m_CountDone

Maliza Kupata

Maliza Mali

Darasa la Mwisho

Kuna uwezekano kwamba nambari hii itafanya kazi katika hali zingine. Hata hivyo:

  • Uzi wa pili hauwezi kuwasiliana na uzi unaounda GUI dhahiri maana yake.
  • Kamwe Usibadilishe vipengee katika programu za michoro kutoka kwa nyuzi zingine za programu. Mabadiliko yote yanapaswa kutokea tu kwenye uzi uliounda GUI.

Ukivunja sheria hizi, sisi tunahakikisha kwamba programu zako za michoro zenye nyuzi nyingi zitaleta hitilafu fiche, fiche.

Pia haitawezekana kupanga mwingiliano wa vitu kwa kutumia matukio. 06 - Mfanyikazi wa tukio huendesha kwenye mazungumzo ambayo simu ya RaiseEvent ilitokea ili matukio yasikusaidie.

Bado, akili ya kawaida inaamuru kwamba programu za picha zinapaswa kuwa na njia ya kurekebisha vipengele kutoka kwa thread nyingine. Mfumo wa NET hutoa njia salama ya kuita njia za utumizi za GUI kutoka kwa mazungumzo mengine. Aina maalum ya mjumbe, Mchochezi wa Mbinu, kutoka kwa nafasi ya majina ya Mfumo.Windows, hutumiwa kwa madhumuni haya. Fomu. Kijisehemu kifuatacho kinaonyesha toleo jipya la mbinu ya GetEes (mistari iliyobadilishwa iko katika herufi nzito):

Pembe ndogo za Kibinafsi()

Punguza Mimi Kama Nambari kamili

Kwa mimi = 0 Hadi m_length

Ikiwa m_Data.Chars(I) = CChar("E")Basi

m_hesabu += 1

Mwisho Ikiwa Inayofuata

m_CountDone = Jaribio la Kweli

Dim mylnvoker Kama Mbinu Mpya(AnwaniYaKitufe Cha Usasishaji)

myInvoker.Invoke() Catch na Kama ThreadlnterruptedException

"Kushindwa

Maliza Jaribu

Maliza Sub

Kitufe cha Kusasisha Ndogo ya Umma()

m_Button.Imewezeshwa =Kweli

Maliza Sub

Simu zenye nyuzi kati hadi kwenye kitufe hazipigwi moja kwa moja, bali kupitia Njia ya Kichochezi. NET Framework inahakikisha kuwa chaguo hili ni salama kwa mazungumzo.

Kwa nini kuna matatizo mengi na programu ya multithreaded?

Kwa kuwa sasa una uelewa fulani wa utayarishaji wa masomo mengi na matatizo yanayoweza kuhusishwa nayo, tuliamua kuwa itakuwa sahihi kujibu swali lililoulizwa katika kichwa cha kifungu mwishoni mwa sura hii.

Moja ya sababu ni kwamba usomaji mwingi ni mchakato usio na mstari, na tumezoea muundo wa programu wa mstari. Mara ya kwanza, ni vigumu kuzoea wazo kwamba utekelezaji wa programu unaweza kuingiliwa bila mpangilio, na udhibiti utahamishiwa kwa msimbo mwingine.

Walakini, kuna sababu nyingine, ya msingi zaidi: watengenezaji programu siku hizi mara chache sana hupanga katika lugha ya kusanyiko au hata kuangalia matokeo yaliyotenganishwa ya mkusanyaji. Vinginevyo, itakuwa rahisi kwao kuzoea wazo kwamba amri moja katika lugha ya kiwango cha juu (kama vile VB .NET) inaweza kuendana na maagizo kadhaa ya mkusanyiko. Thread inaweza kuingiliwa baada ya yoyote ya maagizo haya, na kwa hiyo katikati ya maagizo ya kiwango cha juu.

Lakini sio yote: wakusanyaji wa kisasa huongeza utendaji wa programu, na vifaa vya kompyuta vinaweza kuingilia kati mchakato wa usimamizi wa kumbukumbu. Kwa hivyo, mkusanyaji au maunzi yanaweza kubadilisha agizo la amri lililotajwa katika msimbo wa chanzo cha programu bila wewe kujua [ Wakusanyaji wengi huboresha utendakazi wa kunakili wa safu ya mzunguko wa fomu kwa i=0 hadi n:b(i)=a(i):ncxt. Mkusanyaji (au hata meneja maalum wa kumbukumbu) anaweza tu kuunda safu na kisha kuijaza na operesheni ya nakala moja, badala ya kunakili vitu vya mtu binafsi mara kadhaa!].

Tunatumahi maelezo haya yatakusaidia kuelewa vyema kwa nini upangaji wa nyuzi nyingi husababisha matatizo mengi - au angalau usishangae kidogo na tabia ya ajabu ya programu zako zenye nyuzi nyingi!