ultimatepp/tutorial/CoreTutorial/CoWork.cpp
cxl d586b7ce09 CoreTutorial
git-svn-id: svn://ultimatepp.org/upp/trunk@11449 f0d560ea-af0d-0410-9eb7-867de7ffcac7
2017-11-12 07:10:36 +00:00

107 lines
3.6 KiB
C++

#include "Tutorial.h"
void CoWorkTutorial()
{
/// .`CoWork`
/// `CoWork` is intented to be use when thread are used to speedup code by distributing tasks
/// over multiple CPU cores. `CoWork` spans a single set of worker threads that exist for the
/// whole duration of program run. `CoWork` instances then manage assigning jobs to these
/// worker threads and waiting for the all work to finish.
/// Job units to `CoWork` are represented by `Function<void ()>` and thus can be written
/// inline as lambdas.
/// As an example, following code reads input file by lines, splits lines into words (this
/// is the parallelized work) and then adds resulting words to `Index`:
FileIn in(GetDataFile("test.txt")); // let us open some tutorial testing data
Index<String> w;
Mutex m; // need mutex to serialize access to w
CoWork co;
while(!in.IsEof()) {
String ln = in.GetLine();
co & [ln, &w, &m] {
Vector<String> h = Split(ln, [](int c) { return IsAlpha(c) ? 0 : c; });
Mutex::Lock __(m);
for(const auto& s : h)
w.FindAdd(s);
};
}
co.Finish();
DUMP(w);
/// Adding words to `w` requires `Mutex`. Alternative to this 'result gathering' `Mutex` is
/// `CoWork::FinLock`. The idea behind this is that CoWork requires an internal `Mutex` to
/// serialize access to common data, so why `FinLock` locks this internal mutex a bit
/// earlier, saving CPU cycles required to lock and unlock dedicated mutex. From API
/// contract perspective, you can consider `FinLock` to serialize code till the end of
/// worker job.
in.Seek(0);
while(!in.IsEof()) {
String ln = in.GetLine();
co & [ln, &w, &m] {
Vector<String> 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);
};
}
co.Finish();
DUMP(w);
/// Of course, the code performed after `FinLock` should not take long, otherwise there is
/// negative impact on all `CoWork` instances. In fact, from this perspective, above code is
/// probably past the threshold...
/// When exception is thrown in `CoWork`, it is propagated to the thread that calls
/// `Finish` and `CoWork` is canceled. If more than single job throws, one of exceptions is
/// selected randomly to be rethrown in Finish.
/// As `CoWork` destructor calls `Finish` too,
/// it is possible that it will be thrown by destructor, which is not exactly recommended
/// thing to do in C++, but is well defined and really the best option here:
in.Seek(0);
try {
while(!in.IsEof()) {
String ln = in.GetLine();
co & [ln, &w, &m] {
if(ln.GetCount() > 75)
throw "Input line was too long!";
Vector<String> 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);
};
}
co.Finish();
}
catch(const char *exception) {
DUMP(exception);
}
/// Sometimes there is a need for cancellation of the whole `CoWork`. `Cancel` method
/// cancels all scheduled jobs that have not been yet executed and sets `CoWork` to
/// canceled state, which can be checked in job routine using `CoWork::IsCanceled`:
for(int i = 0; i < 100; i++)
co & [] {
for(;;) {
if(CoWork::IsCanceled()) {
LOG("Job was canceled");
return;
}
Sleep(1);
}
};
Sleep(200); // Give CoWork a chance to start some jobs
co.Cancel();
/// Canceling CoWork is common in GUI applications.
}