diff --git a/tutorial/CoreTutorial/CoPartition.cpp b/tutorial/CoreTutorial/CoPartition.cpp new file mode 100644 index 000000000..7959b555b --- /dev/null +++ b/tutorial/CoreTutorial/CoPartition.cpp @@ -0,0 +1,56 @@ +#include "Tutorial.h" + +void CoPartitionTutorial() +{ + /// .CoPartition + + Vector data; + for(int i = 0; i < 10000; i++) + data.Add(i); + + int sum = 0; + + CoWork co; + for(int i = 0; i < data.GetCount(); i++) + co & [i, &sum, &data] { CoWork::FinLock(); sum += data[i]; }; + co.Finish(); + DUMP(sum); + + /// + + sum = 0; + CoPartition(data, [&sum](const auto& subrange) { + int partial_sum = 0; + for(const auto& x : subrange) + partial_sum += x; + CoWork::FinLock(); // available as CoPartition uses CoWork + sum += partial_sum; + }); + DUMP(sum); + + /// + + sum = 0; + CoPartition(data.begin(), data.end(), [&sum] (auto l, auto h) { + int partial_sum = 0; + while(l != h) + partial_sum += *l++; + CoWork::FinLock(); // available as CoPartition uses CoWork + sum += partial_sum; + }); + DUMP(sum); + + /// + + sum = 0; + CoPartition(0, data.GetCount(), [&sum, &data] (int l, int h) { + int partial_sum = 0; + while(l != h) + partial_sum += data[l++]; + CoWork::FinLock(); // available as CoPartition uses CoWork + sum += partial_sum; + }); + DUMP(sum); + + /// +} \ No newline at end of file diff --git a/tutorial/CoreTutorial/CoWork.cpp b/tutorial/CoreTutorial/CoWork.cpp new file mode 100644 index 000000000..b222f6f40 --- /dev/null +++ b/tutorial/CoreTutorial/CoWork.cpp @@ -0,0 +1,43 @@ +#include "Tutorial.h" + +void CoWorkTutorial() +{ + /// .`CoWork` + + FileIn in(GetDataFile("test.txt")); // let us open some tutorial testing data + + Index w; + Mutex m; // need mutex to serialize access to w + + CoWork co; + while(!in.IsEof()) { + String ln = in.GetLine(); + co & [ln, &w, &m] { + for(const auto& s : Split(ln, [](int c) { return IsAlpha(c) ? 0 : c; })) { + Mutex::Lock __(m); + w.FindAdd(s); + } + }; + } + co.Finish(); + + DUMP(w); + + /// + + + in.Seek(0); + while(!in.IsEof()) { + String ln = in.GetLine(); + co & [ln, &w, &m] { + Vector h = Split(ln, [](int c) { return IsAlpha(c) ? 0 : c; }); + CoWork::FinLock(); // replaces the mutex, locked till the end of CoWork job + for(const auto& s : h) + w.FindAdd(s); + }; + } + + DUMP(w); + + /// +} diff --git a/tutorial/CoreTutorial/ConditionVariable.cpp b/tutorial/CoreTutorial/ConditionVariable.cpp new file mode 100644 index 000000000..15eb39d2e --- /dev/null +++ b/tutorial/CoreTutorial/ConditionVariable.cpp @@ -0,0 +1,50 @@ +#include "Tutorial.h" + +void ConditionVariableTutorial() +{ + /// .`ConditionVariable` + + /// `ConditionVariable` in general is a synchronization primitive used to block/awaken the + /// thread. `ConditionVariable` is associated with `Mutex` used to protect some data; in + /// the thread that is to be blocked, `Mutex` has to locked; call to `Wait` atomically + /// unlocks the `Mutex` and puts the thread to waiting. Another thread then can resume the + /// thread by calling `Signal`, which also causes `Mutex` to lock again. Multiple threads + /// can be waiting on single `ConditionVariable`; `Signal` resumes single waiting thread, + /// `Brodcast` resumes all waitng threads. + + bool stop = false; + BiVector data; + Mutex m; + ConditionVariable cv; + + Thread t; + t.Run([&stop, &data, &m, &cv] { + Mutex::Lock __(m); + for(;;) { + while(data.GetCount()) { + int q = data.PopTail(); + LOG("Data received: " << q); + } + if(stop) + break; + cv.Wait(m); + } + }); + + for(int i = 0; i < 10; i++) { + { + Mutex::Lock __(m); + data.AddHead(i); + } + cv.Signal(); + Sleep(1); + } + stop = true; + cv.Signal(); + t.Wait(); + + /// Important note: rarely thread can be resumed from `Wait` even if no other called + /// `Signal`. This is not a bud, but ^https://en.wikipedia.org/wiki/Spurious_wakeup:design + /// decision for performance reason^. In practice it only means that situation has to be + /// (re)checked after resume. +} \ No newline at end of file diff --git a/tutorial/CoreTutorial/CoreTutorial.upp b/tutorial/CoreTutorial/CoreTutorial.upp index 7c4215714..ab1b0a874 100644 --- a/tutorial/CoreTutorial/CoreTutorial.upp +++ b/tutorial/CoreTutorial/CoreTutorial.upp @@ -41,6 +41,11 @@ file Function.cpp, CapturingContainers.cpp, Thread.cpp, + Mutex.cpp, + ConditionVariable.cpp, + test.txt, + CoWork.cpp, + CoPartition.cpp, tutorial2.cpp, help.qtf; diff --git a/tutorial/CoreTutorial/Mutex.cpp b/tutorial/CoreTutorial/Mutex.cpp new file mode 100644 index 000000000..f35129613 --- /dev/null +++ b/tutorial/CoreTutorial/Mutex.cpp @@ -0,0 +1,51 @@ +#include "Tutorial.h" + +void MutexTutorial() +{ + /// .`Mutex` + + /// Mutex ("mutual exclusion") is a well known concept in multithreaded programming: When + /// multiple threads write and read the same data, the access has to be serialized using + /// Mutex. Following invalid code demonstrates why: + + Thread t; + + int sum = 0; + t.Run([&sum] { + for(int i = 0; i < 1000000; i++) + sum++; + }); + + for(int i = 0; i < 1000000; i++) + sum++; + + t.Wait(); + DUMP(sum); + + /// While the expected value is 2000000, produced value is different. The problem is that + /// both thread read / modify / write `sum` value without any locking. Using `Mutex` locks + /// the `sum` and thus serializes access to it - read / modify / write sequence is now + /// exclusive for the thread that has `Mutex` locked, this fixing the issue. `Mutex` can be + /// locked / unlocked with `Enter` / `Leave` methods. Alternatively, `Mutex::Lock` helper + /// class locks `Mutex` in constructor and unlocks it in destructor: + + Mutex m; + sum = 0; + t.Run([&sum, &m] { + for(int i = 0; i < 1000000; i++) { + m.Enter(); + sum++; + m.Leave(); + } + }); + + for(int i = 0; i < 1000000; i++) { + Mutex::Lock __(m); // Lock m till the end of scope + sum++; + } + + t.Wait(); + DUMP(sum); + + /// +} \ No newline at end of file diff --git a/tutorial/CoreTutorial/Null.cpp b/tutorial/CoreTutorial/Null.cpp index 76ae8723a..0729e3cd6 100644 --- a/tutorial/CoreTutorial/Null.cpp +++ b/tutorial/CoreTutorial/Null.cpp @@ -20,11 +20,11 @@ void NullTutorial() DUMP(d); DUMP(e > d); - /// Null is the only instance of `Nuller` type. Assigning `Null` to + /// `Null` is the only instance of `Nuller` type. Assigning `Null` to /// primitive types is achieved by cast operators of `Nuller`, other types can do it using /// constructor from `Nuller`. - /// As a special case, if Value contains Null, it is convertible to any value type that can contain Null: + /// As a special case, if `Value` contains `Null`, it is convertible to any value type that can contain `Null`: Value v = x; // x is int e = v; // e is Date, but v is Null, so Null is assigned to e diff --git a/tutorial/CoreTutorial/Thread.cpp b/tutorial/CoreTutorial/Thread.cpp index 83ec4b944..6ed2de352 100644 --- a/tutorial/CoreTutorial/Thread.cpp +++ b/tutorial/CoreTutorial/Thread.cpp @@ -2,7 +2,21 @@ void ThreadTutorial() { - /// .Thread + /// .`Thread` + + /// Since C++11, there is now a reasonable support for threads in standard library. + /// There are however reasons to use U++ threads instead. One of them is that U++ high + /// performance memory allocator needs a cleanup call at the the thread exit, which is + /// naturally implemented into `Upp::Thread`. Second 'hard' reason is that Microsoft + /// compiler is using Win32 API function for condition variable that are not available for + /// Windows XP, while U++ has alternative implementation for Windows XP, thus making + /// executable compatible with it. + + /// Then of course we believe U++ multithreading / parallel programming support is easier + /// to use and leads to higher performance... + + /// `Thread` class can start the thread and allows launching thread to `Wait` for its + /// completion: Thread t; t.Run([] { @@ -20,5 +34,19 @@ void ThreadTutorial() t.Wait(); LOG("Wait for thread done"); - /// -} \ No newline at end of file + /// `Thread` destructor calls `Detach` method with 'disconnects' `Thread` from the thread. + /// Thread continues running. + + /// `Thread::Start` static method launches a thread without possibility to wait for its + /// completion; if you need to wait, you have to use some other method: + + bool x = false; + + Thread::Start([&x] { LOG("In the Started thread"); x = true; }); + + LOG("About to wait for thread to finish"); + while(!x) { Sleep(1); } // Do not do this in real code! + LOG("Wait for thread done"); + + /// (method used here is horrible, but should demonstrate the point). +} diff --git a/tutorial/CoreTutorial/test.txt b/tutorial/CoreTutorial/test.txt new file mode 100644 index 000000000..76602ea22 --- /dev/null +++ b/tutorial/CoreTutorial/test.txt @@ -0,0 +1,6 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod +tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, +quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo +consequat. Duis aute irure dolor in reprehenderit in voluptate velit +esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat +non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. \ No newline at end of file diff --git a/tutorial/CoreTutorial/tutorial2.cpp b/tutorial/CoreTutorial/tutorial2.cpp index 9dae24c09..e074f5196 100644 --- a/tutorial/CoreTutorial/tutorial2.cpp +++ b/tutorial/CoreTutorial/tutorial2.cpp @@ -41,6 +41,10 @@ GUI_APP_MAIN DO(ValueArrayMap); DO(ThreadTutorial); + DO(MutexTutorial); + DO(ConditionVariableTutorial); + DO(CoWorkTutorial); + DO(CoPartitionTutorial); MakeTutorial(); }