std::promise set_value og trådsikkerhed

std::promise set_value og trådsikkerhed

Hvis det ikke var et atomlager, så kunne to tråde samtidigt kalde promise::set_value , som gør følgende:

  1. kontroller, at fremtiden ikke er klar (dvs. har en gemt værdi eller undtagelse)
  2. gem værdien
    • marker staten klar
    • frigiv alt, der blokerer, når den delte tilstand bliver klar

Ved at gøre denne sekvens atomisk, kommer den første tråd, der udføres (1), hele vejen igennem til (3), og enhver anden tråd, der kalder promise::set_value på samme tid vil fejle ved (1) og hæve en future_error med promise_already_satisfied .

Uden atomiciteten kunne to tråde potentielt lagre deres værdi, og så ville den ene med succes markere staten klar, og den anden ville rejse en undtagelse, dvs. det samme resultat, undtagen at det måske var værdien fra tråden, der så en undtagelse, der slap igennem.

I mange tilfælde er det måske ligegyldigt, hvilken tråd 'vinder', men når det gør noget, uden atomicitetsgarantien ville du være nødt til at vikle endnu en mutex omkring promise::set_value opkald. Andre tilgange såsom sammenligning-og-bytte ville ikke fungere, fordi du ikke kan tjekke fremtiden (medmindre det er en shared_future ) for at se, om din værdi vandt eller ej.

Når det er ligegyldigt, hvilken tråd 'vinder', kan du give hver tråd sin egen fremtid og bruge std::experimental::when_any for at indsamle det første resultat, der tilfældigvis blev tilgængeligt.

Rediger efter nogle historiske undersøgelser:

Selvom ovenstående (to tråde, der bruger det samme løfteobjekt) ikke virker som en god use-case, var det bestemt forudset af en af ​​de nutidige artikler om introduktionen af ​​future til C++:N2744. Dette papir foreslog et par use-cases, som havde så modstridende tråde, der kalder set_value , og jeg vil citere dem her:


Du gik glip af hele ideen om løfter og fremtider.

Normalt har vi et par løfter og en fremtid. løftet er det objekt, du skubber på det asynkrone resultat eller undtagelsen, og fremtiden er det objekt, du trækker det asynkrone resultat eller undtagelsen.

I de fleste tilfælde ligger fremtiden og løfteparret ikke på samme tråd (ellers ville vi bruge en simpel pointer). så du kan videregive løftet til en eller anden tråd, threadpool eller en eller anden asynkron funktion i et tredje bibliotek og indstille resultatet derfra og trække resultatet i opkaldstråden.

indstil resultatet med std::promise::set_value skal være atomisk, ikke fordi mange løfter sætter resultatet, men fordi et objekt (fremtiden) som ligger på en anden tråd skal læse resultatet, og at gøre det uatomisk er udefineret adfærd, så at sætte værdien og trække den (enten ved at ringer til std::future::get eller std::future::then ) skal ske atomært

Husk, at enhver fremtid og enhver løfte har en delt tilstand , indstilling af resultatet fra en tråd opdaterer den delte tilstand, og få resultatet læst fra den delte tilstand. ligesom enhver delt tilstand/hukommelse i C++, når det er udført fra flere tråde, skal opdateringen/læsningen ske under en lås. ellers er det udefineret adfærd.