File: joining.yo

package info (click to toggle)
c%2B%2B-annotations 13.02.02-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 13,576 kB
  • sloc: cpp: 25,297; makefile: 1,523; ansic: 165; sh: 126; perl: 90; fortran: 27
file content (79 lines) | stat: -rw-r--r-- 3,145 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
Once a thread starts and it isn't detached it must eventually join
its starting (parent) thread, or the program aborts. Usually, once a thread
has started the parent thread continues to do some work by itself:
        verb(    void childActions();
    void doSomeWork();

    void parent()
    {
        thread child(childActions);
        doSomeWork();
        child.join();
    })

However, maybe tt(doSomeWork) can't complete its work, and throws an
exception, to be caught outside of tt(parent). This, unfortunately, ends
tt(parent), and tt(child.join()) is missed. Consequently, the program aborts
because of a thread that hasn't been joined.

Clearly, all exceptions must be caught, tt(join) must be called, and the
exception must be rethrown. But tt(parent) cannot use a function try-block, as
the thread object is already out of scope once execution reaches the matching
tt(catch)-clause. So we get:
        verb(    void childActions();
    void doSomeWork();

    void parent()
    {
        thread child(childActions);
        try
        {
            doSomeWork();
            child.join();
        }
        catch (...)
        {
            child.join();
            throw;
        }
    })

This is ugly: suddenly the function's code is clobbered with a
tt(try-catch) clause, as well as some unwelcome code-duplication.

    This situation can be avoided using object based programming. Like, e.g.,
unique pointers, which use their destructors to encapsulate the destruction of
dynamically allocated memory, we can use a comparable technique to encapsulate
thread joining in an object's destructor.

By defining the tt(thread) object inside a class we're sure that by the time
the our object goes out of scope, even if the tt(childActions) function
throws an exception, the thread's tt(join) member is called. Here are the bare
essentials of our tt(JoinGuard) class, providing the join-guarantee (using
in-line member implementations for brevity):
        verbinclude(-ans4 examples/joinguard.h)
    itemization(
    it() At line 8 its only constructor starts: it receives a temporary
tt(thread) object, which is moved, in line 10, to tt(JoinGuard's d_thread)
data member. 

    it() When the tt(JoinGuard) object ceases to exist, its destructor (line
12) makes sure the thread is joined if it's still joinable (lines 14 and 15).
    )
    Here is an example how tt(JoinGuard) could be used:
        verbinclude(-ans4 examples/joinguard.cc)
    itemization(
    it() At line 4 tt(childActions) is declared. Its implementation (not
provided here) defines the child thread's actions.

    it() The tt(main) function (lines 17 through 25) provides the function 
try-block to catch the exception thrown by tt(parent);

    it() The tt(parent) function defines (line 13) an anonymous tt(JoinGuard),
receiving  an anonymous tt(thread) object. Anonymous objects are used, as the
parent function doesn't need to access them anymore.
    
    it() In line 14 tt(doSomeWork) is called, which throws an exception. This
ends tt(parent), but just before that tt(JoinGuard's) destructor makes sure
that the child-thread has been joined.
    )