Slides for C++ Russia 2018
I'm presenting my `cpp_stm_free` library: composable monadic STM for C++ on Free monads for lock-free concurrent programming.
3. struct Presentation
{
Introduction1 Functional programming in C++
Introduction2 Parallel and concurrent programming in C++
Theory Software Transactional Memory
Practice Dining philosophers task
Implementation What’s inside?
Limitations What you should be aware of
};
4. Introduction 1. Functional programming in C++
● Lambdas, closures, functions (almost pure)
● std::function<>
● Combining of pure functions
● Immutability, POD-types
● Templates - pure functional language
● for_each(), recursion
● Type inference
● Pain
6. Theory. Software Transactional Memory
● Concurrent data model
● Independent parallel computations (“transactions”) over a different parts of the model
● Atomic commit of changed at the end of the computations:
○ Successful, if there are no conflicts of competing changes
○ Failed, if there are conflicts
■ Changes will be rollbacked, transaction will be restarted
● It’s also nice to have:
○ Absence of explicit synchronizations
○ Composable transactions
○ Capability to retry transactions
○ Adequate behavior and safety
7. ● New blocks:
○ atomic_noexcept
○ atomic_cancel
○ atomic_commit
○ synchronized
● Transaction-safe functions
○ transaction_safe
● Pros:
○ Transaction-safe functions
● Cons:
○ Too chaotic
○ Too far from release
○ Bad way to do STM
● Requirements:
○ GCC 6.1
Technical Specification ISO/IEC TS 19841:2015
int f()
{
static int i = 0;
atomic_noexcept
{
++i;
return i;
}
}
8. Wyatt-STM
WSTM::Atomically ([&](WSTM::WAtomic& at)
{
++repeat1;
v1.Set (v1.Get (at) + v2.Get (at), at);
barrier.wait ();
});
● Pros:
○ Composable transactions
○ Retry operation
○ In production
● Cons:
○ Too imperative: easy to hack
○ Has some unwanted weakness
○ Boost
● Requirements:
○ Visual Studio 2015, GCC 4.9, Clang 3.4
○ Boost 1.57
● https://github.com/bretthall/Wyatt-STM
● CppCon 2015: Brett Hall “Transactional Memory in Practice"
9. My library: cpp_stm_free
STML<ForkPair>
readForks(const TForkPair& forks)
{
STML<Fork> lm = readTVar(forks.left);
STML<Fork> rm = readTVar(forks.right);
return both(lm, rm, mkForkPair);
};
● Pros:
○ Purely functional: hard to hack
○ Composable monadic transactions
○ Retry operation
○ Adequate behavior
○ Many ways to improve and optimize
● Cons:
○ Highly experimental, Proof of Concept
○ Mind blowing, probably
○ Performance - ??? (will measure later)
● Requirements:
○ GCC 7.2, C++17
● https://github.com/graninas/cpp_stm_free
17. STML<TVar<int>> t1 = newTVar(10);
STML<int> t2 = bind(t1, readTVar); // short form without lambda
Context context; // All TVars live in the context
int result1 = atomically(context, t2); // result1 == 10
int result2 = atomically(context, t2); // result2 == 10, also*
// * but can be different on case of many competing threads trying to change this TVar.
Atomic evaluation
18. STML<TVar<int>> t1 = newTVar(10);
STML<int> t2 = bind(t1, readTVar); // short form without lambda
STML<int> t3 = bind(t2, [](int i) {
if (i == 10) return retry(); // retry if i == 10
return pure(i + 1); // return incremented i otherwise
});
Context context; // All TVars live in the context
int result1 = atomically(context, t2); // result1 == 10
int result2 = atomically(context, t2); // result2 == 10, also*
int result3 = atomically(context, t3); // tries hard to retry the transaction
// * but can be different on case of many competing threads trying to change this TVar.
Retrying
19. template <typename A, typename B, typename Ret>
STML<Ret> both(const STML<A>& ma,
const STML<B>& mb,
const function<Ret(A, B)>& f);
template <typename A, typename B>
STML<B> sequence(const STML<A>& ma,
const STML<B>& mb);
template <typename B>
STML<B> ifThenElse(const STML<bool>& m,
const STML<B>& mOnTrue,
const STML<B>& mOnFalse);
Useful combinators
// Do both computations separately,
// combine the results with function,
// return combined result
// Do the first computation,
// drop its result,
// do the second computation,
// return its result
// Do the conditional computation `m`,
// if true, do `mOnTrue`
// otherwise, do `mOnFalse`
37. TVar Interface & Implementation
using TVarId = utils::GUID;
template <typename T>
struct TVar
{
std::string name;
TVarId id;
};
struct TVarHandle
{
GUID ustamp;
std::any data;
};
using TVars = std::map<TVarId, TVarHandle>;
class Context
{
std::map<TVarId, TVarHandle> _tvars;
std::mutex _lock;
public:
bool tryCommit(const GUID& ustamp, const TVars& stagedTvars);
};
38. Limitations. What you should be aware of
● Side effects in transactions are prohibited
● Proper binding of transactions
● Proper data model (fine grained vs coarse grained)
● Occasional infinite retry
● Dangerous closures (be careful passing things by reference)
● Starvation
● Deadlock / livelock
● Implementation isn’t optimal by performance (yet)