Presented at code::dive (2016-11-16)
Video available at https://www.youtube.com/watch?v=yl25p91flLY
Ask programmers what comes to mind when you say concurrency and most are likely to say threads. Ask what comes to mind when you say threads and most are likely to say locks or synchronisation. These assumptions are so deeply held that they define and constrain how programmers are taught and think about concurrency: thread safety is almost synonymous with the avoidance of race conditions and the guarded protection of mutable state. But this is only one quadrant of four possibilities, a quadrant diagram partitioned by mutable–immutable along one axis and shared–unshared along another. Modern C++ supports programmers in all four quadrants, not just the synchronisation quadrant. From immutability to actors, this talk will take a look at patterns and practices that encourage thinking and coding outside the locked box.
12. Mutable
Immutable
Unshared Shared
Unshared mutable
data needs no
synchronisation
Unshared immutable
data needs no
synchronisation
Shared mutable
data needs
synchronisation
Shared immutable
data needs no
synchronisation
The Synchronisation Quadrant
17. This is the monstrosity in love,
lady, that the will is infinite,
and the execution confined;
that the desire is boundless,
and the act a slave to limit.
William Shakespeare
Troilus and Cressida
18. Multitasking is really just rapid
attention-switching.
And that'd be a useful skill, except it
takes us a second or two to engage
in a new situation we've graced with
our focus.
So, the sum total of attention is
actually decreased as we multitask.
Slicing your attention, in other
words, is less like slicing potatoes
than like slicing plums: you always
lose some juice.
David Weinberger
26. Command-line tools
can be 235x faster than
your Hadoop cluster
Adam Drake
http://aadrake.com/command-line-tools-can-be-235x-faster-than-your-hadoop-cluster.html
28. A large fraction of the flaws in software development
are due to programmers not fully understanding all
the possible states their code may execute in.
In a multithreaded environment, the lack of
understanding and the resulting problems are greatly
amplified, almost to the point of panic if you are
paying attention.
John Carmack
http://www.gamasutra.com/view/news/169296/Indepth_Functional_programming_in_C.php
29. Some people, when confronted with a
problem, think, "I know, I'll use threads,"
and then two they hav erpoblesms.
Ned Batchelder
https://twitter.com/#!/nedbat/status/194873829825327104
30. Shared memory is like a canvas where threads
collaborate in painting images, except that
they stand on the opposite sides of the canvas
and use guns rather than brushes.
The only way they can avoid killing each other
is if they shout "duck!" before opening fire.
Bartosz Milewski
"Functional Data Structures and Concurrency in C++"
http://bartoszmilewski.com/2013/12/10/functional-data-structures-and-concurrency-in-c/
31. There are several ways to
address the problem of
deadlock...
http://www.cs.rpi.edu/academics/courses/fall04/os/c10/index.html
32. Just ignore it and hope it
doesn't happen.
Ostrich Algorithm
http://www.cs.rpi.edu/academics/courses/fall04/os/c10/index.html
33. Detection and recovery —
if it happens, take action.
http://www.cs.rpi.edu/academics/courses/fall04/os/c10/index.html
34. Dynamic avoidance by careful
resource allocation — check to
see if a resource can be
granted, and if granting it will
cause deadlock, don't grant it.
http://www.cs.rpi.edu/academics/courses/fall04/os/c10/index.html
35. Prevention — change the rules.
http://www.cs.rpi.edu/academics/courses/fall04/os/c10/index.html
39. Habitability is the characteristic
of source code that enables
programmers, coders, bug-fixers,
and people coming to the code
later in its life to understand its
construction and intentions and
to change it comfortably and
confidently.
40. Habitability makes a place
livable, like home. And this is
what we want in software — that
developers feel at home, can
place their hands on any item
without having to think deeply
about where it is.
42. Simple Testing Can Prevent
Most Critical Failures
An Analysis of Production Failures in
Distributed Data-Intensive Systems
https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-yuan.pdf
43. We want our code
to be unit testable.
What is a unit test?
44. A test is not a unit test if:
▪ It talks to the database
▪ It communicates across the network
▪ It touches the file system
▪ It can't run at the same time as any of your other
unit tests
▪ You have to do special things to your environment
(such as editing config files) to run it.
Michael Feathers
http://www.artima.com/weblogs/viewpost.jsp?thread=126923
45. A unit test is a test of behaviour
whose success or failure is wholly
determined by the correctness of
the test and the correctness of the
unit under test.
Kevlin Henney
http://www.theregister.co.uk/2007/07/28/what_are_your_units/
53. Future
Immediately return a ‘virtual’ data object—
called a future—to the client when it invokes a
service. This future [...] only provides a value
to clients when the computation is complete.
57. In functional programming, programs are
executed by evaluating expressions, in
contrast with imperative programming where
programs are composed of statements which
change global state when executed.
Functional programming typically avoids
using mutable state.
https://wiki.haskell.org/Functional_programming
58. Many programming languages support
programming in both functional and
imperative style but the syntax and facilities
of a language are typically optimised for only
one of these styles, and social factors like
coding conventions and libraries often force
the programmer towards one of the styles.
https://wiki.haskell.org/Functional_programming
63. To keep our C++ API boundary simple, we [...] adopted
one-way data flow. The API consists of methods to
perform fire-and-forget mutations and methods to
compute view models required by specific views.
To keep the code understandable, we write functional
style code converting raw data objects into immutable
view models by default. As we identified performance
bottlenecks through profiling, we added caches to avoid
recomputing unchanged intermediate results.
The resulting functional code is easy to maintain,
without sacrificing performance.
https://code.facebook.com/posts/498597036962415/under-the-hood-building-moments/
64. Immutable Value
Define a value object type whose instances
are immutable. The internal state of a value
object is set at construction and no
subsequent modifications are allowed.
68. Copied Value
Define a value object type whose instances
are copyable. When a value is used in
communication with another thread, ensure
that the value is copied.
69. class date
{
public:
date(int year, int month, int day_in_month);
date(const date &);
date & operator=(const date &);
...
int get_year() const;
int get_month() const;
int get_day_in_month() const;
...
void set_year(int);
void set_month(int);
void set_day_in_month(int);
...
};
71. class date
{
public:
date(int year, int month, int day_in_month);
date(const date &);
date & operator=(const date &);
...
int get_year() const;
int get_month() const;
int get_day_in_month() const;
...
void set(int year, int month, int day_in_month);
...
};
today.set(2016, 11, 16);
72. class date
{
public:
date(int year, int month, int day_in_month);
date(const date &);
date & operator=(const date &);
...
int get_year() const;
int get_month() const;
int get_day_in_month() const;
...
};
today = date(2016, 11, 16);
73. class date
{
public:
date(int year, int month, int day_in_month);
date(const date &);
date & operator=(const date &);
...
int get_year() const;
int get_month() const;
int get_day_in_month() const;
...
};
today = date { 2016, 11, 16 };
74. class date
{
public:
date(int year, int month, int day_in_month);
date(const date &);
date & operator=(const date &);
...
int get_year() const;
int get_month() const;
int get_day_in_month() const;
...
};
today = { 2016, 11, 16 };
76. class date
{
public:
date(int year, int month, int day_in_month);
date(const date &);
date & operator=(const date &);
...
int get_year() const;
int get_month() const;
int get_day_in_month() const;
...
};
77. class date
{
public:
date(int year, int month, int day_in_month);
date(const date &);
date & operator=(const date &);
...
int year() const;
int month() const;
int day_in_month() const;
...
};
78. class date
{
public:
date(int year, int month, int day_in_month);
date(const date &);
date & operator=(const date &);
...
int year() const;
int month() const;
int day_in_month() const;
date with_year(int) const;
...
};
79. Builder
Introduce a builder that provides separate
methods for constructing and disposing of
each different part of a complex object, or
for combining cumulative changes in the
construction of whole objects.
81. class date
{
public:
...
int year() const;
int month() const;
int day_in_month() const;
date with_year(int new_year) const
{
return { new_year, month(), day_in_month() };
}
...
};
82. class date
{
public:
...
int year() const;
int month() const;
int day_in_month() const;
date with_year(int new_year) const
{
return
new_year == year
? *this
: date { new_year, month(), day_in_month() };
}
...
};
83. class date
{
public:
...
int year() const;
int month() const;
int day_in_month() const;
date with_year(int) const;
date with_month(int) const;
date with_day_in_month(int) const
...
};
86. Referential transparency is a very
desirable property: it implies that
functions consistently yield the same
results given the same input,
irrespective of where and when they are
invoked. That is, function evaluation
depends less—ideally, not at all—on the
side effects of mutable state.
Edward Garson
"Apply Functional Programming Principles"
90. In computing, a persistent data structure is a data structure
that always preserves the previous version of itself when it is
modified. Such data structures are effectively immutable, as
their operations do not (visibly) update the structure in-place,
but instead always yield a new updated structure.
http://en.wikipedia.org/wiki/Persistent_data_structure
(A persistent data structure is not a data structure committed
to persistent storage, such as a disk; this is a different and
unrelated sense of the word "persistent.")
100. I still have a deep fondness for the
Lisp model. It is simple, elegant, and
something with which all developers
should have an infatuation at least
once in their programming life.
Kevlin Henney
"A Fair Share (Part I)", CUJ C++ Experts Forum, October 2002
102. template<typename ValueType>
class list
{
public:
class iterator;
...
std::size_t size() const;
iterator begin() const;
iterator end() const;
const ValueType & front() const;
list popped_front() const;
list pushed_front() const;
...
private:
struct link
{
link(const ValueType & value, link * next);
ValueType value;
link * next;
};
link * head;
std::size_t length;
};
103. Hamlet: Yea, from the table of
my memory I'll wipe away all
trivial fond records.
William Shakespeare
The Tragedy of Hamlet
[Act I, Scene 5]
104. Garbage collection [...] is optional
in C++; that is, a garbage collector
is not a compulsory part of an
implementation.
Bjarne Stroustrup
http://stroustrup.com/C++11FAQ.html
106. Ophelia: 'Tis in my memory
locked, and you yourself shall
keep the key of it.
William Shakespeare
The Tragedy of Hamlet
[Act I, Scene 3]
107. A use-counted class is more
complicated than a non-use-
counted equivalent, and all of
this horsing around with use
counts takes a significant
amount of processing time.
Robert Murray
C++ Strategies and Tactics
111. Instead of using threads and shared
memory as our programming model, we
can use processes and message passing.
Process here just means a protected
independent state with executing code,
not necessarily an operating system
process.
Russel Winder
"Message Passing Leads to Better Scalability in Parallel Systems"
112. Languages such as Erlang (and occam
before it) have shown that processes are a
very successful mechanism for
programming concurrent and parallel
systems. Such systems do not have all
the synchronization stresses that shared-
memory, multithreaded systems have.
Russel Winder
"Message Passing Leads to Better Scalability in Parallel Systems"
133. class receiving
{
public:
receiving(channel * that, ValueType & to_receive)
: that(that), to_receive(to_receive)
{
}
receiving(receiving && other)
: that(other.that), to_receive(other.to_receive)
{
other.that = nullptr;
}
operator bool()
{
auto from = that;
that = nullptr;
return from && from->try_receive(to_receive);
}
~receiving()
{
if (that)
that->receive(to_receive);
}
private:
channel * that;
ValueType & to_receive;
};
134. std::string fizzbuzz(int n)
{
if (n < 1 || n > 100)
throw std::domain_error(
"fizzbuzz(n) is defined for n in [1..100]")
return
n % 15 == 0 ? "FizzBuzz" :
n % 3 == 0 ? "Fizz" :
n % 5 == 0 ? "Buzz" :
std::to_string(n);
}
135. void fizzbuzzer(channel<int> & in, channel<std::any> & out)
{
for (;;)
{
try
{
int n;
in >> n;
out << fizzbuzz(n);
}
catch (...)
{
out << std::current_exception();
}
}
}
136. int main()
{
channel<int> out;
channel<std::any> back;
std::thread fizzbuzzing(fizzbuzzer, out, back)
for (int n = 1; n <= 100; ++n)
{
out << n;
std::any result;
back >> result;
if (result.type() == typeid(std::string))
std::cout << std::any_cast<std::string>(result) << "n";
}
...
}
138. int main()
{
channel<int> out;
channel<std::any> back(
[](const any & received)
{
if (received.type() == typeid(std::exception_ptr))
std::rethrow_exception(any_cast<std::exception_ptr>(received));
});
std::thread fizzbuzzing(fizzbuzzer, out, back)
...
}
139. int main()
{
...
for (int n = 1; n <= 100; ++n)
{
try
{
out << n;
std::any result;
back >> result;
std::cout << std::any_cast<std::string>(result) << "n";
}
catch (std::domain_error & caught)
{
std::cout << caught.what() << "n";
}
}
...
}
140. Pipes and Filters
Divide the application's task into
several self-contained data
processing steps and connect these
steps to a data processing pipeline
via intermediate data buffers.
141. Simple filters that can be arbitrarily
chained are more easily re-used, and
more robust, than almost any other
kind of code.
Brandon Rhodes
http://rhodesmill.org/brandon/slides/2012-11-pyconca/
142.
143. Multithreading is just one
damn thing after, before, or
simultaneous with another.
Andrei Alexandrescu
145. No matter what language you work in,
programming in a functional style provides
benefits.
You should do it whenever it is convenient,
and you should think hard about the
decision when it isn't convenient.
John Carmack
http://www.gamasutra.com/view/news/169296/Indepth_Functional_programming_in_C.php
146. Programming in a functional style makes
the state presented to your code explicit,
which makes it much easier to reason
about, and, in a completely pure system,
makes thread race conditions impossible.
John Carmack
http://www.gamasutra.com/view/news/169296/Indepth_Functional_programming_in_C.php