Vytváranie a čakanie na vykonanie vlákien. C vlákno - čo to je? choré vlákno


Detailný popis

Protothreads sú typom ľahkých vlákien bez zásobníka navrhnutých pre systémy s nízkou pamäťou, ako sú napríklad vstavané systémy mikrokontrolérov alebo uzly senzorovej siete.

Protothreads poskytujú lineárne spúšťanie kódu pre systémy riadené udalosťami implementované v C. Protothreads môžu byť použité s alebo bez RTOS.

Protothreads poskytujú kontext blokovania čakania na vrchole systému riadeného udalosťami bez réžie zásobníka pre jedno vlákno. Účelom vlákien je implementovať sekvenčné vykonávanie kódu bez použitia zložitých stavových strojov alebo multithreadingu. Protothreads poskytujú podmienené blokovanie kódu v tele funkcií C.

Výhodou protothreadov je, že sa s nimi dosahuje čistý mechanizmus udalostí, v ktorom je možné blokovať lineárne vykonávanie kódu funkcie podľa požadovanej podmienky. V čisto udalostiach založených systémoch musí byť blokovanie implementované manuálne rozdelením funkcie na 2 časti – jedna časť pre kód pred volaním blokovania a druhá časť kódu po volaní blokovania. To sťažuje použitie riadiacich štruktúr, ako je napríklad if() stavový príkaz a while() slučky.

Výhodou protothreadov oproti bežným vláknam je, že protothread nevyžaduje pre seba samostatný zásobník. V systémoch, kde je pamäť vzácnym zdrojom, môže alokácia viacerých zásobníkov viesť k nadmernému využívaniu pamäte. Na rozdiel od bežného streamu vyžaduje protostream na uloženie 2 až 12 bajtov stavu v závislosti od použitej architektúry.

Poznámky: Pretože protovlákna neukladajú kontext do zásobníka medzi blokovacími volaniami, lokálne premenné nebudú pretrvávať, keď je protovlákno zablokované. To znamená, že lokálne premenné by sa mali používať opatrne - ak máte pochybnosti, nepoužívajte lokálne premenné v protovlákne!

Hlavné rysy:

  • Žiadny kód viazaný na assembler - knižnica protothread je napísaná v čistom jazyku C.
  • Funkcie náchylné na chyby ako longjmp() sa nepoužívajú.
  • Veľmi nízka spotreba RAM - iba 2 bajty na protostream.
  • Môže byť použitý ako operačným systémom (kde je multithreading), tak aj bez neho.
  • Poskytuje čakanie na blokovanie bez zapojenia viacerých vlákien alebo zásobníka.

Príklady aplikácií, kde môžete použiť:

  • Systémy s pamäťovými obmedzeniami.
  • Zásobníky protokolov riadené udalosťami.
  • Veľmi malé vstavané systémy.
  • Sieťové uzly pre senzory.

Protothreads API pozostáva zo 4 základných operácií. Sú to PT_INIT() inicializácia, PT_BEGIN() spustenie, PT_WAIT_UNTIL() podmienené blokovanie a PT_END() ukončenie. Okrem toho sú tu pre pohodlie ďalšie 2 funkcie, ktoré blokujú pri opačnej podmienke PT_WAIT_WHILE() a blokujú v protovlákne PT_WAIT_THREAD() .

Pozri tiež: Protothread API dokumentácia

Autori

Knižnicu Protothread napísal Adam Dunkels s podporou Olivera Schmidta .

Protothreads

Protothreads sú extrémne ľahké vlákna bez zásobníkov, ktoré poskytujú kontext blokovania na vrchole systému riadeného udalosťami bez réžie zásobníka jednotlivých vlákien. Účelom protothreadov je implementovať sekvenčný tok vykonávania bez zložitých stavových strojov alebo plného multithreadingu. Protothreads poskytujú podmienené blokovanie kódu v tele funkcií C.

V systémoch s obmedzenou pamäťou (ako sú systémy založené na mikrokontroléroch) vedie tradičné multithreading k príliš veľkej spotrebe pamäte. Pri tradičnom multithreadingu vyžaduje každé vlákno samostatný zásobník, ktorý môže zaberať veľa pamäte.

Hlavnou výhodou protothreadov v porovnaní s bežnými vláknami je to, že protothread je veľmi ľahký a nevyžaduje pre seba samostatný zásobník. Namiesto toho všetky protovlákna používajú rovnaký systémový zásobník a prepínanie kontextu nastáva prevíjaním zásobníka. To je výhoda v systémoch, kde je pamäť vzácny zdroj, pretože prideľovanie viacerých zásobníkov pre vlákna môže plytvať pamäťou. Protostream vyžaduje iba 2 bajty na jeden protostream. Okrem toho sú protovlákna implementované v čistom jazyku C a nevyžadujú architektonicky špecifický assemblerový kód.

Protothread beží v rámci jednej funkcie C a nemôže zahŕňať iné funkcie. Protovlákno môže volať bežné funkcie C, ale blokovanie v rámci volanej funkcie nie je možné. Namiesto blokovania v rámci volanej funkcie sa pre každú potenciálne zablokovanú funkciu vytvorí samostatné protovlákno. Výhodou tohto prístupu je explicitné blokovanie: programátor presne vie, ktoré funkcie blokujú vykonávanie a ktoré nie.

Protothreads sú podobné asymetrickým korutínam (korutinám). Hlavný rozdiel od korutínov je v tom, že korutíny používajú zásobník pre každý korutín, zatiaľ čo protovlákna nepoužívajú samostatný zásobník pre seba. Mechanizmus, ktorý sa najviac podobá protovláknom, sa nachádza v generátoroch Pythonu. Majú tiež bezskladový dizajn, len iný účel. Protothreads poskytujú kontextové zámky vo funkcii C, zatiaľ čo generátory Pythonu poskytujú viacero výstupných bodov z funkcie generátora.

Lokálne premenné

Poznámky: Keďže protovlákna neukladajú kontext do zásobníka medzi volaniami blokovania, lokálne premenné sa neuložia, keď protovlákno získa blokovanie. To znamená, že lokálne premenné by sa mali používať opatrne – ak máte pochybnosti, nepoužívajte lokálne premenné v protovlákne!

Plánovanie (plánovač úloh) protothreadov

Protothread je riadený opakovanými volaniami funkcie, v ktorej je spustené protothread. Zakaždým, keď je funkcia zavolaná, protothread pobeží, kým sa nezamkne alebo neukončí. Plánovanie teda vykonáva aplikácia, ktorá využíva protovlákna.

Implementácia

Protothreads sú implementované pomocou lokálnych pokračovaní. Lokálne pokračovanie predstavuje aktuálny stav vykonávania na určitom mieste v programe, ale neposkytuje žiadnu históriu hovorov ani lokálne premenné. V samostatnej funkcii je možné nastaviť lokálne pokračovanie na zachytenie stavu funkcie. Po nastavení lokálneho pokračovania môže pokračovať ako obnovenie stavu funkcie v bode, kde bolo nastavené lokálne pokračovanie. Poznámka. prekladateľ: znie to ako nezmysel, samozrejme, ale niečo sa vyjasní, keď sa pozriete na kód makier protothread a ako sa používajú - napríklad v sieťovej aplikácii hello-world, ktorá je postavená na protovlákne.

Lokálne pokračovanie je možné realizovať niekoľkými spôsobmi:

  1. pomocou kódu assembleru špecifického pre architektúru,
  2. pomocou štandardných C konštruktov, príp
  3. pomocou rozšírení kompilátora.

Prvá metóda funguje tak, že uloží a obnoví stav procesora, s výnimkou ukazovateľov zásobníka, a vyžaduje 16 až 32 bajtov na protovlákno. Presné množstvo pamäte závisí od použitej architektúry procesora.

Implementácia v štandarde C vyžaduje iba 2 bajty na protothread na uloženie stavu a používa príkaz C switch() nezjavným spôsobom. Táto implementácia však prináša malé obmedzenie pre kód, ktorý používa protothreads – samotný kód nemôže používať príkazy switch().

Niektoré kompilátory majú rozšírenia C, ktoré možno použiť na implementáciu protothreadov. GCC podporuje ukazovatele štítkov, ktoré možno použiť na tento účel. S touto implementáciou budú protovlákna vyžadovať 4 bajty RAM na jedno vlákno.

Makrá

Príklady: dhcpc.c

Príklady: dhcpc.c

Pozri tiež: PT_SPAWN() Príklady: dhcpc.c

Pozri definíciu v súbore

Čo má spoločné tričko a počítačový program? Obe pozostávajú z mnohých vlákien! Zatiaľ čo vlákna na tričku držia látku pohromade, C vlákno (doslova - „nity“ alebo „nity“) operačný systém skombinovať všetky programy, aby ste mohli vykonávať sekvenčné alebo paralelné akcie súčasne. Každé vlákno v programe identifikuje proces, ktorý sa spustí, keď si to systém (systémové vlákno C) vyžiada. Tým sa optimalizuje prevádzka takého zložitého zariadenia ako je Osobný počítač a má pozitívny vplyv na jeho rýchlosť a výkon.

Definícia

V informatike C je vlákno alebo vlákno vykonávania najmenšia postupnosť inštrukcií, ktorá je riadená nezávislým plánovačom, ktorý je zvyčajne neoddeliteľnou súčasťou operačného systému.

Vláknam sa zvyčajne dáva určitá priorita, čo znamená, že niektoré vlákna majú prednosť pred inými. Keď procesor dokončí spracovanie jedného vlákna, môže spustiť ďalšie, ktoré čaká vo fronte. Čakanie zvyčajne nepresiahne niekoľko milisekúnd. Počítačové programy, ktoré implementujú "multitreading", môžu vykonávať niekoľko vlákien naraz. Väčšina moderných operačných systémov podporuje vlákno C na úrovni systému. To znamená, že keď sa jeden program pokúsi odobrať všetky zdroje procesora, systém násilne prepne na iné programy a prinúti program podpory procesora rozdeliť zdroje rovnomerne.

Výraz „vlákno“ (C Thread) môže označovať aj sériu súvisiacich príspevkov v online diskusii. Webové nástenky sa skladajú z mnohých tém alebo vlákien. Odpovede odoslané ako odpoveď na pôvodný príspevok sú súčasťou toho istého vlákna. IN e-mail vlákno môže odkazovať na sériu odpovedí vo forme príkazov „späť“ a „dopredu“ súvisiacich s konkrétnou správou a štruktúruje strom konverzácie.

Viacvláknové vlákno C v systéme Windows

V počítačovom programovaní je jednoduché vlákno spracovanie jednej inštrukcie naraz. Opakom jedného vlákna je viacvláknové. Oba termíny sú široko používané v komunite funkčného programovania.

Multithreading je podobný multitaskingu, ale umožňuje spracovať viacero vlákien naraz, ale nie viacero procesov. Keďže vlákna sú menšie a ovládané jednoduchšími inštrukciami, viacvláknové spracovanie sa môže vyskytnúť aj v rámci procesov.

Príklady fungovania nástroja úloh C Thread

Viacvláknový operačný systém môže súčasne spúšťať viacero úloh na pozadí, ako je protokolovanie zmien súborov, indexovanie údajov a správa okien. Webové prehliadače, ktoré podporujú multithreading, môžu otvárať viacero okien s JavaScriptom a Flashom súčasne. Ak je program plne viacvláknový, rôzne procesy by sa nemali navzájom rušiť, pokiaľ má procesor dostatok výkonu na ich spracovanie.

Rovnako ako multitasking, aj multithreading zlepšuje stabilitu programov. Viacvláknové spracovanie môže zabrániť zlyhaniu programu a zabrániť zlyhaniu počítača. Keďže každé vlákno sa spracováva samostatne, chyba v jednom z nich nemôže narušiť PC. Multithreading teda môže viesť k menšiemu počtu pádov v operačnom systéme ako celku.

multitasking

Multitasking spracováva niekoľko úloh paralelne a charakterizuje aj fungovanie počítača. Procesor dokáže spracovať niekoľko procesov naraz s absolútnou presnosťou. Spracováva však iba pokyny, ktoré mu softvér pošle. Preto, aby sa naplno využili výhody CPU, softvér musí byť schopný zvládnuť viac úloh naraz a tiež musí byť schopný vykonávať viacero úloh naraz.

Historická retrospektíva

Prvé operačné systémy mohli spúšťať viacero programov naraz, ale nepodporovali plne multitasking. Jeden program by mohol spotrebovať všetky zdroje CPU pri vykonávaní určitej operácie. Základné úlohy operačného systému, ako je kopírovanie súborov, bránili používateľovi vykonávať iné úlohy (napríklad otváranie alebo zatváranie okien).

Moderné operačné systémy zahŕňajú plnú podporu multitaskingu – viacero softvérových riešení môže bežať súčasne bez toho, aby si navzájom rušili funkčnosť.

Multitasking tiež zlepšuje stabilitu vášho počítača. Napríklad, ak jeden z procesov vypadne, neovplyvní to ostatné spustené programy pretože počítač rieši každý proces samostatne. Dá sa to prirovnať k procesu písania listu: ak ste v strede listu a už ste napísali časť textu, ale váš webový prehliadač sa neočakávane ukončí, neprídete o už vykonanú prácu.

Jednoprocesorové a viacprocesorové systémy

Implementácia technológií vlákien a procesorov sa líši v závislosti od operačného systému, ale najčastejšie je vlákno súčasťou procesu. Súčasne môže v jednom procese existovať niekoľko vlákien, ktoré vykonávajú a zdieľajú zdroje. Najmä procesné vlákna C Thread zdieľajú spustiteľný kód a hodnoty premenných v akomkoľvek danom čase.

Systémy s jedným procesorom implementujú časovo orientované multithreading: CPU(CPU) prepína medzi rôznymi vláknami softvér. Vo viacprocesorovom ako aj viacjadrovom systéme beží množstvo vlákien paralelne, pričom každý procesor alebo jadro súčasne vykonáva samostatné vlákno.

Typy prúdov

Plánovače procesov väčšiny moderných operačných systémov priamo podporujú prechodné aj viacprocesové vlákna, zatiaľ čo jadro operačného systému umožňuje vývojárom spravovať vlákna poskytovaním požadované funkcie cez rozhranie systémového volania. Niektoré implementácie vlákien sa nazývajú vlákna jadra, zatiaľ čo ľahké spracovanie (LWP) je typ vlákna, ktoré zdieľa rovnaký informačný stav. Aj softvérové ​​riešenia môžu mať vlákna používateľského priestoru, keď sa používajú s časovačmi (časovač vlákna C), signálmi alebo inými metódami na prerušenie ich vlastného vykonávania, pričom sa vykonáva určitý druh časovania ad hoc.

Vlákna a procesy: rozdiely

Vlákna sa od klasických multitaskingových procesov OS líšia nasledujúcimi spôsobmi:

    procesy sú zvyčajne nezávislé, zatiaľ čo vlákna existujú ako podmnožiny procesu;

    procesy nesú oveľa viac informácií ako prúdy;

    procesy majú vyhradené adresné priestory;

    procesy interagujú iba prostredníctvom mechanizmov systémovej komunikácie;

    prepínanie kontextu medzi vláknami v procese je rýchlejšie ako prepínanie kontextu medzi procesmi.

Preventívne a kolaboratívne plánovanie

Vo viacužívateľských operačných systémoch je preemptívne multithreading rozšírenejším prístupom na riadenie času vykonávania prostredníctvom prepínania kontextu. Proaktívne plánovanie však môže viesť k nekontrolovanému stanovovaniu priorít a zlyhaniam programátorov. Na rozdiel od toho sa spoločné vlákno spolieha na vlákna, ktoré sa vzdajú kontroly vykonávania. To môže spôsobiť problémy, ak je zdieľané multitaskingové vlákno zablokované čakaním na zdroj.

Technologický vývoj

Až do začiatku roku 2000. na väčšine stolné počítače existoval iba jeden jednojadrový procesor, ktorý nepodporoval hardvérové ​​vlákna. V roku 2002 Intel predstavil podporu pre simultánny multithreading na procesore Pentium 4, ktorý sa nazýva Hyper-Threading. V roku 2005 dvojjadrový procesor a dvojjadrový procesor AMD Athlon 64X2.

Procesory v integrovaných systémoch s vyššími požiadavkami na akciu v reálnom čase sú schopné podporovať multithreading, čím skracujú čas prepínania vlákien a používajú vyhradený súbor registra pre každé vlákno.

Modelky

Uvádzame hlavné modely implementácie.

1:1 (vlákno na úrovni jadra) − Vlákna vytvorené používateľom v jadre sú najjednoduchšou možnou implementáciou vlákien. OS/2 a Win32 používajú tento prístup natívne, zatiaľ čo Linux Thread join implementuje tento prístup cez NPTL alebo staršie LinuxThreads. Tento prístup využívajú aj Solaris, NetBSD, FreeBSD, macOS a iOS.

N:1 (používateľské vlákno) – Tento model zabezpečuje, že všetky vlákna na úrovni aplikácie sú namapované na jeden plánovaný objekt na úrovni jadra. S týmto prístupom je možné prepínanie kontextu vykonať veľmi rýchlo a okrem toho ho možno implementovať aj na jadrách, ktoré nepodporujú vlákna. Jednou z hlavných nevýhod je však to, že z toho nemá prospech hardvérová akcelerácia na viacvláknových procesoroch alebo počítačoch. Napríklad: ak sa jedno z vlákien musí vykonať na vstupno-výstupnej požiadavke, celý proces je zablokovaný a vlákno sa nedá použiť. V GNU Portable C sa výnimka vlákna používa ako vláknová úroveň používateľa.

M: N (hybridná implementácia) - model mapuje určitý počet aplikačných vlákien pre nejaký počet N buniek jadra alebo " virtuálne procesory". Toto je kompromis medzi vláknami jadra („1:1“) a používateľskými („N:1“). Streamovacie systémy "M:N" sú zložitejšie, pretože sú potrebné zmeny jadra aj používateľského kódu. V implementácii M:N je knižnica vlákien zodpovedná za plánovanie vlákien na dostupných plánovacích entitách. Vďaka tomu je kontext najoptimálnejší, pretože sa vyhýba systémovým volaniam. To však zvyšuje zložitosť a pravdepodobnosť inverzie, ako aj suboptimálne plánovanie bez rozsiahlej (a nákladnej) koordinácie medzi plánovačom užívateľskej krajiny a plánovačom jadra.

Príklady hybridnej implementácie sú aktivácia plánovača používaná natívnou implementáciou knižnice POSIX NetBSD (pre model M:N, na rozdiel od modelu implementácie jadra 1:1 alebo modelu používateľského priestoru).

Odľahčené procesy používané staršími verziami operačného systému Solaris (Std Thread C toolkit).

Podpora programovacích jazykov

Mnoho formálnych systémov podporuje funkciu vlákna. Implementácie C a C++ implementujú túto technológiu a poskytujú prístup k natívnym API pre operačný systém. Niektoré programovacie jazyky sú viac vysoký stupeň, ako sú jazyky Java, Python a .NET Framework, odhaľujú vlákna vývojárom a zároveň abstrahujú špecifické rozdiely v implementácii vlákien za behu. Ďalšie jazykové rozšírenia sa tiež snažia abstrahovať od vývojára koncept súbežnosti a vlákna. Niektoré jazyky sú navrhnuté pre sériový paralelizmus pomocou GPU.

Množstvo interpretovaných jazykov má implementácie, ktoré podporujú vytváranie vlákien a paralelné spracovanie, ale nie paralelné vykonávanie vlákien vďaka globálnemu zámku tlmočníka (GIL). GIL je zámok mutex implementovaný tlmočníkom, ktorý môže zabrániť interpretácii kódu aplikácie na dvoch alebo viacerých vláknach súčasne, čo obmedzuje súbežnosť na viacjadrových systémoch.

Iné programovacie implementácie, ako napríklad Tcl, používajú rozšírenie Thread sleep C. Tým sa vyhnete maximálnemu limitu GIL použitím modelu, kde obsah a kód musia byť explicitne „zdieľané“ medzi vláknami.

Aplikačné programovacie jazyky riadené udalosťami, ako je Verilog a rozšírenie Thread sleep C, majú odlišný model vlákna, ktorý podporuje maximálny počet vlákien na simuláciu hardvéru.

Praktický multithreading

Viacvláknové knižnice iniciujú volanie funkcie na vygenerovanie nového vlákna, ktoré berie funkčnú hodnotu ako parameter. Potom sa vytvorí nové paralelné vlákno a spustí sa spracovanie spustenej funkcie s následným návratom. Programovacie jazyky obsahujú knižnice vlákien, ktoré zahŕňajú globálne synchronizačné funkcie, ktoré vám umožňujú vytvárať a úspešne implementovať bezchybné multithreading pomocou mutexov, premenlivé podmienky, kritické sekcie, monitory a iné typy synchronizácie.

modul závitovanie bol prvýkrát predstavený v Pythone 1.5.2 ako rozšírenie modulu nízkoúrovňových vlákien. závitový modul výrazne zjednodušuje prácu s vláknami a umožňuje spustenie programu viacero operácií súčasne. Všimnite si, že vlákna v Pythone fungujú najlepšie s I/O operáciami, ako je sťahovanie zdrojov z internetu alebo čítanie súborov a priečinkov v počítači.

Ak potrebujete urobiť niečo, čo je náročné na CPU, možno by ste sa mali pozrieť na modul multiprocessing, namiesto závitovanie. Dôvodom je, že Python obsahuje Global Interpreter Lock (GIL), ktorý spúšťa všetky vlákna v hlavnom vlákne. Z tohto dôvodu, keď potrebujete spustiť nejaké intenzívne operácie s vláknami, všimnete si, že všetko je dosť pomalé. Zameriame sa teda na to, v ktorých vláknach sú najlepšie: I/O operácie.

malý úvod

Vlákno vám umožňuje spustiť kus dlhého kódu, ako keby to bol samostatný program. Je to niečo ako volanie zdedeného procesu, ibaže namiesto samostatného programu voláte funkciu alebo triedu. Vždy som našiel konkrétne príklady mimoriadne užitočné. Pozrime sa na niečo celkom jednoduché:

Import threading def doubler(number): """ Funkcia, ktorú môže vlákno použiť """ print(threading.currentThread().getName() + "\n") print(number * 2) print() ak __name__ == "__main__": for i in range(5): my_thread = threading.Thread(target=doubler, args=(i,)) my_thread.start()

Tu dovážame závitový modul a vytvorte pravidelnú funkciu nazývanú zdvojovač. Naša funkcia nadobúda hodnotu a zdvojnásobuje ju. Vypíše tiež názov vlákna, ktoré volá funkciu, a na konci vytlačí prázdny riadok. Ďalej v poslednom bloku kódu vytvoríme päť vlákien a postupne spustíme každé z nich.

Pomocou multithreadingu môžete vyriešiť mnoho bežných problémov. Napríklad nahranie videa alebo iného materiálu do sociálne médiá, ako je napríklad Youtube alebo Facebook. Na rozvoj vášho Youtube kanála môžete použiť https://publbox.com/ru/youtube, ktorý prevezme správu vášho kanála. Youtube je skvelý zdroj príjmov a čím viac kanálov, tým lepšie. Bez Publboxu sa nezaobídete.

Všimnite si, že keď definujeme vlákno, nastavíme jeho cieľ na náš funkcia zdvojovača, a tiež odovzdáme argument funkcii. Dôvod, prečo parameter args vyzerá trochu čudne, je ten, že do funkcie zdvojovača potrebujeme odovzdať sekvenciu a vyžaduje len jeden argument, takže na vytvorenie sekvencie jedného z nich musíme na koniec pridať čiarku. Všimnite si, že ak chcete počkať na definovanie vlákna, môžete zavolať jeho metódu pripojiť sa(). Keď spustíte tento kód, dostanete nasledujúci výstup:

Závit-1 0 Závit-2 2 Závit-3 4 Závit-4 6 Závit-5 8

Samozrejme, pravdepodobne nebudete chcieť vytlačiť svoj výstup na stdout. To môže skončiť veľkým chaosom. Namiesto toho musíte použiť modul Python s názvom ťažba dreva. Je to modul bezpečný pre vlákna a robí svoju prácu dobre. Poďme trochu aktualizovať náš príklad a pridať protokolovací modul a zároveň volajte naše streamy:

Import loggingu import threading def get_logger(): logger = logging.getLogger("threading_example") logger.setLevel(logging.DEBUG) fh = logging.FileHandler("threading.log") fmt = "%(asctime)s - %( threadName)s - %(levelname)s - %(message)s" formatter = logging.Formatter(fmt) fh.setFormatter(formatter) logger.addHandler(fh) return logger def doubler(number, logger): """ A funkcia, ktorú môže vlákno použiť """ logger.debug("vykonávanie dvojitej funkcie") výsledok = číslo * 2 logger.debug("funkcia zdvojenia ukončená: ()".format(result)) if __name__ == " __main__": logger = get_logger() thread_names = ["Mike", "George", "Wanda", "Dingbat", "Nina"] for i in range(5): my_thread = threading.Thread(target=doubler, name =thread_names[i], args=(i,logger)) my_thread.start()

Najväčšou zmenou v tomto kóde je pridanie funkcie get_logger. Táto časť kódu vytvorí záznamník, ktorý je nastavený na ladenie. Tým sa uloží protokol do aktuálneho pracovného priečinka (inými slovami, odkiaľ sa skript spúšťa) a následne nastavíme formát každého riadku, ktorý sa má protokolovať. Formát obsahuje časovú pečiatku, názov streamu, úroveň protokolovania a protokolovanú správu. Vo funkcii zdvojovača zmeníme naše výstupné príkazy na príkazy ťažba dreva.

Všimnite si, že záznamník odovzdávame funkcii zdvojovača, keď vytvoriť stream. Je to preto, že ak zadefinujete objekt protokolovania v každom vlákne, získate ich niekoľko singletons a váš denník bude obsahovať veľa duplicitných riadkov. Nakoniec svoje streamy pomenujeme vytvorením zoznamu mien a potom každému streamu nastavíme konkrétny názov pomocou parametra name. Keď spustíte tento kód, mali by ste získať súbor denníka s nasledujúcim obsahom:

Tento výstup je celkom samovysvetľujúci, takže poďme ďalej. V tomto článku sa chcem venovať ešte jednému problému. Povieme si o triednom dedičstve tzv navliekanie.Niť. Pozrime sa ešte raz na predchádzajúci príklad, len namiesto priameho volania vlákna si vytvoríme vlastnú podtriedu. Tu je aktualizovaný kód:

Import protokolovania import trieda vlákna MyThread(threading.Thread): def __init__(self, number, logger): threading.Thread.__init__(self) self.number = číslo self.logger = logger def run(self): """ Spustiť vlákno """ logger.debug("Calling doubler") doubler(self.number, self.logger) def get_logger(): logger = logging.getLogger("threading_example") logger.setLevel(logging.DEBUG) fh = logging .FileHandler("threading_class.log") fmt = "%(asctime)s - %(threadName)s - %(levelname)s - %(message)s" formatter = logging.Formatter(fmt) fh.setFormatter(formatter) logger.addHandler(fh) return logger def doubler(number, logger): """ Funkcia, ktorú môže použiť vlákno """ logger.debug("doubler function executing") result = number * 2 logger.debug( "funkcia zdvojovača sa skončila: ()".format(result)) if __name__ == "__main__": logger = get_logger() thread_names = ["Mike", "George", "Wanda", "Dingbat", "Nina" ] for i in range(5): thread = MyThread(i, logger) thread.setName(thr read_names[i]) thread.start()

V tomto príklade sme práve zdedili triedu navliekanie.Niť. Prešli sme v počte, ktorý chceme zdvojnásobiť a prešli sme aj v prihlásenom objekte, tak ako predtým. Tentoraz však nastavíme názov streamu inak volaním funkcie setName v objekte potoka. Stále musíme volať štart v každom vlákne, ale nezabudnite, že to nemusíme definovať v zdedenej triede. Keď zavoláte štart, spustí sa vaše vlákno volaním metóda spustenia. V našej triede voláme funkciu zdvojovača, aby sme vykonali naše výpočty. Výstup je veľmi podobný predchádzajúcemu príkladu, až na to, že som k výstupu pridal ďalší riadok. Skúste to sami a uvidíte, čo sa stane.

Zámky a synchronizácia

Ak máte k dispozícii viac ako jedno vlákno, možno budete musieť pochopiť ako vyhnúť sa konfliktom. Tým chcem povedať, že môžete použiť prípad, keď viac ako jedno vlákno potrebuje prístup k rovnakému zdroju súčasne. Ak na takéto problémy nemyslíte a podľa toho neplánujete, potom môžete naraziť na najhoršie problémy v mimoriadne nevhodných časoch a zvyčajne v čase vydania kódu.

Riešenie problémov je používať zámky. Zámok poskytovaný modulom Python závitovanie a môže držať jednu niť alebo žiadnu niť. Ak sa vlákno pokúsi získať zámok na zdroj, ktorý je už zatvorený, toto vlákno počká, kým sa zámok neotvorí. Pozrime sa na praktický príklad jedného kódu, ktorý nemá žiadnu funkciu uzamykania, ale pokúsime sa ho pridať:

Importovať vlákno celkom = 0 def update_total (suma): """ Aktualizuje celkovú sumu o zadanú sumu """ celkový celkový počet += tlač sumy (celkom), ak __name__ == "__main__": pre i v rozsahu (10): my_thread = threading.Thread(target=update_total, args=(5,)) my_thread.start()

Tento príklad môžeme urobiť ešte zaujímavejším pridaním hovoru čas.spánok. Preto je tu problém, že jedno vlákno môže volať update_total a pred aktualizáciou ho môže zavolať iné vlákno a pokúsiť sa ho tiež aktualizovať. V závislosti od poradia operácií možno hodnotu pridať iba raz. K funkcii pridáme zámok. Existujú dva spôsoby, ako to urobiť. Prvým je použitie skús/konečne ak sa chceme uistiť, že je zámok vypnutý. Tu je príklad:

Importovať vlákno celkom = 0 lock = threading.Lock() def update_total(suma): """ Aktualizuje celkovú sumu o zadanú sumu """ globálny celkový lock.acquire() try: total += suma nakoniec: lock.release( ) print (total) if __name__ == "__main__": for i in range(10): my_thread = threading.Thread(target=update_total, args=(5,)) my_thread.start()

Tu len zavesíme zámok predtým, ako urobíme čokoľvek iné. Ďalej sa pokúsime aktualizovať Celkom A konečne, odstránime zámok a zobrazíme aktuálny Celkom. Môžeme zjednodušiť túto úlohu, pomocou príkazu Pythonu s názvom s:

Importovať vlákno celkom = 0 lock = threading.Lock() def update_total(suma): """ Aktualizuje súčet o danú sumu """ globálny súčet so zámkom: total += množstvo tlače (celkom), ak __name__ == "__main__ ": for i in range(10): my_thread = threading.Thread(target=update_total, args=(5,)) my_thread.start()

Ako vidíte, už nepotrebujeme skús/konečne, pretože kontextový manažér poskytovaný operátorom s urobil to všetko za nás. Samozrejme, môžete sa ocitnúť pri písaní kódu tam, kde sú potrebné viaceré vlákna s prístupom k viacerým funkciám. Keď prvýkrát začnete písať konkurenčný kódex, môžete urobiť niečo takéto:

Importovať vlákno celkom = 0 lock = threading.Lock() def do_something(): lock.acquire() try: print("Zámok získaný vo funkcii urobiť_niečo") nakoniec: lock.release() print("Zámok uvoľnený vo funkcii urobiť_niečo function") return "Done doing something" def do_something_else(): lock.acquire() try: print("Zámok získaný vo funkcii do_something_else") nakoniec: lock.release() print("Zámok uvoľnený vo funkcii do_something_else") návrat "Dokončil som niečo iné", ak __name__ == "__main__": result_one = urob_niečo() vysledok_dva = urob_niečo_ostatné()

Tento kód v tomto prípade funguje dobre, ale predpokladá, že ho máte viaceré vlákna ktoré volajú obe tieto funkcie. Zatiaľ čo jedno vlákno pracuje na funkciách, druhé môže naopak aktualizovať údaje a dostanete nesprávny výsledok. Problém je v tom, že si najskôr nemusíte všimnúť, že s výsledkami nie je niečo v poriadku. Ako nájsť riešenie tohto problému? Poďme sa na to pozrieť. Prvá vec, ktorú môžete vymyslieť, je uzamknúť dve volania funkcií. Pokúsme sa aktualizovať vyššie uvedený príklad, aby mal niečo ako nasledovné:

Import threading total = 0 lock = threading.RLlock() def do_something(): with lock: print("Zámok získaný vo funkcii urobiť_niečo") print("Zámok uvoľnený vo funkcii urobiť_niečo") return "Vykonať niečo" def do_something_else (): so zámkom: print("Zámok získaný vo funkcii urobiť_niečo_ostatné") print("Zámok uvoľnený vo funkcii urobiť_niečo_ostatné") return "Dokončiť niečo iné" def main(): so zámkom: result_one = urobiť_niečo() vysledok_dva = urobiť_niečo_ostatné () print (result_one) print (result_two) if __name__ == "__main__": main()

Keď spustíte tento kód, uvidíte, že jednoducho visí. Dôvodom je, že modul jednoducho povieme závitovanie zavesiť visiaci zámok. Takže keď zavoláme prvú funkciu, vidí, že zámok je už hore a zamkne sa. Toto bude trvať až do odstránenia zámku, čo sa nikdy nestane, pretože to nie je uvedené v kóde. Dobré rozhodnutie v tomto prípade použite vratný zámok. modul závitovanie poskytuje jednu ako funkciu RLock. Stačí vymeniť zámok linky = závitovanie.Zámok() na zámku= navliekanie.RLlock() a skúste znova spustiť kód. Teraz musí zarábať. Ak chcete vyskúšať kód uvedený vyššie, ale pridať k nemu vlákna, môžeme ho nahradiť hovor na Hlavná nasledujúcim spôsobom:

If __name__ == "__main__": for i in range(10): my_thread = threading.Thread(target=main) my_thread.start()

Toto spustí hlavnú funkciu v každom vlákne, čo následne spôsobí zavolanie ďalších dvoch funkcií. Nakoniec dostanete pomerne veľký problém.

Časovače

modul závitovanie obsahuje jednu veľmi šikovnú triedu tzv časovač, ktorý môžete použiť na spustenie akcie po určitom čase. Táto trieda spúšťa svoje vlastné vlákno a začína pracovať od toho istého štartovacia metóda() rovnako ako bežné streamy. Časovač môžete zastaviť aj pomocou metódy zrušenia. Upozorňujeme, že časovač môžete zrušiť ešte pred jeho spustením. Raz som mal prípad, keď som potreboval komunikovať s podprocesom, ktorý som začal, ale potreboval som odpočítavanie. Napriek existencii čísla rôznymi spôsobmi riešenie tohto konkrétneho problému, mojím obľúbeným riešením bolo vždy použitie Trieda časovača závitový modul. V tomto príklade sa pozrieme na použitie príkazu ping. na linuxe, príkaz ping bude fungovať, kým ho nezabijete. Takže trieda Timer sa stáva obzvlášť užitočnou vo svete Linuxu. Tu je príklad:

Importovať podproces z vlákna import Timer kill = lambda process: process.kill() cmd = ["ping", "www.google.com"] ping = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE ) my_timer = Timer(5, kill, ) try: my_timer.start() stdout, stderr = ping.communicate() nakoniec: my_timer.cancel() print (str(stdout))

Tu práve nastavujeme lambdu, ktorú môžeme použiť na zabitie procesu. Ďalej začneme našu prácu ping a vytvorte objekt časovača. Všimnite si, že prvý argument je časový limit v sekundách, potom funkcia, ktorá sa má volať, a argument, ktorý sa má funkcii odovzdať. V našom prípade je naša funkcia lambda a odovzdávame jej zoznam argumentov, kde zoznam obsahuje iba jeden prvok. Ak spustíte tento kód, pobeží asi 5 sekúnd, po ktorých vytlačí výsledok pingu.

Ostatné komponenty vlákna

modul závitovanie zahŕňa aj podporu pre iné objekty. Môžete napríklad vytvoriť semafor, ktorý je jedným z najstarších synchronizačných primitívov v informatike. Ovládanie semaforu vnútorné počítadlo, ktorá sa pri zavolaní zníži získať a zvyšuje sa, keď zavoláte uvoľniť. Počítadlo je navrhnuté tak, aby nemohlo klesnúť pod nulu. Ak sa teda stane, že zavoláte získať keď je nula, potom sa zablokuje.

Ďalším užitočným nástrojom obsiahnutým v module je udalosť. S ním môžete získať komunikáciu medzi dvoma vláknami pomocou signálov. Na príklady použitia Event sa pozrieme v nasledujúcom článku. Nakoniec v Pythone 3.2 bol pridaný objekt Bariéra. Toto je primitívum, ktoré riadi oblasť vlákien bez ohľadu na to, kde by vlákna mali čakať, kým na ne príde rad. Aby vlákno prekonalo bariéru, musí zavolať metódu počkaj(), ktorá bude blokovať, kým všetky vlákna nevykonajú hovor. Potom pôjdu všetky prúdy súčasne ďalej.

Streamové pripojenie

Existuje množstvo prípadov, keď potrebujete mať vlákna navzájom prepojené. Ako som už spomenul, môžete použiť udalosť pre tento účel. Ale pohodlnejší spôsob je použiť Fronta. V našom príklade používame obe metódy! Pozrime sa, ako to bude vyzerať:

Import vlákna z fronty import Queue def creator(data, q): """ Vytvorí údaje, ktoré sa majú spotrebovať, a čaká, kým spotrebiteľ dokončí spracovanie """ print("Vytváranie údajov a ich vkladanie do frontu") pre položku v údajoch : evt = threading.Event() q.put((item, evt)) print("Čaká sa na zdvojnásobenie údajov") evt.wait() def my_consumer(q): """ Spotrebuje nejaké údaje a pracuje na nich V tomto prípade všetko, čo robí, je zdvojnásobiť vstup """, zatiaľ čo True: data, evt = q.get() print("údaje sa majú spracovať: ()".format(data)) spracované = údaje * 2 print (spracované) evt.set() q.task_done() if __name__ == "__main__": q = Queue() data = thread_one = threading.Thread(target=creator, args=(data, q)) thread_two = threading. Thread(target=my_consumer, args=(q,)) thread_one.start() thread_two.start() q.join()

Trochu spomaľme. Po prvé, máme funkciu tvorca(taktiež známy ako výrobca), ktoré používame na vytváranie údajov, s ktorými chceme pracovať (alebo používať). Ďalej dostaneme ďalšiu funkciu, ktorú používame na spracovanie údajov, tzv my_consumer. Funkcia tvorcu používa metódu Fronta volaný put na pridanie údajov do frontu, potom spotrebiteľ následne skontroluje nové údaje a spracuje ich, keď sú k dispozícii. Fronta vybavuje všetko zatváranie a otváranie zámkov, aby vám tento osud nehrozil osobne.

V tomto príklade sme vytvorili zoznam hodnôt, ktoré chceme duplikovať. Ďalej vytvoríme dve vlákna, jedno pre funkciu tvorca/producent, druhý pre spotrebiteľ(spotrebiteľ). Všimnite si, že prechádzame objektom Fronta na vlákno, čo je priam magické vzhľadom na to, ako sa narába so zámkami. Fronta začne prvým vláknom, ktoré odošle údaje do druhého vlákna. Keď prvé vlákno odošle nejaké údaje do frontu, pošle ich aj do udalosť, potom pred dokončením čaká na udalosti. Ďalej sa v spotrebiteľskej funkcii spracúvajú údaje a potom sa zavolá metóda nastavenia udalosť, ktorý hovorí prvému vláknu, že druhé vlákno dokončilo spracovanie, takže môže pokračovať. Posledný riadok kódových hovorov spájacia metóda objekt Queue, ktorý hovorí Queue, aby počkal, kým vlákna nedokončia spracovanie. Prvé vlákno končí, keď už nemá čo poslať do fronty.

Zhrnutie

Pokryli sme veľa materiálu. menovite:

  1. Základy práce s modulom závitovania
  2. Ako fungujú zámky
  3. Čo je udalosť a ako ju možno použiť
  4. Ako používať časovač
  5. Komunikácia v rámci vlákna pomocou fronty/udalosti

Teraz viete, ako používať streamy a v čom sú dobré. Dúfam, že pre ne nájdete využitie vo svojom vlastnom kóde!

Značky: pthreads, pthread_create, pthread_join, EINVAL, ESRCH, EDEADLK, EDEADLOCK, EAGAIN, EPERM, PTHREAD_THREADS_MAX, odovzdávanie argumentov do vlákna, vrátenie argumentov z vlákna, chyby pthread_create, chyby pthread_join, čakanie na vlákno, spájanie vlákien, spájanie vlákien, príklad.

Vytváranie a čakanie na vlákno

Zvážte jednoduchý príklad

#include #include #include #include #define ERROR_CREATE_THREAD -11 #define ERROR_JOIN_THREAD -12 #define SUCCESS 0 void* helloWorld(void *args) ( printf("Dobrý deň z vlákna!\n"); return SUCCESS; ) int main() ( vlákno pthread_t; stav int; int status_addr; status = pthread_create(&vlákno, NULL, helloWorld, NULL); if (stav != 0) ( printf("hlavná chyba: nemožno vytvoriť vlákno, stav = %d\n", stav); exit(ERROR_CREATE_THREAD ); ) printf("Dobrý deň!\n"); stav = pthread_join(vlákno, (neplatné**)&status_addr); if (stav != ÚSPECH) ( printf("hlavná chyba: nemožno sa pripojiť k vláknu, stav = %d\n", status); exit(ERROR_JOIN_THREAD); ) printf("pripojené s adresou %d\n", status_addr); _getch(); return 0; )

V tomto príklade vo vnútri hlavného vlákna, v ktorom je hlavná funkcia, vytvorí sa nové vlákno, v rámci ktorého sa volá funkcia helloWorld. Funkcia helloWorld zobrazí pozdrav. Pozdrav sa zobrazuje aj vo vnútri hlavného vlákna. Prúdy sa potom zlúčia.

Nové vlákno sa vytvorí pomocou funkcie pthread_create

int pthread_create(*ptherad_t, const pthread_attr_t *attr, void* (*start_routine)(void*), void *arg);

Funkcia dostáva ako argumenty smerník na vlákno, premennú typu pthread_t, do ktorej v prípade úspešného dokončenia uloží id vlákna. pthread_attr_t - atribúty vlákna. Ak sa použijú predvolené atribúty, možno zadať hodnotu NULL. start_routin je priamo funkcia, ktorá bude vykonaná v novom vlákne. arg sú argumenty, ktoré budú odovzdané funkcii.

Vlákno môže robiť veľa rôznych vecí a prijímať rôzne argumenty. Na tento účel funkcia, ktorá bude spustená v novom vlákne, prevezme argument typu void*. Vďaka tomu môžete všetky odovzdané argumenty zabaliť do štruktúry. Hodnotu môžete vrátiť aj prostredníctvom odovzdaného argumentu.

Ak je úspešná, funkcia vráti 0. Ak sa vyskytnú chyby, môžu sa vrátiť nasledujúce hodnoty

  • ZNOVU– systém nemá prostriedky na vytvorenie nového vlákna alebo systém už nemôže vytvárať vlákna, pretože počet vlákien prekročil hodnotu PTHREAD_THREADS_MAX (napríklad na jednom zo strojov používaných na testovanie je toto magické číslo 2019 )
  • EINVAL– neplatné atribúty streamu (predané argumentom attr)
  • EPERM– Volajúce vlákno nemá správne práva na nastavenie požadované parametre alebo politika plánovača.

Poďme si prejsť program

#define ERROR_CREATE_THREAD -11 #define ERROR_JOIN_THREAD -12 #define ÚSPECH 0

Tu nastavíme množinu hodnôt potrebných na riešenie prípadných chýb.

Void* helloWorld(void *args) ( printf("Ahoj z vlákna!\n"); návrat ÚSPECH; )

Toto je funkcia, ktorá bude spustená v samostatnom vlákne. Nebude dostávať žiadne argumenty. Podľa štandardu explicitné ukončenie funkcie volá funkciu pthread_exit a návratová hodnota bude odovzdaná pri volaní funkcie pthread_join ako stavu.

Status = pthread_create(&vlákno, NULL, helloWorld, NULL); if (stav != 0) ( printf("hlavná chyba: nemožno vytvoriť vlákno, stav = %d\n", stav); exit(ERROR_CREATE_THREAD); )

Tu sa vytvorí a okamžite spustí nové vlákno. Vlákno neprijíma žiadne atribúty ani argumenty. Po vytvorení vlákna sa vykoná kontrola chýb.

Status = pthread_join(thread, (void**)&status_addr); if (stav != ÚSPECH) ( printf("hlavná chyba: nemožno sa pripojiť k vláknu, stav = %d\n", stav); exit(ERROR_JOIN_THREAD); )

Spôsobí, že hlavné vlákno čaká na dokončenie podriadeného vlákna. Funkcia

int pthread_join(pthread_t thread, void **value_ptr);

Odloží vykonanie volajúceho (túto funkcie) vlákna až do vykonania niť. Keď pthread_join uspeje, vráti 0. Ak vlákno explicitne vrátilo hodnotu (to je hodnota SUCCESS z našej funkcie), potom bude umiestnená do premennej value_ptr. Možné chyby vrátené pthread_join

  • EINVAL– vlákno ukazuje na nezlúčené vlákno
  • ESRCH- neexistuje vlákno s rovnakým identifikátorom, ktorý je uložený v premennej vlákna
  • EDEADLK– bolo zistené uviaznutie (vzájomný blok) alebo bolo ako zlúčené vlákno zadané samotné volajúce vlákno.

Príklad vytvárania vlákien s odovzdávaním argumentov do nich

Povedzme, že chceme odovzdať dáta do streamu a vrátiť niečo späť. Povedzme, že pošleme reťazec do prúdu a vrátime dĺžku tohto reťazca z prúdu.

Keďže funkcia môže prijať iba ukazovateľ neplatnosti, všetky argumenty by mali byť zabalené do štruktúry. Definujme novú štruktúru typu:

Typedef struct someArgs_tag ( int id; const char *msg; int out; ) someArgs_t;

Tu id je identifikátor streamu (v našom príklade vo všeobecnosti nie je potrebný), druhé pole je reťazec a tretie je dĺžka reťazca, ktorý vrátime.

Vo vnútri funkcie prehodíme argument na požadovaný typ, vytlačíme reťazec a vypočítanú dĺžku reťazca vložíme do štruktúry.

Void* helloWorld(void *args) ( someArgs_t *arg = (someArgs_t*) args; int len; if (arg->msg == NULL) ( return BAD_MESSAGE; ) len = strlen(arg->msg); printf(" %s\n", arg->msg); arg->out = len; návrat ÚSPECH; )

V prípade, že všetko prebehlo v poriadku, potom vráťte hodnotu SUCCESS ako stav a ak došlo k chybe (v našom prípade, ak bol odovzdaný nulový reťazec), tak skončíme so stavom BAD_MESSAGE.

V tomto príklade vytvoríme 4 vlákna. Pre 4 vlákna budete potrebovať pole typu pthread_t s dĺžkou 4, pole argumentov, ktoré sa majú odovzdať, a 4 reťazce, ktoré prejdeme.

pthread_t vlákna; stav int; int i; int stav_addr; someArgs_t args; const char *messages = ( "Prvá", NULL, "Tretia správa", "Štvrtá správa" );

Najprv vyplníme hodnoty argumentov.

Pre (i = 0; i< NUM_THREADS; i++) { args[i].id = i; args[i].msg = messages[i]; }

Pre (i = 0; i< NUM_THREADS; i++) { status = pthread_create(&threads[i], NULL, helloWorld, (void*) &args[i]); if (status != 0) { printf("main error: can"t create thread, status = %d\n", status); exit(ERROR_CREATE_THREAD); } }

Potom čakáme na dokončenie

Pre (i = 0; i< NUM_THREADS; i++) { status = pthread_join(threads[i], (void**)&status_addr); if (status != SUCCESS) { printf("main error: can"t join thread, status = %d\n", status); exit(ERROR_JOIN_THREAD); } printf("joined with address %d\n", status_addr); }

Na konci tiež vypíšeme argumenty, ktoré teraz ukladajú vrátené hodnoty. Všimnite si, že jeden z argumentov je "zlý" (reťazec je NULL). Tu úplný kód

#include #include #include #include #include #define ERROR_CREATE_THREAD -11 #define ERROR_JOIN_THREAD -12 #define BAD_MESSAGE -13 #define SUCCESS 0 typedef struct someArgs_tag ( int id; const char *msg; int out; ) someArgs_t; void* helloWorld(void *args) ( someArgs_t *arg = (someArgs_t*) args; int len; if (arg->msg == NULL) ( return BAD_MESSAGE; ) len = strlen(arg->msg); printf(" %s\n", arg->msg); arg->out = len; návrat ÚSPECH; ) #define NUM_THREADS 4 int main() (pthread_t threads; int status; int i; int status_addr; someArgs_t args; const char * správy = ( "Prvá", NULL, "Tretia správa", "Štvrtá správa"); pre (i = 0; i< NUM_THREADS; i++) { args[i].id = i; args[i].msg = messages[i]; } for (i = 0; i < NUM_THREADS; i++) { status = pthread_create(&threads[i], NULL, helloWorld, (void*) &args[i]); if (status != 0) { printf("main error: can"t create thread, status = %d\n", status); exit(ERROR_CREATE_THREAD); } } printf("Main Message\n"); for (i = 0; i < NUM_THREADS; i++) { status = pthread_join(threads[i], (void**)&status_addr); if (status != SUCCESS) { printf("main error: can"t join thread, status = %d\n", status); exit(ERROR_JOIN_THREAD); } printf("joined with address %d\n", status_addr); } for (i = 0; i < NUM_THREADS; i++) { printf("thread %d arg.out = %d\n", i, args[i].out); } _getch(); return 0; }

Urobte to niekoľkokrát. Všimnite si, že poradie, v ktorom sa vlákna vykonávajú, nie je deterministické. Spustením programu môžete zakaždým získať iný príkaz na vykonanie.



Načítava...
Hore