Problems happening with multithreading (C++)

The two most common problems that are happing when dealing with multithreading are the deadlock and the race condition. Follow this Link to get an easy code example for both of them.


Deadlock
A deadlock is happening when a thread keeps a lock constantly. All threads that have to excute the still locked code parts are getting blocked. The user will normaly experience this problem with a (partly) program freeze.

Example
Code

#include <thread>
#include <mutex>
#include <chrono>
#include <iostream>
 
class FooBar
{
public:
  void foo()
  {
    std::lock_guard<std::mutex> lock1(m_Mutex1);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::lock_guard<std::mutex> lock2(m_Mutex2);
    std::cout << "foo" << std::endl;
  }
 
  void bar()
  {
    std::lock_guard<std::mutex> lock1(m_Mutex2);
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::lock_guard<std::mutex> lock2(m_Mutex1);
    std::cout << "bar" << std::endl;
  }
private:
  std::mutex m_Mutex1;
  std::mutex m_Mutex2;
};
 
int main()
{
  FooBar fb;
  std::thread t1(&FooBar::foo, &fb), t2(&FooBar::bar, &fb);
  t1.join(); t2.join();
}

Analysis

$ g++ main.cpp -pthread -g
$ gdb a.out
(gdb) run # afterwards press 'ctrl' + 'c'
[New Thread 0x7ffff6f47700 (LWP 18927)]
[New Thread 0x7ffff6746700 (LWP 18928)]
^C
Thread 1 "a.out" received signal SIGINT, Interrupt.
0x00007ffff762398d in pthread_join (threadid=140737336604416, thread_return=0x0) at pthread_join.c:90
90	pthread_join.c: No such file or directory.
(gdb) thread apply all backtrace

Thread 3 (Thread 0x7ffff6746700 (LWP 18928)):
#0  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
#1  0x00007ffff7624dbd in __GI___pthread_mutex_lock (mutex=0x7fffffffdc70) at ../nptl/pthread_mutex_lock.c:80
#2  0x0000000000400ff9 in __gthread_mutex_lock (__mutex=0x7fffffffdc70) at /usr/include/x86_64-linux-gnu/c++/6/bits/gthr-default.h:748
#3  0x00000000004012e2 in std::mutex::lock (this=0x7fffffffdc70) at /usr/include/c++/6/bits/std_mutex.h:103
#4  0x0000000000401532 in std::lock_guard<std::mutex>::lock_guard (this=0x7ffff6745db0, __m=...) at /usr/include/c++/6/bits/std_mutex.h:162
#5  0x000000000040148a in FooBar::bar (this=0x7fffffffdc70) at main.cpp:21
#6  0x00000000004024a7 in std::__invoke_impl<void, void (FooBar::* const&)(), FooBar*>(std::__invoke_memfun_deref, void (FooBar::* const&)(), FooBar*&&) (__f=@0x616d90: (void (FooBar::*)(FooBar * const)) 0x40141e <FooBar::bar()>, 
    __t=<unknown type in /home/user/Documents/gdb-tests/a.out, CU 0x0, DIE 0x6131>) at /usr/include/c++/6/functional:227
#7  0x0000000000402420 in std::__invoke<void (FooBar::* const&)(), FooBar*>(void (FooBar::* const&)(), FooBar*&&) (__fn=@0x616d90: (void (FooBar::*)(FooBar * const)) 0x40141e <FooBar::bar()>, 
    __args#0=<unknown type in /home/user/Documents/gdb-tests/a.out, CU 0x0, DIE 0x6131>) at /usr/include/c++/6/functional:251
#8  0x00000000004023c2 in std::_Mem_fn_base<void (FooBar::*)(), true>::operator()<FooBar*>(FooBar*&&) const (this=0x616d90, __args#0=<unknown type in /home/user/Documents/gdb-tests/a.out, CU 0x0, DIE 0x6131>) at /usr/include/c++/6/functional:604
#9  0x000000000040238d in std::_Bind_simple<std::_Mem_fn<void (FooBar::*)()> (FooBar*)>::_M_invoke<0ul>(std::_Index_tuple<0ul>) (this=0x616d88) at /usr/include/c++/6/functional:1391
#10 0x00000000004022c8 in std::_Bind_simple<std::_Mem_fn<void (FooBar::*)()> (FooBar*)>::operator()() (this=0x616d88) at /usr/include/c++/6/functional:1380
#11 0x0000000000402298 in std::thread::_State_impl<std::_Bind_simple<std::_Mem_fn<void (FooBar::*)()> (FooBar*)> >::_M_run() (this=0x616d80) at /usr/include/c++/6/thread:197
#12 0x00007ffff7b0b16f in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#13 0x00007ffff76226ba in start_thread (arg=0x7ffff6746700) at pthread_create.c:333
#14 0x00007ffff735841d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109

Thread 2 (Thread 0x7ffff6f47700 (LWP 18927)):
#0  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
#1  0x00007ffff7624dbd in __GI___pthread_mutex_lock (mutex=0x7fffffffdc98) at ../nptl/pthread_mutex_lock.c:80
#2  0x0000000000400ff9 in __gthread_mutex_lock (__mutex=0x7fffffffdc98) at /usr/include/x86_64-linux-gnu/c++/6/bits/gthr-default.h:748
#3  0x00000000004012e2 in std::mutex::lock (this=0x7fffffffdc98) at /usr/include/c++/6/bits/std_mutex.h:103
#4  0x0000000000401532 in std::lock_guard<std::mutex>::lock_guard (this=0x7ffff6f46db0, __m=...) at /usr/include/c++/6/bits/std_mutex.h:162
#5  0x00000000004013a0 in FooBar::foo (this=0x7fffffffdc70) at main.cpp:13
#6  0x00000000004024a7 in std::__invoke_impl<void, void (FooBar::* const&)(), FooBar*>(std::__invoke_memfun_deref, void (FooBar::* const&)(), FooBar*&&) (__f=@0x616c30: (void (FooBar::*)(FooBar * const)) 0x401334 <FooBar::foo()>, 
    __t=<unknown type in /home/user/Documents/gdb-tests/a.out, CU 0x0, DIE 0x6131>) at /usr/include/c++/6/functional:227
#7  0x0000000000402420 in std::__invoke<void (FooBar::* const&)(), FooBar*>(void (FooBar::* const&)(), FooBar*&&) (__fn=@0x616c30: (void (FooBar::*)(FooBar * const)) 0x401334 <FooBar::foo()>, 
    __args#0=<unknown type in /home/user/Documents/gdb-tests/a.out, CU 0x0, DIE 0x6131>) at /usr/include/c++/6/functional:251
#8  0x00000000004023c2 in std::_Mem_fn_base<void (FooBar::*)(), true>::operator()<FooBar*>(FooBar*&&) const (this=0x616c30, __args#0=<unknown type in /home/user/Documents/gdb-tests/a.out, CU 0x0, DIE 0x6131>) at /usr/include/c++/6/functional:604
#9  0x000000000040238d in std::_Bind_simple<std::_Mem_fn<void (FooBar::*)()> (FooBar*)>::_M_invoke<0ul>(std::_Index_tuple<0ul>) (this=0x616c28) at /usr/include/c++/6/functional:1391
#10 0x00000000004022c8 in std::_Bind_simple<std::_Mem_fn<void (FooBar::*)()> (FooBar*)>::operator()() (this=0x616c28) at /usr/include/c++/6/functional:1380
#11 0x0000000000402298 in std::thread::_State_impl<std::_Bind_simple<std::_Mem_fn<void (FooBar::*)()> (FooBar*)> >::_M_run() (this=0x616c20) at /usr/include/c++/6/thread:197
#12 0x00007ffff7b0b16f in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#13 0x00007ffff76226ba in start_thread (arg=0x7ffff6f47700) at pthread_create.c:333
#14 0x00007ffff735841d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109

Thread 1 (Thread 0x7ffff7fdb740 (LWP 18923)):
#0  0x00007ffff762398d in pthread_join (threadid=140737336604416, thread_return=0x0) at pthread_join.c:90
#1  0x00007ffff7b0b3f3 in std::thread::join() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#2  0x00000000004010df in main () at main.cpp:33
(gdb) thread 3
[Switching to thread 3 (Thread 0x7ffff6746700 (LWP 18928))]
#0  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
135	../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S: No such file or directory.
(gdb) frame 1
#1  0x00007ffff7624dbd in __GI___pthread_mutex_lock (mutex=0x7fffffffdc70) at ../nptl/pthread_mutex_lock.c:80
80	../nptl/pthread_mutex_lock.c: No such file or directory.
(gdb) p mutex.__data.__owner
$1 = 18927
(gdb) thread 2
[Switching to thread 2 (Thread 0x7ffff6f47700 (LWP 18927))]
#0  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
135	../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S: No such file or directory.
(gdb) frame 1
#1  0x00007ffff7624dbd in __GI___pthread_mutex_lock (mutex=0x7fffffffdc98) at ../nptl/pthread_mutex_lock.c:80
80	../nptl/pthread_mutex_lock.c: No such file or directory.
(gdb) p mutex.__data.__owner
$2 = 18928
(gdb) quit

Thread 3 (LWP 18928) waits for the mutex owned by LWP 18927
Thread 2 (LWP 18927) waits for the mutex owned by LWP 18928
–> Deadlock

Fix
Restructure the code or modify the scope of the different mutexes.

Information: There are a few awesome deadlock detection plugins for the GDB
https://github.com/DamZiobro/gdb-automatic-deadlock-detector
https://github.com/gkoszegi/gdb_deadlock_plugin


Race condition
A race condition is happening if more then one thread are executing interdependent code in a undeterministic order. The user will recognise this if the program (sometimes) behave in a (totally) different way.

Example
Code

#include <thread>
#include <chrono>
#include <vector>
#include <iostream>
 
class FooBar
{
public:
  FooBar() : m_Vector({1,2,3}) {}

  void foo()
  {
    const int vecSize = m_Vector.size();
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    const int lastVal = m_Vector.at(vecSize-1);
    std::cout << "lastVal: " << lastVal << std::endl;
  }
 
  void bar()
  {
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    m_Vector.clear();
  }
private:
  std::vector<int> m_Vector;
};
 
int main()
{
  FooBar fb;
  std::thread t1(&FooBar::foo, &fb), t2(&FooBar::bar, &fb);
  t1.join(); t2.join();
}

Analysis

$ g++ main.cpp -pthread -g
$ gdb a.out
(gdb) run
[New Thread 0x7ffff6f47700 (LWP 19120)]
[New Thread 0x7ffff6746700 (LWP 19121)]
[Thread 0x7ffff6746700 (LWP 19121) exited]
terminate called after throwing an instance of 'std::out_of_range'
  what():  vector::_M_range_check: __n (which is 2) >= this->size() (which is 0)

Thread 2 "a.out" received signal SIGABRT, Aborted.
[Switching to Thread 0x7ffff6f47700 (LWP 19120)]
0x00007ffff7286428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
54	../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
(gdb) catch throw
Catchpoint 1 (throw)
(gdb) r
[New Thread 0x7ffff6f47700 (LWP 19334)]
[New Thread 0x7ffff6746700 (LWP 19335)]
[Thread 0x7ffff6746700 (LWP 19335) exited]
[Switching to Thread 0x7ffff6f47700 (LWP 19334)]

Thread 2 "a.out" hit Catchpoint 1 (exception thrown), 0x00007ffff7adef1d in __cxa_throw () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
(gdb) bt
#0  0x00007ffff7adef1d in __cxa_throw () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#1  0x00007ffff7b08ad7 in std::__throw_out_of_range_fmt(char const*, ...) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#2  0x0000000000401dcc in std::vector<int, std::allocator<int> >::_M_range_check (this=0x7fffffffdcb0, __n=2) at /usr/include/c++/6/bits/stl_vector.h:804
#3  0x0000000000401adb in std::vector<int, std::allocator<int> >::at (this=0x7fffffffdcb0, __n=2) at /usr/include/c++/6/bits/stl_vector.h:825
#4  0x00000000004013c8 in FooBar::foo (this=0x7fffffffdcb0) at main.cpp:15
#5  0x0000000000402c95 in std::__invoke_impl<void, void (FooBar::* const&)(), FooBar*>(std::__invoke_memfun_deref, void (FooBar::* const&)(), FooBar*&&) (__f=@0x617c50: (void (FooBar::*)(FooBar * const)) 0x401360 <FooBar::foo()>, 
    __t=<unknown type in /home/user/Documents/gdb-tests/a.out, CU 0x0, DIE 0x732b>) at /usr/include/c++/6/functional:227
#6  0x0000000000402c0e in std::__invoke<void (FooBar::* const&)(), FooBar*>(void (FooBar::* const&)(), FooBar*&&) (__fn=@0x617c50: (void (FooBar::*)(FooBar * const)) 0x401360 <FooBar::foo()>, 
    __args#0=<unknown type in /home/user/Documents/gdb-tests/a.out, CU 0x0, DIE 0x732b>) at /usr/include/c++/6/functional:251
#7  0x0000000000402bb0 in std::_Mem_fn_base<void (FooBar::*)(), true>::operator()<FooBar*>(FooBar*&&) const (this=0x617c50, __args#0=<unknown type in /home/user/Documents/gdb-tests/a.out, CU 0x0, DIE 0x732b>) at /usr/include/c++/6/functional:604
#8  0x0000000000402b7b in std::_Bind_simple<std::_Mem_fn<void (FooBar::*)()> (FooBar*)>::_M_invoke<0ul>(std::_Index_tuple<0ul>) (this=0x617c48) at /usr/include/c++/6/functional:1391
#9  0x0000000000402ab6 in std::_Bind_simple<std::_Mem_fn<void (FooBar::*)()> (FooBar*)>::operator()() (this=0x617c48) at /usr/include/c++/6/functional:1380
#10 0x0000000000402a86 in std::thread::_State_impl<std::_Bind_simple<std::_Mem_fn<void (FooBar::*)()> (FooBar*)> >::_M_run() (this=0x617c40) at /usr/include/c++/6/thread:197
#11 0x00007ffff7b0b16f in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#12 0x00007ffff76226ba in start_thread (arg=0x7ffff6f47700) at pthread_create.c:333
#13 0x00007ffff735841d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109
(gdb) f 4
#4  0x00000000004013c8 in FooBar::foo (this=0x7fffffffdcb0) at main.cpp:15
15	    const int lastVal = m_Vector.at(vecSize-1);
(gdb) info locals
vecSize = 3
lastVal = 0
(gdb) p m_Vector
$1 = std::vector of length 0, capacity 3
(gdb) quit

Someone has already cleared m_Vector
–> Race condition

Fix
Restructure the code or use a mutex to synchronize the access on that member variable.