c ++ - Tilføjelse af trådsikkerhed til en simpel logfunktion?

Indlæg af Hanne Mølgaard Plasc

Problem



Ud fra det jeg læser er standard udgangsstrømme generelt ikke trådsikker. Jeg har en C ++-applikation (Windows-baseret, ved hjælp af Visual Studio 2005), som har en meget enkel logfunktion:


void logText(string text)
{
    if(g\_OutputLogEnabled && g\_OutputLog.is\_open())
    {
        string logDate = getDateStamp("\%Y-\%m-\%d \%H:\%M:\%S");
        g\_OutputLog << "[" << logDate << "]: " << text << endl;
    }

    cout << text << endl; // Also echo on stdout
}


I dette eksempel er g\_OutputLog en forstrøm, og g\_OutputLogEnabled er en boolsk.


Jeg har brugt denne lille funktion i min hovedapplikation uden problemer, men jeg vil nu udvide brugen til nogle børnetråde. Disse tråde gør arbejde og asynkront print data som det arbejde er udført.


Spørgsmål: Hvordan kan jeg tilføje enkel trådbeskyttelse på trådløst niveau til denne rutine? Alt jeg virkelig bryr mig om er, at hver linje, der kommer ind, forbliver intakt i min log. Performance er ikke et problem i dette tilfælde, men (som altid) hurtigere er pænere.


Jeg er klar over, at jeg kunne bruge tredjeparts loggingpakker, men Jeg vil gøre det selv, så jeg kan lære, hvordan det virker . Min multi-threading viden er ikke, hvad det skal være, og jeg ' Jeg forsøger at forbedre det.


Jeg har hørt begrebet kritiske afsnit, og jeg er lidt opmærksom på mutexes og semaforer, men som vil jeg bruge i dette tilfælde? Er der en ren, enkel løsning? Tak på forhånd for ethvert råd. [16]

Bedste reference


Brug scoped lås som:


void logText(string text)
{
    if(g\_OutputLogEnabled && g\_OutputLog.is\_open())
    {
        string logDate = getDateStamp("\%Y-\%m-\%d \%H:\%M:\%S");

        boost::scoped\_lock (g\_log\_mutex);  //lock
        g\_OutputLog << "[" << logDate << "]: " << text << endl;

    } //mutex is released automatically here

    boost::scoped\_lock (g\_cout\_log\_mutex); //lock on different mutex!
    cout << text << endl; // Also echo on stdout
}


Eller du kan bruge std::unique\_lock hvis din compiler understøtter dette. [17]





Hvordan ville du implementere scoped\_lock, hvis du ikke kan bruge Boost, og hvis du ikke har std::unique\_lock?



Definer først mutex klasse:


#include <Windows.h>

class mutex : private CRITICAL\_SECTION  //inherit privately!
{
public:
     mutex() 
     {
        ::InitializeCriticalSection(this);
     }
     ~mutex() 
     {
        ::DeleteCriticalSection(this);
     }
private:

     friend class scoped\_lock;  //make scoped\_lock a friend of mutex

     //disable copy-semantic 
     mutex(mutex const &);           //do not define it!
     void operator=(mutex const &);  //do not define it!

     void lock() 
     {
        ::EnterCriticalSection(this);
     }
     void unlock() 
     {
        ::LeaveCriticalSection(this);
     }
};


Definer derefter scoped\_lock som:


class scoped\_lock
{
      mutex & m\_mutex;
  public:
      scoped\_lock(mutex & m) : m\_mutex(m) 
      {
          m\_mutex.lock();
      }
      ~scoped\_lock()
      {
          m\_mutex.unlock();
      }
};


Nu kan du bruge dem.

Andre referencer 1


I betragtning af at funktionen er tydeligvis ikke præstationsorienteret, skal du tilføje en simpel mutex og lås, før du skriver til strømmen.


Selvfølgelig af sikkerhedsmæssige grunde skal låsen automatisk frigives, så sørg for at bruge RAII.


Jeg vil anbefale et kig på Boost.Threads biblioteket.

Andre referencer 2


Ja, du bør tilføje en slags beskyttelse til din kode. Du behøver ikke noget eksotisk, du vil have adgang til en ressource (din strøm), mens den kan blive brugt af noget andet. To typer synkroniseringsobjekter fungerer godt med denne opgave: Kritiske sektioner og Mutex For mere information om låse kan du begynde at læse denne artikel på Wikipedia. Mutex er normalt langsommere, men kan deles på tværs af processer, det er ikke tilfældet, så du kan bruge en enkel kritisk sektion til at synkronisere dine tråde . [18] [19]


Få tips, hvis du ikke har planer om at bruge et 3. del bibliotek (som den store Boost).


Når EnterCriticalSection fungerer for at erhverve låsen. Hvis ressourcen er låst af en anden, vil din tråd blive suspenderet og genaktiveret, når ressourcen frigives af ejeren (desuden kan din låste tråd muligvis øge prioriteten). For korte levende låse er dette muligvis ikke en optimal løsning fordi at suspendere/genoptage en tråd er tid og ressourceforbrugende. Af denne grund kan du angive en spin ; før du suspenderer din tråd, vil OS bruge lidt tid på at gøre noget på den tråd, det kan give tid til at låse ejeren for at fuldføre jobbet og frigive det tredie. For at bruge dette skal du initialisere din kritiske sektion med InitializeCriticalSectionAndSpinCount i stedet for InitializeCriticalSection. [20] [21] [22]


Hvis du planlægger at bruge en kritisk sektion, kan du overveje at indhente alt, hvad der behøves i en klasse, du vil bruge variabler til at gøre alt, og din kode bliver mere klar (det er bare et eksempel, en sand implementering kan ikke være så naiv) :


class critical\_section
{
public:
 critical\_section()
 {
  // Here you may use InitializeCriticalSectionAndSpinCount
  InitializeCriticalSection(&\_cs);

  // You may not need this behavior, anyway here when you create
  // the object you acquire the lock too
  EnterCriticalSection(&\_cs);
 }

 ~critical\_section()
 {
   LeaveCriticalSection(&\_cs);
   DeleteCriticalSection(&cs);
 }

private:
 CRITICAL\_SECTION \_cs;
};


Under Unix/Linux (eller du vil være bærbar) skal du bruge funktionerne pthread.h til Mutex.


Lås er måske ikke nok



Dette er bare det første skridt, hvis din ansøgning logger meget , kan du sænke alle tråde, der venter på log (gør ikke noget forutgående, check og profil). Hvis det er tilfældet, skal du Opret en kø, din logText() -funktion skubber blot et nyt (forformateret) emne i køen og kalder PulseEvent til at signalere en begivenhed (oprettet med CreateEvent). Du får en anden tråd, der venter på den begivenhed med WaitForSingleObject din tråd vil vågne op, og det vil 'pope et emne fra køen. Du kan stadig have brug for en låsemekanisme (eller du kan skrive din egen ikke-blokerende samtidige kø for at undgå lås. Denne løsning er hurtigere, og det gør det ikke' t brug lås af nogen art, men jeg synes du bør kun gøre sådan noget, hvis du vil studere emnet (tråde), normalt for et simpelt logkrav, behøver du ikke at tilføje sådan kompleksitet. [23] [24] [25] [26]