Concurrency
without mutexes
What’s wrong
with mutex?
• Hard to write safe code	

• Mutexes are slow	

• Hard to parallelize
Hard to write
safe code
void first() {	
Guard<Mutex> guard(mutex1);	
...	
}	
!
void second() {	
Guard<Mutex> guard(mutex2);	
...	
}	
!
void third(...
void foo() {	
// must be locked	
}	
!
void bar() { Guard guard; foo(); }	
void baz() { Guard guard; foo(); }	
void qux() {...
Mutexes are expensive
• mutex lock/unlock takes about 1us under
contention	

• under high load it is almost always a
conte...
struct spinlock lock =	
SPINLOCK_INIT;	
!
void do_smth() {	
spinlock_lock(&lock);	
…	
spinlock_unlock(&lock);	
}
Spinlock ...
struct spinlock {	
int locked;	
};	
!
#define SPINLOCK_INIT { 0 };	
!
void spinlock_lock(struct spinlock* spinlock) {	
whi...
Code examples
github.com/stepancheg/no-mutex-c	

github.com/stepancheg/no-mutex
struct mutex lock =	
MUTEX_INIT;	
!
void do_smth() {	
mutex_lock(&lock);	
…	
mutex_unlock(&lock);	
}
Mutex API
struct mutex {	
int locked; // 1 if locked	
int count; // number of threads requesting a lock	
};	
!
#define MUTEX_INIT { ...
Numbers
lock cmpxchg 8ns
uncont. mutex lock/unlock 11ns
futex_wake 400ns
cont. mutex lock ~500ns
Hard to parallelize
• We want for some app to use 5 cores.
How many threads should we allocate?
There’s a solution!
Message passing/
Actor model
class BlockingQueue<T> {	
void Enqueue(T elem) { … }	
// block if empty	
Vector<T> DequeueAll() { … }	
}
BlockingQueue
class BlockingQueue<T> {	
Mutex mutex;	
CondVar condVar;	
Vector<T> elements;	
!
void Enqueue(T elem) {	
mutex.lock();	
el...
class BlockingQueue<T> {	
Mutex mutex;	
CondVar condVar;	
Vector<T> elements;	
!
Vector<T> DequeueAll() {	
mutex.lock();	
...
Simple message passing
with dedicated thread
// non-blocking queue	
// mutex+condvar	
BlockingQueue<Request> queue;	
!
void runProcessingThread() {	
for (;;) {	
Vector...
Actors
interface Runnable {	
void run();	
}	
!
interface ThreadPoolExecutor {	
void submit(Runnable);	
}
Executor
abstract class Actor {	
Actor(Executor executor);	
!
// is not called in parallel	
protected abstract void act();	
!
// ex...
class MyReqProcessor: Actor {	
MyReqProcessor(Executor exec) { super(exec); }	
!
NonBlockingQueue<Request> queue;	
!
overr...
enum ETaskState {	
WAITING,	
RUNNING,	
RUNNING_GOT_TASKS,	
};	
!
class Actor: Runnable {	
Atomic<TaskState> taskState;	
!
...
enum ETaskState {	
WAITING,	
RUNNING,	
RUNNING_GOT_TASKS,	
};	
!
class Actor: Runnable {	
Atomic<TaskState> taskState;	
!
...
Thanks
Upcoming SlideShare
Loading in …5
×

Степан Кольцов — Message passing: многопоточное программирование без мьютексов

1,444 views

Published on

Степан Кольцов, Яндекс.

Самые распространённые примитивы многопоточной синхронизации — это mutex и condvar. Эти примитивы плохо работают в случае contention (т. е. когда несколько потоков заходят в одну критическую область) — операции захвата и отпускания лока начинают работать на порядки медленнее и заметно нагружать CPU, при этом непредсказуемо деградирует производительность системы и появляются другие проблемы. Альтернативный подход к многопоточному программированию — это передача сообщений, или message passing. Степан расскажет о том, как устроены мьютексы, почему возникают такие проблемы и как эффективно реализовать подход message passing.

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
1,444
On SlideShare
0
From Embeds
0
Number of Embeds
860
Actions
Shares
0
Downloads
6
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Степан Кольцов — Message passing: многопоточное программирование без мьютексов

  1. 1. Concurrency without mutexes
  2. 2. What’s wrong with mutex? • Hard to write safe code • Mutexes are slow • Hard to parallelize
  3. 3. Hard to write safe code
  4. 4. void first() { Guard<Mutex> guard(mutex1); ... } ! void second() { Guard<Mutex> guard(mutex2); ... } ! void third() { Guard<Mutex> guard(mutex1); ... second(); // possible deadlock } ! void fourth() { Guard<Mutex> guard(mutex2); ... first(); // possible deadlock }
  5. 5. void foo() { // must be locked } ! void bar() { Guard guard; foo(); } void baz() { Guard guard; foo(); } void qux() { foo(); } void quux() { Guard guard; qux(); } void corge() { quux(); } // grault does not lock void grault() { qux(); }
  6. 6. Mutexes are expensive • mutex lock/unlock takes about 1us under contention • under high load it is almost always a contention • spinlocks are not worse
  7. 7. struct spinlock lock = SPINLOCK_INIT; ! void do_smth() { spinlock_lock(&lock); … spinlock_unlock(&lock); } Spinlock API
  8. 8. struct spinlock { int locked; }; ! #define SPINLOCK_INIT { 0 }; ! void spinlock_lock(struct spinlock* spinlock) { while (!atomic_compare_exchange( &spinlock->locked, 0, 1)) {} } ! void spinlock_unlock(struct spinlock* spinlock) { atomic_store(&spinlock->locked, 0, __ATOMIC_SEQ_CST); } Spinlock impl
  9. 9. Code examples github.com/stepancheg/no-mutex-c github.com/stepancheg/no-mutex
  10. 10. struct mutex lock = MUTEX_INIT; ! void do_smth() { mutex_lock(&lock); … mutex_unlock(&lock); } Mutex API
  11. 11. struct mutex { int locked; // 1 if locked int count; // number of threads requesting a lock }; ! #define MUTEX_INIT { 0, 0 }; ! void mutex_lock(struct mutex* mutex) { atomic_add_fetch(&mutex->count, 1); while (!atomic_compare_exchange(&mutex->locked, 0, 1)) { futex(&mutex->locked, FUTEX_WAIT, 1); } } ! void mutex_unlock(struct mutex* mutex) { int left = atomic_add_fetch(&mutex->count, -1); atomic_store(&mutex->locked, 0); if (left > 0) { futex(&mutex->locked, FUTEX_WAKE, 1); } } Mutex impl
  12. 12. Numbers lock cmpxchg 8ns uncont. mutex lock/unlock 11ns futex_wake 400ns cont. mutex lock ~500ns
  13. 13. Hard to parallelize • We want for some app to use 5 cores. How many threads should we allocate?
  14. 14. There’s a solution!
  15. 15. Message passing/ Actor model
  16. 16. class BlockingQueue<T> { void Enqueue(T elem) { … } // block if empty Vector<T> DequeueAll() { … } } BlockingQueue
  17. 17. class BlockingQueue<T> { Mutex mutex; CondVar condVar; Vector<T> elements; ! void Enqueue(T elem) { mutex.lock(); elements.push(elem); condVar.signal(); mutex.unlock(); } }
  18. 18. class BlockingQueue<T> { Mutex mutex; CondVar condVar; Vector<T> elements; ! Vector<T> DequeueAll() { mutex.lock(); while (elements.empty()) { condVar.wait(); } Vector<T> r = move elements; mutex.unlock(); return r; } }
  19. 19. Simple message passing with dedicated thread
  20. 20. // non-blocking queue // mutex+condvar BlockingQueue<Request> queue; ! void runProcessingThread() { for (;;) { Vector<Request> requests = Queue.dequeueAll(); // process requests } } ! void start(Request request) { queue.enqueue(request); }
  21. 21. Actors
  22. 22. interface Runnable { void run(); } ! interface ThreadPoolExecutor { void submit(Runnable); } Executor
  23. 23. abstract class Actor { Actor(Executor executor); ! // is not called in parallel protected abstract void act(); ! // execute act() // at least once void schedule() { … } } Actor
  24. 24. class MyReqProcessor: Actor { MyReqProcessor(Executor exec) { super(exec); } ! NonBlockingQueue<Request> queue; ! override void act() { // is not called in parallel Vector<Request> reqs = queue.dequeueAll(); // process reqs } ! // may be called from different threads void addWork(Request request) { queue.enqueue(request); schedule(); } } MyReqProcessor
  25. 25. enum ETaskState { WAITING, RUNNING, RUNNING_GOT_TASKS, }; ! class Actor: Runnable { Atomic<TaskState> taskState; ! void schedule() { if (AtomicSwap(RGT) == WAITING) { executor.submit(this); } } ! … } Actor.schedule
  26. 26. enum ETaskState { WAITING, RUNNING, RUNNING_GOT_TASKS, }; ! class Actor: Runnable { Atomic<TaskState> taskState; ! override void run() { for (;;) { while (CAS(RGT -> RUNNING)) { fetch tasks act } if (CAS(RUNNING -> WAITING) { return; } } } } Actor.run
  27. 27. Thanks

×