Sunt destul de familiarizat cu componentele std :: thread. std :: async și std :: viitoarea bibliotecă standard standard (de exemplu, consultați Acest răspuns), care sunt simple.
Cu toate acestea, nu pot înțelege ceea ce înseamnă std :: promisiunea. ceea ce face și în ce situații este cel mai bine folosit. Documentul cel mai standard nu conține multe informații dincolo de rezumatul său, și nici unul dintre ele nu este ușor. fir.
Poate cineva, vă rog, să dați un scurt și scurt exemplu al unei situații în care std :: promisiunea este necesară și unde este soluția cea mai idiomatică?
În cuvintele [futures.state] std :: viitor - aceasta este un obiect de retur asincron ( „obiect care citește rezultatele stării generale“), și std :: promisiunea - este un furnizor de asincron ( „obiect, care dă rezultatul stării generale“) care este o promisiune - este ceva pe care le-ați instalat rezultatul, astfel încât să puteți obține de viitor.
Un furnizor asincron este ceva care inițial creează o stare generală, la care se referă viitorul. std :: promisiunea este unul dintre tipurile furnizorului asincron, std :: packaged_task este altul, iar partea internă a std :: async este diferită. Fiecare dintre ele poate crea o stare generală și vă poate oferi un std :: viitor care împărtășește această stare și poate face statul pregătit.
std :: async este un utilitar utilitar de nivel superior care vă oferă un obiect rezultat rezultat asincron și are grijă intern să creeze un furnizor asincron și să se asigure că starea generală este gata atunci când sarcina este finalizată. Îl puteți emula cu std :: packaged_task (sau std :: bind and std :: promise) și std :: thread, dar este mai sigur și mai ușor de utilizat std :: async.
std :: promisiunea este un nivel puțin mai mic, pentru că dacă doriți să transmiteți un rezultat asincron viitorului, dar codul care face rezultatul gata nu poate fi completat într-o singură funcție potrivită pentru mutarea în std :: async. De exemplu, puteți avea o serie de promisiuni și viitorul asociat cu acesta și aveți un fir care efectuează mai multe calcule și stabilește rezultatul pentru fiecare promisiune. async vă permite doar să returnați un rezultat pentru a returna mai multe, va trebui să apelați apelul asincron de mai multe ori, ceea ce poate duce la disfuncționalitatea resurselor.
În C ++ există două concepte diferite, deși legate: Calculul asincron (o funcție numită în altă parte) și executarea simultană (firul, ceva ce funcționează simultan). Aceste două concepte ortogonale. Calculul asincron este pur și simplu un apel funcțional excelent, în timp ce un fir este contextul de execuție. Subiectele sunt utile în sine, dar în scopul acestei discuții, le voi considera ca un detaliu al implementării.
Există o ierarhie de abstractizare pentru calculul asincron. De exemplu, să presupunem că avem o funcție care ia câteva argumente:
Mai întâi, avem șablonul std :: viitor
Acum, prin ierarhie, de la cel mai înalt nivel:
std :: async. Cea mai convenabilă și directă modalitate de a efectua calculul asincron este prin intermediul șablonului funcției de asincronizare. care returnează imediat viitorul corespunzător:
Avem un control foarte mic asupra detaliilor. În special, nu știm chiar dacă funcția este executată simultan, alternativ după ce get () sau altă magie neagră. Cu toate acestea, rezultatul este ușor de obținut, dacă este necesar:
Acum putem lua în considerare implementarea unui async. dar într-un mod pe care îl controlează. De exemplu, putem insista că funcția este executată într-un fir separat. Știm deja că putem oferi un fir separat utilizând clasa thread std ::.
Următorul nivel inferior al abstractizării nu face decât acest lucru: std :: packaged_task. Acesta este un șablon care împachetează funcția și oferă un viitor pentru valoarea de retur a funcției, însă obiectul însuși este apelat și apelul este la discreția utilizatorului. Putem configura acest lucru după cum urmează:
Viitorul va fi gata după apelul la sarcină și la sfârșitul apelului. Acesta este un loc ideal pentru un fir separat. Pur și simplu trebuie să mutăm sarcina în flux:
Debitul începe imediat. Putem fie să o detașăm, fie să ni se alăture la sfârșitul scopului sau de fiecare dată (de exemplu, folosind scoped_thread Anthony Williams, care ar trebui să fie în biblioteca standard). Cu toate acestea, detaliile utilizării std :: thread nu ne interesează; Asigurați-vă că vă alăturați sau le deconectați. Important este că, de fiecare dată când se termină apelul funcției, rezultatul nostru este gata:
Acum am scăzut la cel mai scăzut nivel: cum să implementăm sarcina lotului? Aici vine promisiunea std :: promisiunea. O promisiune este un element de bază pentru comunicarea cu viitorul. Etapele de bază:
Firul de asteptare face o promisiune.
Firele de asteptare primesc viitorul din promisiune.
Promisiunea, împreună cu argumentele funcției, este înfășurată într-un fir separat.
Noul fir îndeplinește funcția, iar umplutura îndeplinește promisiunea.
Rezultatul returnează fluxul sursă.
De exemplu, aici este propria noastră "sarcină împachetată":
Utilizarea acestui șablon este în esență aceeași ca și pentru std :: packaged_task. Rețineți că mișcarea întregii sarcini aduce promisiunea mai aproape. În situații mai complexe, puteți, de asemenea, să mutați în mod explicit obiectul promisiunii într-un fir nou și să-l creați un argument funcțional pentru funcția de curgere, însă wrapper-ul de sarcini, similar celui de mai sus, pare mai flexibil și mai puțin intruziv.
Executarea excepțiilor
Promisiunile sunt strâns legate de excepții. Interfața unei singure promisiuni nu este suficientă pentru a-și transfera complet starea, astfel încât excepțiile sunt aruncate ori de câte ori promisiunea nu are sens. Toate excepțiile sunt de tip std :: future_error. care este derivat din std :: logic_error. În primul rând, o descriere a unor limitări:
În mod implicit, promisiunea promisă este inactivă. Promisiunile inactive pot muri fără consecințe.
Promisiunea devine activă atunci când viitorul este obținut prin intermediul get_future (). Cu toate acestea, puteți obține doar un viitor!
Promisiunea trebuie îndeplinită fie cu set_value (), fie set_exception () trebuie să fie setată înainte de set_exception () din durata de viață a acesteia, dacă se folosește viitorul său. O promisiune satisfăcută poate să moară fără consecințe, iar obținerea () va deveni disponibilă în viitor. O promisiune cu excepția va ridica excepția salvată atunci când apelați get () în viitor. Dacă promisiunea nu moare prin valoare sau prin excepție, chemarea get () în viitor va duce la excluderea unei "promisiuni sparte".
Iată o serie mică de teste care demonstrează aceste diferite acțiuni excepționale. În primul rând, hamul:
Acum despre teste.
Cazul 1: Promisiunea inactivă
Cazul 2: promisiuni active, neutilizate
Cazul 3: Prea multe futures
Cazul 4: Promisiunea satisfăcută
Cazul 5: prea multă satisfacție
Aceeași excepție este set_value dacă există mai mult de una dintre valorile set_value sau set_exception.
Cazul 6: Excepție
Cazul 7: O promisiune spartă
C ++ întrerupe implementarea contractelor futures într-un set de blocuri mici
Std. bal este una dintre aceste părți.
O promisiune este un mijloc de transmitere a valorii returnate (sau excepției) din firul care execută funcția într-un fir care utilizează funcția viitorului.
Viitorul este obiectul de sincronizare, construit în jurul capătului de primire al canalului promis.
Deci, dacă doriți să utilizați viitorul, veți obține promisiunea pe care o utilizați pentru a obține rezultatul prelucrării asincrone.
Exemplu de la pagina:
În aproximație, puteți trata std :: promisiunea ca fiind celălalt capăt al std :: viitorului (acest lucru nu este adevărat, dar pentru ilustrare, vă puteți gândi ca și cum ar fi fost). Capătul consumatorului de legătură va folosi std :: future pentru a folosi datele din starea generală, în timp ce firul producătorului va folosi std :: promisiunea de a scrie la starea generală.
std :: promisiunea este canalul sau calea pentru informația returnată de la funcția de asincronizare. std :: future este un mecanism de sincronizare care determină apelantul să aștepte până când valoarea returnată va trece la std :: promisiunea este returnată (aceasta înseamnă că valoarea sa este stabilită în interiorul funcției).
Procesarea asincronă are 3 obiecte principale. În prezent, C ++ 11 se concentrează pe două dintre ele.
Funcțiile de bază necesare pentru logica asincronă sunt:
- O sarcină (logică împachetată ca un obiect functor) care va fi "undeva".
- Nodul real de procesare este firul, procesul și așa mai departe. Care lansează astfel de funcții când le sunt furnizate. Uită-te la modelul de design "Command" pentru a obține o idee despre modul în care bazinul de lucru principal face acest lucru.
- Managerul de rezultate. cineva are nevoie de acest rezultat și are nevoie de un obiect care să-l primească pentru ei. Pentru OOP și din alte motive, orice așteptare sau sincronizare ar trebui să fie efectuată în API-ul acestui descriptor.
C ++ 11 este ceea ce spun (1) std :: promisiune. precum și (3) std :: viitor. std :: fir - singurul lucru care este disponibil publicului (2). Acest lucru este regretabil, pentru că programele reale necesare pentru a controla fluxul de resurse și de memorie, și cele mai multe dintre ele doresc să ruleze sarcini în bazine, cursuri de apă, în loc de a crea și de a distruge un fir pentru fiecare mică problemă (care provoacă aproape întotdeauna imagini inutile ale performanței în sine și pot crea cu ușurință resurse Postul este chiar mai rău).
Ca punct final pentru perechea promis / viitor, std este creat. get_future () și std :: future (create din std. get_future () folosind get_future ()) este un alt punct final. Aceasta este o metodă simplă, cu o singură lovitură, care oferă posibilitatea de a sincroniza două fire, deoarece un fir oferă date unui alt fir prin mesaj.
Vă puteți gândi la acest lucru, deoarece un fir creează o promisiune de a furniza date și un alt fir colectează o promisiune în viitor. Acest mecanism poate fi folosit o singură dată.
Mecanismul promis / viitor este doar o direcție, din firul care folosește metoda set_value () pentru std :: promise set_value () pentru firul care utilizează get () pentru std :: future pentru a obține datele. O excepție este aruncată dacă metoda get () a viitorului este apelată de mai multe ori.
Dacă un fir cu std :: promisiune set_value () nu este utilizat set_value () să-și îndeplinească promisiunile, atunci când al doilea fir numește get () din std :: viitor pentru a colecta angajamentele, al doilea flux intră în starea de așteptare, până când promisiunea este realizată de primul flux cu std :: promise set_value () atunci când folosește metoda set_value () pentru a trimite date.
O notă despre acest exemplu este întârzierile adăugate în diferite locuri. Aceste întârzieri au fost adăugate numai pentru a se asigura că diferitele mesaje trimise la consola folosind std :: cout. va fi clar și că textul din mai multe fluxuri nu va fi amestecat.
Prima parte a main () creează trei fire suplimentare și folosește std :: promise și std :: future pentru a trimite date între fire. Un punct interesant este faptul că fluxul de fluxul principal începe, T2, care va aștepta datele din fluxul principal, ceva de făcut, și apoi trimite datele la al treilea flux este T3, care apoi să facă ceva și a trimite date înapoi la fluxul principal.
A doua parte a main () creează două fire și un set de cozi pentru a permite mai multor mesaje de la firul principal la fiecare dintre cele două fire create. Nu putem folosi std :: promise și std :: future pentru acest lucru, pentru că duo-ul promis / viitor este o singură lovitură și nu poate fi folosit în mod repetat.
Sursa pentru clasa Sync_queue este limba de programare C ++ a lui Stroustrup: ediția a 4-a.
Această aplicație simplă creează următoarea ieșire.
O promisiune este celălalt capăt al firului.
Imaginați-vă că trebuie să obțineți valoarea viitoare. calculați modalitatea de asincronizare. Cu toate acestea, nu doriți ca acesta să fie calculat într-un singur fir, și nici măcar nu se poate crea un flux de „acum“ - probabil, software-ul a fost proiectat pentru a selecta fluxul de la piscina, astfel încât să nu știu cine va efectua orice oprire la sfârșitul anului de calcul.
Acum, ce treceți la acest fir (încă necunoscut) fir / clasă / entitate? Nu treceți de viitor. deoarece acesta este rezultatul. Vrei să transmiți ceva care are legătură cu viitorul și care reprezintă celălalt capăt al firului. astfel încât veți cere pur și simplu viitorul fără să știți cine calculează / scrie.
Aceasta este o promisiune. Acesta este stiloul asociat cu viitorul tău. Dacă viitorul este un vorbitor. și folosind get (), începeți să ascultați până când apare un sunet, promisiunea este un microfon; Dar nu doar un microfon, este un microfon conectat la un fir la difuzorul pe care îl dețineți. Puteți ști cine este la celălalt capăt, dar nu trebuie să știți - dați-l și așteptați până când cealaltă parte spune ceva.