Informator i Kina, Dag 4

När jag anlände med morgonbussen, denna fjärde dag avseende kursen Advanced C++ and Threads Programming i Nanjing, så såg vi alla direkt att något var annorlunda och det i positiv mening. Luften var fylld av festliga stora kinesiska ballonger. Idag firade man 20-års jubileum av fabriken i Nanjing. Samtidigt var det premiär för en helt ny kontorsbyggnad alldeles bredvid.

Vi hade det festligt även i kurslokalen, för idag var det dags för trådprogrammering (eng. concurrent programming). Jag börjande med att göra en distinktion mellan concurrent respektive parallel. Det finns flera olika skäl till att man vill programmera med trådar.

Ett sådant är att man vill det ska så fort som möjligt, s.k. Performance Concurrency. Genom att exekvera på flera processorer (processing units = PU) samtidigt uppnår man dettta. Ett helt annat skäl är att man har ett applikationsproblem som består av flera olika samtidiga aktiviteter. Genom dela upp problemet i mindre del-problem och lösa dem var för sig, så löser man uppgiften både enklare och snabbare. Vi har då s.k. Design Concurrency.

Sedan gick jag igenom hur POSIX Threads C API:et ser ut. Följt av att visa hur man elegant kan kapsla in alla stökiga C funktioner i snygga C++ klasser i stället. Jag gick också igenom hur det ser ut i nya C++11 och hur man kan skapa en trådar m.h.a. lambdas (closures), funktionsanrops-operator, metod-pekare och vanliga funktioner. Jag visade också det stödet i C++11 för implicita trådar m.h.a. uppgifts-objekt (eng. task).

Här nedan ser du ett kodfragment för hur man kan använda en closure för att skapa en tråd:
int main(int numArgs, char* args[]) {
    int T = numArgs > 1 ? stoi(args[1]) : 5;
    int M = numArgs > 2 ? stoi(args[2]) : 100;
    vector<thread>  threads;   
    auto run = [](int id, int n){string msg = to_string(id) + "\n";
        for (int k=0; k<n; ++k) cout << msg << flush;
    };
    for (int k=0; k<T; ++k) threads.push_back( thread(run, k+1, M) );
    for (auto& t : threads) t.join();
}
Själva closuren är uttrycket run ovan, som skickas till konstruktorn för klassen thread. Sedan följer argumenten till denna.

Här nedan ser du ett kodfragment för hur man kan skapa en asynkron uppgift:
tuple<long,int> parallel(int n1, int n2, int n3) {
    auto start  = system_clock::now();
    auto r1     = async(launch::async, fibonacci, n1);
    auto r2     = async(launch::async, fibonacci, n2);
    auto r3     = async(launch::async, fibonacci, n3);
    long result = r1.get() + r2.get() + r3.get();
    auto end    = system_clock::now();
    int elapsed = duration_cast<milliseconds>(end - start).count();
    return make_tuple(result, elapsed);
}
Det intressanta här ovan är att funktionen async() returnerar ett s.k. future-objekt, som med tiden kommer att innehålla resultatet (i detta fall beräkning av ett Fibonacci nummer). På den rad som inleds med "long result" så adderar vi ihop de asynkront beräknade resultaten. Eftersom delresultaten beräknas parallellt (jag har åtta PU på min laptop med Intel Core i7), så är totaltiden mycket kortare jämfört med att beräkna dem efter varandra.

Sen var det dags att studera de två huvudproblemen inom området concurrent programming; critical sections och race conditions. Det illustrerade jag med två modellproblem, beskrev problemets karaktär och hur man löser det med standardmetoder iform av mutex-lås och condition-variabler. Jag gick också igenom hur elegant detta hanteras i C++11.

Kika på detta kodfragment nedan, som visar en brevlåda med en receive() metod:
template<typename T>
class Mailbox {
    queue<T>  q;
    mutex     m;
    condition_varible  c;
  public:
    T  receive() {
  unique_lock<mutex>  guard(m);
  c.wait(guard, []{return !q.empty()});
  T  msg = q.front(); q.pop();
  c.notify_all();
  return msg;
 }
. . .
};
Först så låser vi det kritiska kodavsnittet, sen kollar vi att brevlådan innehåller något. Om den är tom, väntar vi m.h.a. av condition-variablen c. Det eleganta här att använda ett lambda-utryck för att ange ett vänte-villkor. Här ovan betyder det att vänta tills brevlådan är icke-tom. Vi behöver inte låsa upp explicit, för det sköter destruktorn för klassen unique_lock, vilken anropas vid funktionsreturn.

Väl tillbaka i centrala Nanjing vid 18-tiden och aftonskymningen och under avnjutnadet av en (ja, just det) Frappucino, så började det tuta o ha sig alldeles förskräckligt i trafiken. Det visade sig att en reklamskylt fattat eld och att ett helt gäng med brandkårsbilar åkt dit för att släcka branden och också begränsa elden så att den inte spred sig till det varuhus, som skylten var upphängd på.

Tjing tjong från Nanjing /jens

Skicka en kommentar

Trevligt att du vill dela med dig av dina åsikter! Tänk på att hålla på "Netiketten" och använda vårdat språk.