Thread scheduling when releasing lock
up vote
2
down vote
favorite
My expectation when unlocking a mutex was that the scheduler checks for other threads currently trying to lock this mutex and then execute one of those waiting threads. I wrote a test program (see code below) with 2 threads both trying to acquire the same mutex in a loop and do some work (sleep for 1ms). The difference is that one thread t1
waits a short while between unlocking and trying to reacquire the mutex and the other thread t2
doesn’t. I was expecting that both threads acquire the mutex about the same number of times. However, on windows t1
often acquires the mutex only a single time and the other thread hundreds of times. On linux the behavior is different and both threads get work done with t2
roughly twice as many. Why does t1
on windows almost never acquire the mutex? How do I have to modify the code so that it does?
Example code:
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
using namespace std;
int main()
{
mutex m;
atomic<bool> go(false);
int t1Counter = 0, t2Counter = 0;
thread t1([&] {
while(!go);
while(go) {
this_thread::sleep_for(100us);
lock_guard<mutex> lg(m);
this_thread::sleep_for(1ms);
++t1Counter;
}
});
thread t2([&] {
while(!go);
while(go) {
lock_guard<mutex> lg(m);
this_thread::sleep_for(1ms);
++t2Counter;
}
});
go = true;
this_thread::sleep_for(1s);
go = false;
t1.join();
t2.join();
cout << t1Counter << " " << t2Counter << endl;
}
c++ multithreading mutex
add a comment |
up vote
2
down vote
favorite
My expectation when unlocking a mutex was that the scheduler checks for other threads currently trying to lock this mutex and then execute one of those waiting threads. I wrote a test program (see code below) with 2 threads both trying to acquire the same mutex in a loop and do some work (sleep for 1ms). The difference is that one thread t1
waits a short while between unlocking and trying to reacquire the mutex and the other thread t2
doesn’t. I was expecting that both threads acquire the mutex about the same number of times. However, on windows t1
often acquires the mutex only a single time and the other thread hundreds of times. On linux the behavior is different and both threads get work done with t2
roughly twice as many. Why does t1
on windows almost never acquire the mutex? How do I have to modify the code so that it does?
Example code:
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
using namespace std;
int main()
{
mutex m;
atomic<bool> go(false);
int t1Counter = 0, t2Counter = 0;
thread t1([&] {
while(!go);
while(go) {
this_thread::sleep_for(100us);
lock_guard<mutex> lg(m);
this_thread::sleep_for(1ms);
++t1Counter;
}
});
thread t2([&] {
while(!go);
while(go) {
lock_guard<mutex> lg(m);
this_thread::sleep_for(1ms);
++t2Counter;
}
});
go = true;
this_thread::sleep_for(1s);
go = false;
t1.join();
t2.join();
cout << t1Counter << " " << t2Counter << endl;
}
c++ multithreading mutex
The search terms you are looking for are "fairness" and "starvation". Windows mutex doesn't guarantee fairness. I'm not familiar with Linux; looks like its implementation is more fair.
– Igor Tandetnik
Nov 22 at 16:28
Callingstd::this_thread::yield
between unlock and relock might change the picture somewhat.
– Igor Tandetnik
Nov 22 at 16:34
@IgorTandetnik Doesn't really change much. Thread t1 runs about ~5 times instead of only once, but t2 still runs hundred of times.
– sebi707
Nov 22 at 17:26
1
Accidental synchronization problem, those sleep_for() calls are not innocent. You can tell something is amiss from the t2Counter value. If t2 runs (nearly) unimpeded then the value should be close to a 1000. My crystal ball says that you see something closer to 60 or 95. On Windows, a sleep can only complete at the clock tick interrupt, it ticks 64 times per second by default.
– Hans Passant
Nov 22 at 18:04
@HansPassant You are right. Replacing the sleep_for with some busy waiting sleep does give much more sensible results.
– sebi707
Nov 22 at 19:26
add a comment |
up vote
2
down vote
favorite
up vote
2
down vote
favorite
My expectation when unlocking a mutex was that the scheduler checks for other threads currently trying to lock this mutex and then execute one of those waiting threads. I wrote a test program (see code below) with 2 threads both trying to acquire the same mutex in a loop and do some work (sleep for 1ms). The difference is that one thread t1
waits a short while between unlocking and trying to reacquire the mutex and the other thread t2
doesn’t. I was expecting that both threads acquire the mutex about the same number of times. However, on windows t1
often acquires the mutex only a single time and the other thread hundreds of times. On linux the behavior is different and both threads get work done with t2
roughly twice as many. Why does t1
on windows almost never acquire the mutex? How do I have to modify the code so that it does?
Example code:
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
using namespace std;
int main()
{
mutex m;
atomic<bool> go(false);
int t1Counter = 0, t2Counter = 0;
thread t1([&] {
while(!go);
while(go) {
this_thread::sleep_for(100us);
lock_guard<mutex> lg(m);
this_thread::sleep_for(1ms);
++t1Counter;
}
});
thread t2([&] {
while(!go);
while(go) {
lock_guard<mutex> lg(m);
this_thread::sleep_for(1ms);
++t2Counter;
}
});
go = true;
this_thread::sleep_for(1s);
go = false;
t1.join();
t2.join();
cout << t1Counter << " " << t2Counter << endl;
}
c++ multithreading mutex
My expectation when unlocking a mutex was that the scheduler checks for other threads currently trying to lock this mutex and then execute one of those waiting threads. I wrote a test program (see code below) with 2 threads both trying to acquire the same mutex in a loop and do some work (sleep for 1ms). The difference is that one thread t1
waits a short while between unlocking and trying to reacquire the mutex and the other thread t2
doesn’t. I was expecting that both threads acquire the mutex about the same number of times. However, on windows t1
often acquires the mutex only a single time and the other thread hundreds of times. On linux the behavior is different and both threads get work done with t2
roughly twice as many. Why does t1
on windows almost never acquire the mutex? How do I have to modify the code so that it does?
Example code:
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
using namespace std;
int main()
{
mutex m;
atomic<bool> go(false);
int t1Counter = 0, t2Counter = 0;
thread t1([&] {
while(!go);
while(go) {
this_thread::sleep_for(100us);
lock_guard<mutex> lg(m);
this_thread::sleep_for(1ms);
++t1Counter;
}
});
thread t2([&] {
while(!go);
while(go) {
lock_guard<mutex> lg(m);
this_thread::sleep_for(1ms);
++t2Counter;
}
});
go = true;
this_thread::sleep_for(1s);
go = false;
t1.join();
t2.join();
cout << t1Counter << " " << t2Counter << endl;
}
c++ multithreading mutex
c++ multithreading mutex
edited Nov 22 at 17:41
mrflash818
6441018
6441018
asked Nov 22 at 16:14
sebi707
605
605
The search terms you are looking for are "fairness" and "starvation". Windows mutex doesn't guarantee fairness. I'm not familiar with Linux; looks like its implementation is more fair.
– Igor Tandetnik
Nov 22 at 16:28
Callingstd::this_thread::yield
between unlock and relock might change the picture somewhat.
– Igor Tandetnik
Nov 22 at 16:34
@IgorTandetnik Doesn't really change much. Thread t1 runs about ~5 times instead of only once, but t2 still runs hundred of times.
– sebi707
Nov 22 at 17:26
1
Accidental synchronization problem, those sleep_for() calls are not innocent. You can tell something is amiss from the t2Counter value. If t2 runs (nearly) unimpeded then the value should be close to a 1000. My crystal ball says that you see something closer to 60 or 95. On Windows, a sleep can only complete at the clock tick interrupt, it ticks 64 times per second by default.
– Hans Passant
Nov 22 at 18:04
@HansPassant You are right. Replacing the sleep_for with some busy waiting sleep does give much more sensible results.
– sebi707
Nov 22 at 19:26
add a comment |
The search terms you are looking for are "fairness" and "starvation". Windows mutex doesn't guarantee fairness. I'm not familiar with Linux; looks like its implementation is more fair.
– Igor Tandetnik
Nov 22 at 16:28
Callingstd::this_thread::yield
between unlock and relock might change the picture somewhat.
– Igor Tandetnik
Nov 22 at 16:34
@IgorTandetnik Doesn't really change much. Thread t1 runs about ~5 times instead of only once, but t2 still runs hundred of times.
– sebi707
Nov 22 at 17:26
1
Accidental synchronization problem, those sleep_for() calls are not innocent. You can tell something is amiss from the t2Counter value. If t2 runs (nearly) unimpeded then the value should be close to a 1000. My crystal ball says that you see something closer to 60 or 95. On Windows, a sleep can only complete at the clock tick interrupt, it ticks 64 times per second by default.
– Hans Passant
Nov 22 at 18:04
@HansPassant You are right. Replacing the sleep_for with some busy waiting sleep does give much more sensible results.
– sebi707
Nov 22 at 19:26
The search terms you are looking for are "fairness" and "starvation". Windows mutex doesn't guarantee fairness. I'm not familiar with Linux; looks like its implementation is more fair.
– Igor Tandetnik
Nov 22 at 16:28
The search terms you are looking for are "fairness" and "starvation". Windows mutex doesn't guarantee fairness. I'm not familiar with Linux; looks like its implementation is more fair.
– Igor Tandetnik
Nov 22 at 16:28
Calling
std::this_thread::yield
between unlock and relock might change the picture somewhat.– Igor Tandetnik
Nov 22 at 16:34
Calling
std::this_thread::yield
between unlock and relock might change the picture somewhat.– Igor Tandetnik
Nov 22 at 16:34
@IgorTandetnik Doesn't really change much. Thread t1 runs about ~5 times instead of only once, but t2 still runs hundred of times.
– sebi707
Nov 22 at 17:26
@IgorTandetnik Doesn't really change much. Thread t1 runs about ~5 times instead of only once, but t2 still runs hundred of times.
– sebi707
Nov 22 at 17:26
1
1
Accidental synchronization problem, those sleep_for() calls are not innocent. You can tell something is amiss from the t2Counter value. If t2 runs (nearly) unimpeded then the value should be close to a 1000. My crystal ball says that you see something closer to 60 or 95. On Windows, a sleep can only complete at the clock tick interrupt, it ticks 64 times per second by default.
– Hans Passant
Nov 22 at 18:04
Accidental synchronization problem, those sleep_for() calls are not innocent. You can tell something is amiss from the t2Counter value. If t2 runs (nearly) unimpeded then the value should be close to a 1000. My crystal ball says that you see something closer to 60 or 95. On Windows, a sleep can only complete at the clock tick interrupt, it ticks 64 times per second by default.
– Hans Passant
Nov 22 at 18:04
@HansPassant You are right. Replacing the sleep_for with some busy waiting sleep does give much more sensible results.
– sebi707
Nov 22 at 19:26
@HansPassant You are right. Replacing the sleep_for with some busy waiting sleep does give much more sensible results.
– sebi707
Nov 22 at 19:26
add a comment |
1 Answer
1
active
oldest
votes
up vote
3
down vote
accepted
On Windows, std::mutex
is implemented using a slim reader/writer lock. This lock implementation is not fair (meaning that it provides no guarantees about the order in which waiting threads will acquire the lock). Windows moved away from fair locks some time ago because Microsoft deemed lock convoys a more serious problem than thread starvation.
You can read more about slim reader/writer locks on Microsoft docs: Slim Reader/Writer (SRW) Locks
Joe Duffy has also blogged about the fairness versus lock convoy issue: Anti-convoy locks in Windows Server 2003 SP1 and Windows Vista
add a comment |
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
3
down vote
accepted
On Windows, std::mutex
is implemented using a slim reader/writer lock. This lock implementation is not fair (meaning that it provides no guarantees about the order in which waiting threads will acquire the lock). Windows moved away from fair locks some time ago because Microsoft deemed lock convoys a more serious problem than thread starvation.
You can read more about slim reader/writer locks on Microsoft docs: Slim Reader/Writer (SRW) Locks
Joe Duffy has also blogged about the fairness versus lock convoy issue: Anti-convoy locks in Windows Server 2003 SP1 and Windows Vista
add a comment |
up vote
3
down vote
accepted
On Windows, std::mutex
is implemented using a slim reader/writer lock. This lock implementation is not fair (meaning that it provides no guarantees about the order in which waiting threads will acquire the lock). Windows moved away from fair locks some time ago because Microsoft deemed lock convoys a more serious problem than thread starvation.
You can read more about slim reader/writer locks on Microsoft docs: Slim Reader/Writer (SRW) Locks
Joe Duffy has also blogged about the fairness versus lock convoy issue: Anti-convoy locks in Windows Server 2003 SP1 and Windows Vista
add a comment |
up vote
3
down vote
accepted
up vote
3
down vote
accepted
On Windows, std::mutex
is implemented using a slim reader/writer lock. This lock implementation is not fair (meaning that it provides no guarantees about the order in which waiting threads will acquire the lock). Windows moved away from fair locks some time ago because Microsoft deemed lock convoys a more serious problem than thread starvation.
You can read more about slim reader/writer locks on Microsoft docs: Slim Reader/Writer (SRW) Locks
Joe Duffy has also blogged about the fairness versus lock convoy issue: Anti-convoy locks in Windows Server 2003 SP1 and Windows Vista
On Windows, std::mutex
is implemented using a slim reader/writer lock. This lock implementation is not fair (meaning that it provides no guarantees about the order in which waiting threads will acquire the lock). Windows moved away from fair locks some time ago because Microsoft deemed lock convoys a more serious problem than thread starvation.
You can read more about slim reader/writer locks on Microsoft docs: Slim Reader/Writer (SRW) Locks
Joe Duffy has also blogged about the fairness versus lock convoy issue: Anti-convoy locks in Windows Server 2003 SP1 and Windows Vista
edited Nov 22 at 19:32
answered Nov 22 at 16:37
Peter Ruderman
10.1k2352
10.1k2352
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53434834%2fthread-scheduling-when-releasing-lock%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
The search terms you are looking for are "fairness" and "starvation". Windows mutex doesn't guarantee fairness. I'm not familiar with Linux; looks like its implementation is more fair.
– Igor Tandetnik
Nov 22 at 16:28
Calling
std::this_thread::yield
between unlock and relock might change the picture somewhat.– Igor Tandetnik
Nov 22 at 16:34
@IgorTandetnik Doesn't really change much. Thread t1 runs about ~5 times instead of only once, but t2 still runs hundred of times.
– sebi707
Nov 22 at 17:26
1
Accidental synchronization problem, those sleep_for() calls are not innocent. You can tell something is amiss from the t2Counter value. If t2 runs (nearly) unimpeded then the value should be close to a 1000. My crystal ball says that you see something closer to 60 or 95. On Windows, a sleep can only complete at the clock tick interrupt, it ticks 64 times per second by default.
– Hans Passant
Nov 22 at 18:04
@HansPassant You are right. Replacing the sleep_for with some busy waiting sleep does give much more sensible results.
– sebi707
Nov 22 at 19:26