Test environment: Ubuntu 18.04.3 LTS g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0.
Can std::mutex be reentrant? Why does the following test code 1 pass?
code1:
#include <iostream>
#include <mutex>
std::mutex g_mtx4val;
int g_val = 5;
void test() {
std::lock_guard<std::mutex> lck(g_mtx4val);
std::cout << "g_val=" << g_val << std::endl;
if (g_val > 0) {
--g_val;
test();
}
}
int main() {
test();
std::cout << "done ...." << std::endl;
return 0;
}
peanut#peanut:~/demo$ g++ test.cpp
peanut#peanut:~/demo$ ./a.out
g_val=5
g_val=4
g_val=3
g_val=2
g_val=1
g_val=0
done ...
code2:
// Same code 1
int main() {
std::thread t1(test);
t1.join();
std::cout << "done ...." << std::endl;
return 0;
}
peanut#peanut:~/demo$ g++ test2.cpp -lpthread
peanut#peanut:~/demo$ ./a.out
g_val=5
^C
peanut#peanut:~/demo$
code2 has a deadlock.
Why code1 can pass the test?
From the documentation page:
mutex offers exclusive, non-recursive ownership semantics
So the answer to the question in the title is no.
Can std::mutex be reentrant?
No, but if you want a recursive mutex, the std::recursive_mutex class provides that functionality.
Why does the following test code 1 pass?
What behavior were you expecting to see? The std::mutex documentation page simply says:
A calling thread must not own the mutex prior to calling lock or try_lock.
... it doesn't say what will happen if the calling thread breaks the above rule; which means that a program that breaks the rule may "appear to work", but even so is still incorrect and buggy.
Related
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <thread>
std::mutex mtx;
void func2() {
mtx.lock();
std::cout << "here is 2" << std::endl;
mtx.unlock();
}
void func1() {
mtx.lock();
std::cout << "here is 1" << std::endl;
func2();
mtx.unlock();
}
int main() {
func1();
}
but if I modify the main func as follows, it cause dead lock
int main() {
std::thread t1(func1);
t1.join();
}
I complied both two by "g++ test.cpp -std=c++11 -lpthread"
Calling lock in the same thread twice (without unlocking the mutex) is undefined. From cppreference:
If lock is called by a thread that already owns the mutex, the behavior is undefined: for example, the program may deadlock.
It may deadlock. Or it may not. The behavior is undefined.
Note that std::recursive_mutex can be locked multiple times (though only up to some unspecified limit). However, code that needs a recursive mutex is more complicated. In your example it would be easier to remove the locking from func1, because it is only called when the mutex is already locked. In general it isn't that simple.
According to the cppreference.com the std::call_once should work correctly with functions which might throw an exception. Having said that, testing the example represented there it results in an infinite waiting instead of joining all threads.
the example from the page above:
#include <iostream>
#include <thread>
#include <mutex>
std::once_flag flag1, flag2;
void simple_do_once()
{
std::call_once(flag1, [](){ std::cout << "Simple example: called once\n"; });
}
void may_throw_function(bool do_throw)
{
if (do_throw) {
std::cout << "throw: call_once will retry\n"; // this may appear more than once
throw std::exception();
}
std::cout << "Didn't throw, call_once will not attempt again\n"; // guaranteed once
}
void do_once(bool do_throw)
{
try {
std::call_once(flag2, may_throw_function, do_throw);
}
catch (...) {
}
}
int main()
{
std::thread st1(simple_do_once);
std::thread st2(simple_do_once);
std::thread st3(simple_do_once);
std::thread st4(simple_do_once);
st1.join();
st2.join();
st3.join();
st4.join();
std::thread t1(do_once, true);
std::thread t2(do_once, true);
std::thread t3(do_once, false);
std::thread t4(do_once, true);
t1.join();
t2.join();
t3.join();
t4.join();
}
In my case it results in the infinite waiting after the following output:
Simple example: called once
throw: call_once will retry
for all the following versions the result is the same.
clang++
clang version 10.0.0-4ubuntu1
Target: x86_64-pc-linux-gnu
Thread model: posix
g++
g++ (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
g++-10 (Ubuntu 10.2.0-5ubuntu1~20.04) 10.2.0
What does that mean?
Take the following code:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
using namespace std;
int main() {
mutex m;
condition_variable c;
bool fired = false;
int i = 0;
// This thread counts the times the condition_variable woke up.
// If no spurious wakeups occur it should be close to 5.
thread t([&]() {
unique_lock<mutex> l(m);
while (!fired) {
c.wait_for(l, chrono::milliseconds(100));
++i;
}
});
// Here we wait for 500ms, then signal the other thread to stop
this_thread::sleep_for(chrono::milliseconds(500));
{
unique_lock<mutex> l(m);
fired = true;
c.notify_all();
cout << i << endl;
}
t.join();
}
Now, when I build this using clang++ -std=c++11 -pthread foo.cpp everything is fine, it outputs 4 on my machine. When I build it with g++ -std=c++11 -pthread foo.cpp however I get something very large every time, e.g. 81513. I realize the number of spurious wakeups is undefined, but I was surprised to see it so high.
Additional information: When I replace the wait_for by a simple wait both clang and g++ output 0.
Is this a bug / feature in g++? Why is it even different from clang? Can I get it to behave more reasonably?
Also: gcc version 4.7.3 (Debian 4.7.3-4).
I managed to get g++-4.8 running, and the problem is gone. Very weird, seems like a bug in g++-4.7.3, although I wasn't able to reproduce it on another machine.
I was fiddling around with some code in c++ that for some reason didn't want to work and i narrowed it down to this case:
#include <thread>
#include <atomic>
#include <chrono>
#include <mutex>
#include <iostream>
using namespace std;
void test()
{
timed_mutex m;
m.lock();
std::cout << "Can i have the lock? " << m.try_lock() << std::endl;
std::cout << "in test(), should block for 10 seconds" << std::endl;
bool got_lock = m.try_lock_for(std::chrono::seconds(10));
std::cout << "Now i've blocked, got the lock: " << got_lock << std::endl;
m.unlock();
}
int main()
{
thread t = thread(&test);
t.join();
return EXIT_SUCCESS;
}
The problem is that test() doesn't block at all, even though the try_lock returns false. Is there something i have overlooked or is this a bug in gcc or where should i go next to find out what's wrong? Thankful for any advice and help!
I compiled this little program like so: g++ -pthread -std=c++11 threads.cpp -o threads
and if it's any help this is the version of gcc and my os:
g++ --version
g++ (GCC) 4.7.2
Copyright (C) 2012 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
uname -a
Linux *computername* 3.6.11-1-ARCH #1 SMP PREEMPT Tue Dec 18 08:57:15 CET 2012 x86_64 GNU/Linux
Your code's behavior is undefined. std::timed_mutex has non-recursive ownership semantics.
It's forbidden to acquire the lock (include try_lock family) second time on the same thread.
C++11 Standard 30.4.1.3.1 [thread.timedmutex.class]/p3/b2: (thanks to Howard Hinnant)
3 The behavior of a program is undefined if:
a thread that owns a timed_mutex object calls lock(), try_lock(), try_lock_for(), or try_lock_until() on that object, or
C++11 Standard 30.4.1.2 [thread.mutex.requirements.mutex]/p6-7:
EDITED:
how i am going to "work around this" or get it to behave the way i want? Should i use a recursive mutex instead?
Generally speaking, it's discouraged to acquire/release lock of mutex object in light of exception safty. If you use unique_lock object instead, owns_lock() member function may help you.
Meanwhile recursive-mutex is useless for your purpose, because "recursive" means only "I(a thread) can acquire lock twice or more when I already own lock."
void test()
{
std::timed_mutex m;
std::unique_lock<decltype(m)> lk(m, std::defer_lock);
// acquire lock
lk.lock();
// You can query locked status via unique_lock object
std::cout << "Do I have own lock? " << lk.owns_lock() << std::endl;
// release lock
lk.unlock();
}
Tried the following example compiled with g++ -std=gnu++0x t1.cpp and g++ -std=c++0x t1.cpp but both of these result in the example aborting.
$ ./a.out
terminate called after throwing an instance of 'std::system_error'
what():
Aborted
Here is the sample:
#include <thread>
#include <iostream>
void doSomeWork( void )
{
std::cout << "hello from thread..." << std::endl;
return;
}
int main( int argc, char *argv[] )
{
std::thread t( doSomeWork );
t.join();
return 0;
}
I'm trying this on Ubuntu 11.04:
$ g++ --version
g++ (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2
Anyone knows what I've missed?
You have to join std::threads, just like you have to join pthreads.
int main( int argc, char *argv[] )
{
std::thread t( doSomeWork );
t.join();
return 0;
}
UPDATE: This Debian bug report pointed me to the solution: add -pthread to your commandline. This is most probably a workaround until the std::thread code stabilizes and g++ pulls that library in when it should (or always, for C++).
Please use the pthread library during the compilation: g++ -lpthread.
Simplest code to reproduce that error and how to fix:
Put this in a file called s.cpp:
#include <iostream>
#include <stdlib.h>
#include <string>
#include <unistd.h>
#include <thread>
using namespace std;
void task1(std::string msg){
cout << "task1 says: " << msg;
}
int main(){
std::thread t1(task1, "hello");
usleep(1000000);
t1.detach();
}
Compile like this:
el#apollo:~/foo7$ g++ -o s s.cpp -std=c++0x
Run it like this, the error happens:
el#apollo:~/foo7$ ./s
terminate called after throwing an instance of 'std::system_error'
what(): Operation not permitted
Aborted (core dumped)
To fix it, compile it like this with the -pthread flag:
g++ -o s s.cpp -std=c++0x -pthread
./s
Then it works correctly:
task1 says: hello
For what it's worth, I had different issue with similar code using threads in g++ (MinGW). Work-around was to put some "delay" between creating a thread and joining it.
Code with infrequently failing assertion:
std::atomic_bool flag{false};
std::thread worker( [&] () { flag.store(true); } );
worker.join();
assert(flag.load()); // Sometimes fails
Work-around:
std::atomic_bool flag{false};
std::thread worker( [&] () { flag.store(true); } );
while (not flag.load()) { std::this_thread::yield(); }
worker.join();
assert(flag.load()); // Works fine
Note that yield() alone did not help, hence the while loop. Using sleep_for(...) also works.
You need to link to run time library