727-C++11 - thread multithreading programming, thread mutual exclusion and synchronous communication, deadlock analysis and solution

Multithreading class thread of C++11

Before C++11, the C + + library did not provide classes or interfaces related to threads. Therefore, when writing multithreaded programs, you need to call CreateThread to create threads on Windows, and you need to call the interface function pthread of clone or pthread thread library on Linux_ Create to create a thread. However, this is a direct call to the system related API functions, and the code written can not be compiled and run across platforms.

Thread thread class is provided after C++11, which makes it easy to write multithreaded programs (Note: the compiler needs to support the syntax after C++11, and VS2019 and G + + version 4.6 or above are recommended). The code examples are as follows:

#include <iostream>
#include <thread>
#include <string>
using namespace std;

//Thread function for thread 1
void threadProc1()
{
	cout << "thread-1 run begin!" << endl;
	// Thread 1 sleep for 2 seconds
	std::this_thread::sleep_for(std::chrono::seconds(2));
	cout << "thread-1 2 Wake up in seconds, run end!" << endl;
}

//Thread function of thread 2
void threadProc2(int val, string info)
{
	cout << "thread-2 run begin!" << endl;
	cout << "thread-2 args[val:" << val << ",info:" << info << "]" << endl;
	//Thread 2 sleep for 4 seconds
	std::this_thread::sleep_for(std::chrono::seconds(4));
	cout << "thread-2 4 Wake up in seconds, run end!" << endl;
}
int main()
{
	cout << "main thread begin!" << endl;

	//Create a thread object, pass in thread functions and parameters, and the thread will start and run directly
	thread t(threadProc1);
	thread t1(threadProc2, 20, "hello world");

	//Wait for threads t and t1 to finish executing, and then the main thread will continue running
	t.join();
	t1.join();

	cout << "main thread end!" << endl;
	return 0;
}


It can be seen that it is very simple to write multithreaded programs at the C + + language level. To define Thread objects, you only need to pass in the corresponding thread functions and parameters.

The same code above is compiled with g + + under the Linux platform:
g + + source file name.cpp -lpthread
[note]: pthread thread dynamic library needs to be linked, so the thread class of C + + uses the relevant interfaces of pthread thread library in Linux environment.
Then trace the startup process of the program with the strace command:

strace ./a.out

The following printouts are available:

It shows that the calling process of C++ thread object starting thread is thread - > pthread_ Create - > clone is the same set used by the Linux pthread thread library. The advantage is that it can now be compiled and run across platforms. On Windows, of course, the CreateThread system API is called to create threads.

Thread mutual exclusion

For a code segment running in a multithreaded environment, it is necessary to consider whether there is a race condition. If there is a race condition, we say that the code segment is not thread safe and cannot run directly in a multithreaded environment. For such a code segment, we often call it a critical area resource. For a critical area resource, it is necessary to ensure that it is executed by atomic operation in a multithreaded environment, To ensure the atomic operation in the critical area, the mutually exclusive operation lock mechanism between threads needs to be used. The thread class library also provides a lighter atomic operation class based on CAS operation.

Next, simulate the scenario of selling tickets at the same time in three windows, and use the code example to illustrate the mutually exclusive operation between processes.

mutex of thread class library

The following code starts three threads to simulate three windows selling tickets at the same time. The total number of votes is 100. Because the integer operation is not thread safe, because in a multi-threaded environment, thread safety needs to be achieved by adding mutex locks. The code is as follows:

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;

//The total number of tickets is 100
volatile int tickets = 100;
//Global mutex
std::mutex mtx;

//Thread function
void sellTicketTask(std::string wndName)
{
	while (tickets > 0)
	{
		//Get mutex resource
		mtx.lock();
		if (tickets > 0)
		{
			std::cout << wndName << " Sale section" << tickets << "Ticket" << std::endl;
			tickets--;
		}
		//Release mutex resource
		mtx.unlock();

		//Sleep for 100ms for every ticket sold, so that every window has the opportunity to sell tickets
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}

//Simulate the ticket selling in the station window, and use the mutex of C++11 thread
int main()
{
	//Create three simulated window ticket selling threads
	std::thread t1(sellTicketTask, "Ticket window I");
	std::thread t2(sellTicketTask, "Ticket window II");
	std::thread t3(sellTicketTask, "Ticket window III");

	//Wait for three threads to complete execution
	t1.join();
	t2.join();
	t3.join();

	return 0;
}


From the above code, you can see that mutex of C++11 and pthread of pthread thread library on Linux platform_ mutex_ The use of T mutex is almost the same (in fact, mutex is the pthread_mutex_t mutex related system function called on the Linux platform). Mutex also supports the trylock livelock mechanism and can be tested by itself.

Thread thread class library atomic class based on CAS

In fact, in the above code, because the number of tickets is an integer, its -- operation needs to add mutually exclusive operations in a multi-threaded environment, but mutex mutex is heavy after all, which is a little expensive for the system. The thread class library of C++11 provides original sub operation classes for simple types, such as std::atomic_int,atomic_long,atomic_bool, etc. the increase and decrease of their values are based on CAS operation, which not only ensures thread safety, but also has very high efficiency.

The following code example starts 10 threads. Each thread increases the integer by 1000 times. When thread safety is guaranteed, it should be increased to 10000 times. In this case, atomic can be used_ Int. the code example is as follows:

#include <iostream>
#Include < atomic > / / atomic classes provided by C + + 11 thread library
#Include < thread > / / header file of C + + thread class library
#include <vector>

//Atomic integer, CAS operation guarantees the atomic operation of self increment and self decrement to count
std::atomic_int count = 0;

//Thread function
void sumTask()
{
	//Each thread adds 1000 times to count
	for (int i = 0; i < 1000; ++i)
	{
		count++;
	}
}

int main()
{
	//Create 10 threads and put them in the container
	std::vector<std::thread> vec;
	for (int i = 0; i < 10; ++i)
	{
		vec.push_back(std::thread(sumTask));
	}

	//Wait for thread execution to complete
	for (unsigned int i = 0; i < vec.size(); ++i)
	{
		vec[i].join();
	}

	//After all sub threads run, the result of count should be 10000 each time
	std::cout << "count : " << count << std::endl;

	return 0;
}


In fact, the atomic operation class of C++11 class library is also called CAS (compare_and_set) related system interface on Linux platform.

Thread synchronous communication

In the process of multithreading, each thread takes up CPU time slice to execute instructions and do things along with the OS scheduling algorithm. The operation of each thread has no order at all. However, in some application scenarios, a thread needs to wait for the running results of another thread before continuing to execute, which requires a synchronous communication mechanism between threads.

The most typical example of synchronous communication between threads is the producer consumer model. After the producer thread produces a product, it will notify the consumer thread to consume the product; If the consumer thread consumes the product and finds that no product has been produced, it needs to notify the producer thread to produce the product quickly. After the producer thread produces the product, the consumer thread can continue to execute.

Condition Variable provided by C++11 thread library_ Variable is the Condition Variable mechanism under Linux platform, which is used to solve the problem of synchronous communication between threads. The following code demonstrates a producer consumer thread model and carefully analyzes the code:

#include <iostream>           //std::cout
#include <thread>             //std::thread
#include <mutex>              //std::mutex, std::unique_lock
#include <condition_variable> //std::condition_variable
#include <vector>

//Define mutexes (condition variables need to be used with mutexes)
std::mutex mtx;
//Define condition variables (used for synchronous communication between threads)
std::condition_variable cv;
//Define a vector container as a container shared by producers and consumers
std::vector<int> vec;

//Producer thread function
void producer()
{
	//Every time a producer produces one, he informs consumers to consume one
	for (int i = 1; i <= 10; ++i)
	{
		//Get mtx mutex resource
		std::unique_lock<std::mutex> lock(mtx);

		//If the container is not empty, it means that there are still products not consumed. Wait for the consumer thread to consume before reproduction
		while (!vec.empty())
		{
			//Judge that the container is not empty, enter the state of waiting condition variable, and release the mtx lock,
			//Let the consumer thread grab the lock and be able to consume the product
			cv.wait(lock);
		}
		vec.push_back(i); // Indicates the serial number i of the product produced by the producer
		std::cout << "producer yield a product:" << i << std::endl;

		/*
		After the producer thread has finished producing the product, it notifies the consumer thread waiting on the cv condition variable,
		You can start consuming the product, and then release the lock mtx
		*/
		cv.notify_all();

		//Produce a product and sleep for 100ms
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}
//Consumer thread function
void consumer()
{
	//Every time a consumer consumes one, he informs the producer to produce one
	for (int i = 1; i <= 10; ++i)
	{
		//Get mtx mutex resource
		std::unique_lock<std::mutex> lock(mtx);

		//If the container is empty, it means that there is no product to consume, waiting for the producer to produce and then consume
		while (vec.empty())
		{
			//Judge that the container is empty, enter the state of waiting condition variable, and release the mtx lock,
			//Let the producer thread grab the lock and be able to produce the product
			cv.wait(lock);
		}
		int data = vec.back(); // Indicates the product serial number i consumed by the consumer
		vec.pop_back();
		std::cout << "consumer Consumer products:" << data << std::endl;

		/*
		When the consumer has consumed the product, it notifies the producer thread waiting on the cv condition variable,
		You can start production and release the lock mtx
		*/
		cv.notify_all();

		//Consume a product and sleep for 100ms
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}
int main()
{
	//Create producer and consumer threads
	std::thread t1(producer);
	std::thread t2(consumer);

	//Main the main thread waits for all child threads to finish executing
	t1.join();
	t2.join();

	return 0;
}


It can be seen that the producer and consumer threads alternately produce and consume products, and the two threads have a perfect communication and coordinated operation.

Case analysis and solution of deadlock problem

The problem of deadlock is often investigated. In the face of the problem of which situations the program will deadlock, instead of thinking about how to recite the theory in the book, it is better to give examples from the perspective of practice, how to analyze and locate the deadlock problem, and then find the problem points for modification.

When our program is running, the phenomenon of fake death may be that the program is in an endless loop, the program is blocked because the I/O and network events waiting for the program do not occur, or the program is deadlock. The following examples illustrate how to allow the deadlock of our program under Linux system.

Example:
When multiple threads of A program obtain multiple mutex resources, there may be A life and death lock problem. For example, thread A obtains lock 1 first, and thread B obtains lock 2, and then thread A needs to obtain lock 2 to continue execution. However, since lock 2 is held by thread B and has not been released, thread A is blocked in order to wait for lock 2 resources; At this time, thread B needs to obtain lock 1 to execute downward, but because lock 1 is held by thread A, thread A also enters blocking.

Thread A and thread B are waiting for each other to release the lock resources, but they are unwilling to release the original lock resources, resulting in threads A and B waiting for each other and process deadlock. The following code example demonstrates this problem:

#include <iostream>           //std::cout
#include <thread>             //std::thread
#include <mutex>              //std::mutex, std::unique_lock
#include <condition_variable> //std::condition_variable
#include <vector>

//Lock resource 1
std::mutex mtx1;
//Lock resource 2
std::mutex mtx2;

//Function of thread A
void taskA()
{
	//Ensure that thread A acquires lock 1 first
	std::lock_guard<std::mutex> lockA(mtx1);
	std::cout << "thread  A Acquire lock 1" << std::endl;

	//Thread A sleeps for 2s and then acquires lock 2 to ensure that lock 2 is acquired by thread B first, simulating the occurrence of deadlock problem
	std::this_thread::sleep_for(std::chrono::seconds(2));

	//Thread A acquires lock 2 first
	std::lock_guard<std::mutex> lockB(mtx2);
	std::cout << "thread  A Acquire lock 2" << std::endl;

	std::cout << "thread  A Release all lock resources and end the operation!" << std::endl;
}

//Function of thread B
void taskB()
{
	//Thread B sleeps for 1s to ensure that thread A obtains lock 1 first
	std::this_thread::sleep_for(std::chrono::seconds(1));
	std::lock_guard<std::mutex> lockB(mtx2);
	std::cout << "thread  B Acquire lock 2" << std::endl;

	//Thread B attempted to acquire lock 1
	std::lock_guard<std::mutex> lockA(mtx1);
	std::cout << "thread  B Acquire lock 1" << std::endl;

	std::cout << "thread  B Release all lock resources and end the operation!" << std::endl;
}
int main()
{
	//Create producer and consumer threads
	std::thread t1(taskA);
	std::thread t2(taskB);

	//Main the main thread waits for all child threads to finish executing
	t1.join();
	t2.join();

	return 0;
}


It can be seen that after thread A obtains lock 1 and thread B obtains lock 2, the process will not continue to execute and has been waiting here. If this is A problem scenario we encounter, how can we judge that it is caused by inter thread deadlock?

First, check the current running status and PID of the process through the ps command

root@lin-virtual-machine:/home/lin# ps -aux | grep a.out
lin 1953 0.0 0.0 98108 1904 pts/0 Sl+ 10:41 0:00 ./a.out
root 2064 0.0 0.0 21536 1076 pts/1 S+ 10:51 0:00 grep --color=auto a.out

As can be seen from the above command, the PID of a.out process is 1953 and the current state is Sl +, which is equivalent to that all multithreaded programs enter the blocking state.
Use the top command to check the specific operation of each thread in the process

root@lin-virtual-machine:/home/lin# top -Hp 1953
process USER PR NI VIRT RES SHR CPU %MEM TIME+ COMMAND
1953 lin 20 0 98108 1904 1752 S 0.0 0.1 0:00.00 a.out
1954 lin 20 0 98108 1904 1752 S 0.0 0.1 0:00.00 a.out
1955 lin 20 0 98108 1904 1752 S 0.0 0.1 0:00.00 a.out

It can be seen from the print information of the top command that all threads enter the blocking state, and the CPU occupancy rate is 0.0, which can eliminate the problem of dead cycle, because dead cycle will cause high CPU utilization, and the thread state will not be S. Next, the thread may be blocked because the I/O network event does not occur, or the thread may deadlock.

Remotely debug the running program through gdb and print the call stack information of each thread of the process. The process is as follows:
Remotely debug the above a.out process through gdb attach pid. The command is as follows:

root@lin-virtual-machine:/home/lin# gdb attach 1953

After entering the gdb debugging command line, print the call stack information of all threads, as follows:
(gdb) thread apply all bt

Thread 3 (Thread 0x7feb523ec700 (LWP 1955)):
#0 _llllock_wait () at .../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
#1 0x00007feb53928023 in __GI___pthread_mutex_lock (mutex=0x5646aabe7140 ) at .../nptl/pthread_mutex_lock.c:78
#2 0x00005646aa9e40bf in __gthread_mutex_lock(pthread_mutex_t*) ()
#3 0x00005646aa9e4630 in std::mutex::lock() ()
#4 0x00005646aa9e46ac in std::lock_guardstd::mutex::lock_guard(std::mutex&) ()
#5 0x00005646aa9e42c0 in taskB() ()
#6 0x00005646aa9e4bdb in void std::__invoke_impl)()>(std::__invoke_other, void (&&)()) ()
#7 0x00005646aa9e49e8 in std::__invoke_result)()>::type std::__invoke<void ()()>(void (&&)()) ()
#8 0x00005646aa9e50b6 in decltype (__invoke((_S_declval<0ul>)())) std::thread::_Invoker<std::tuple<void ()()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) ()
#9 0x00005646aa9e5072 in std::thread::_Invoker)()> >::operator()() ()
#10 0x00005646aa9e5042 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void ()()> > >::_M_run() ()
#11 0x00007feb5365257f in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#12 0x00007feb539256db in start_thread (arg=0x7feb523ec700) at pthread_create.c:463
#13 0x00007feb530ad88f in clone () at .../sysdeps/unix/sysv/linux/x86_64/clone.S:95
Thread 2 (Thread 0x7feb52bed700 (LWP 1954)):
#0 _llllock_wait () at .../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
#1 0x00007feb53928023 in __GI___pthread_mutex_lock (mutex=0x5646aabe7180 ) at .../nptl/pthread_mutex_lock.c:78
#2 0x00005646aa9e40bf in __gthread_mutex_lock(pthread_mutex_t*) ()
#3 0x00005646aa9e4630 in std::mutex::lock() ()
#4 0x00005646aa9e46ac in std::lock_guardstd::mutex::lock_guard(std::mutex&) ()
#5 0x00005646aa9e4183 in taskA() ()
#6 0x00005646aa9e4bdb in void std::__invoke_impl)()>(std::__invoke_other, void (&&)()) ()
#7 0x00005646aa9e49e8 in std::__invoke_result)()>::type std::__invoke<void ()()>(void (&&)()) ()
#8 0x00005646aa9e50b6 in decltype (__invoke((_S_declval<0ul>)())) std::thread::_Invoker<std::tuple<void ()()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) ()
#9 0x00005646aa9e5072 in std::thread::_Invoker)()> >::operator()() ()
#10 0x00005646aa9e5042 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void ()()> > >::_M_run() ()
#11 0x00007feb5365257f in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#12 0x00007feb539256db in start_thread (arg=0x7feb52bed700) at pthread_create.c:463
#13 0x00007feb530ad88f in clone () at .../sysdeps/unix/sysv/linux/x86_64/clone.S:95
Thread 1 (Thread 0x7feb53d4b740 (LWP 1953)):
—Type to continue, or q to quit—
#0 0x00007feb53926d2d in __GI___pthread_timedjoin_ex (threadid=140648682280704, thread_return=0x0, abstime=0x0,
block=) at pthread_join_common.c:89
#1 0x00007feb536527d3 in std::thread::join() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#2 0x00005646aa9e43bb in main ()
(gdb)

From the above thread call stack information, we can see that the current process has three threads: Thread1 is the main thread, Thread2 is the taskA thread, and Thread3 is the taskB thread.

From the call stack information, we can see that the reason why Thread3 thread enters the S blocking state is because it is finally in the #0_ llllock_ Wait () at, that is, it is waiting to acquire a lock_wait, and the stack information is printed clearly, #1 0x00007feb53928023 in__ GI___ pthread_ mutex_ lock (mutex=0x5646aabe7140 ) at …/nptl/pthread_mutex_lock.c:78, Thread3 is getting but can't get it, so it enters the blocking state. In combination with code analysis, Thread3 thread (i.e. taskB) is finally blocked here:

void taskB()
{undefined
//Thread B sleeps for 1s to ensure that thread A obtains lock 1 first
std::this_thread::sleep_for(std::chrono::seconds(1));
std::lock_guardstd::mutex lockB(mtx2);
std::cout << "thread  B Acquire lock 2"<< std::endl;
//Thread B attempted to acquire lock 1
std::lock_guardstd::mutex lockA(mtx1); //===It's blocked here! If you don't know how to locate the source line, look at the next section!
std::cout << "thread  B Acquire lock 1" << std::endl;
std::cout << "thread  B Release all lock resources and end the operation!" << std::endl;
}

It can still be seen from the call stack information that the reason why Thread2 thread enters the S blocking state is because it is finally in #0_ llllock_ Wait () at, that is, it is waiting to acquire a lock_wait, and the stack information is printed clearly, #1 0x00007feb53928023 in__ GI___ pthread_ mutex_ lock (mutex=0x5646aabe7180 ) at …/nptl/pthread_mutex_lock.c:78, Thread2 is getting but can't get it, so it enters the blocking state. Combined with code analysis, Thread2 thread (i.e. taskA) finally blocked here:

void taskA()
{undefined
// Ensure that thread A acquires lock 1 first
std::lock_guardstd::mutex lockA(mtx1);
std::cout << "thread  A Acquire lock 1"<< std::endl;
// Thread A sleeps for 2s and then acquires lock 2 to ensure that lock 2 is acquired by thread B first, simulating the occurrence of deadlock problem
std::this_thread::sleep_for(std::chrono::seconds(2));
// Thread A acquires lock 2 first
std::lock_guardstd::mutex lockB(mtx2); ===> It's blocked here! If you don't know how to locate the source line, look at the next section!
std::cout << "thread  A Acquire lock 2" << std::endl;
std::cout << "thread  A Release all lock resources and end the operation!" << std::endl;
}

Since it is found that the reason why taskA and taskB threads are blocked is that the lock cannot be obtained, and then analyze and locate in combination with the source code, it is finally found that the reason why taskA cannot obtain mtx2 is that mtx2 has long been obtained by taskB threads; Similarly, the reason why taskB cannot obtain mtx1 is that mtx1 has long been obtained by taskA thread, resulting in all threads entering the blocking state and waiting for the acquisition of lock resources. However, no thread releases the lock, resulting in deadlock. (it can be seen from the call stack information of each thread that it has nothing to do with I/O network events)

How to locate the problem code on the source code

In fact, the above code generally runs in the released release version, and there is no debugging information inside. If we want to locate the cause of the deadlock on a line of code in the source code, we need a debug version (g + + compilation and add the - g option). The operation is as follows:
1. Compile command

lin@lin-virtual-machine:~/code$ g++ 20190316.cpp -g -lpthread

2. Operation code

lin@lin-virtual-machine:~/code$ ./a.out

Thread A acquires lock 1
Thread B acquires lock 2
... (the program will not run down here)

3.gdb debug the process

root@lin-virtual-machine:/home/lin/code# ps -ef | grep a.out
lin 2617 1535 0 12:32 pts/0 00:00:00 ./a.out
root@tony-virtual-machine:/home/lin/code# gdb attach 2617

4. View all current threads
(gdb) info threads

  Id   Target Id         Frame 
* 1    Thread 0x7f8c63002740 (LWP 2617) "a.out" 0x00007f8c62bddd2d in __GI___pthread_timedjoin_ex (
    threadid=140240914892544, thread_return=0x0, abstime=0x0, block=<optimized out>) at pthread_join_common.c:89
  2    Thread 0x7f8c61ea4700 (LWP 2618) "a.out" __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
  3    Thread 0x7f8c616a3700 (LWP 2619) "a.out" __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135

You can see that there are three threads.
5. Switch to thread 2
(gdb) thread 2
6. Check the current call stack information of thread 2. Either the where or bt command can be used
(gdb) where

(gdb) where
#0  __lll_lock_wait () at ../sysdeps/unix/sysv/linux/x86_64/lowlevellock.S:135
#1  0x00007f8c62bdf023 in __GI___pthread_mutex_lock (mutex=0x55678928e180 <mtx2>) at ../nptl/pthread_mutex_lock.c:78
#2  0x000055678908b0bf in __gthread_mutex_lock (__mutex=0x55678928e180 <mtx2>)
    at /usr/include/x86_64-linux-gnu/c++/7/bits/gthr-default.h:748
#3  0x000055678908b630 in std::mutex::lock (this=0x55678928e180 <mtx2>) at /usr/include/c++/7/bits/std_mutex.h:103
#4  0x000055678908b6ac in std::lock_guard<std::mutex>::lock_guard (this=0x7f8c61ea3dc0, __m=...)
    at /usr/include/c++/7/bits/std_mutex.h:162
#5  0x000055678908b183 in taskA () at 20190316.cpp:23
#6  0x000055678908bbdb in std::__invoke_impl<void, void (*)()> (__f=@0x556789d78e78: 0x55678908b0f7 <taskA()>)
    at /usr/include/c++/7/bits/invoke.h:60
#7  0x000055678908b9e8 in std::__invoke<void (*)()> (__fn=@0x556789d78e78: 0x55678908b0f7 <taskA()>)
    at /usr/include/c++/7/bits/invoke.h:95
#8  0x000055678908c0b6 in std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul> (this=0x556789d78e78)
    at /usr/include/c++/7/thread:234
#9  0x000055678908c072 in std::thread::_Invoker<std::tuple<void (*)()> >::operator() (this=0x556789d78e78)
    at /usr/include/c++/7/thread:243
#10 0x000055678908c042 in std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run (
    this=0x556789d78e70) at /usr/include/c++/7/thread:186
#11 0x00007f8c6290957f in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#12 0x00007f8c62bdc6db in start_thread (arg=0x7f8c61ea4700) at pthread_create.c:463
#13 0x00007f8c6236488f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

7. View the frame 5 information of thread 2 above #5 0x000055678908b183 in task a () at 20190316. CPP: 23
(gdb) f 5

#5 0x000055678908b183 in taskA () at 20190316.cpp:23
23 std::lock_guard< std::mutex > lockB(mtx2);

As you can see, it is directly located here that the code has been blocked in line 23 of 20190316.cpp, and the corresponding line code is STD:: lock_ guard< std::mutex > lockB(mtx2);

Deadlock problem code modification

Now that the problem is found, we know that the deadlock in this problem scenario is caused by the inconsistency of the order when multiple threads acquire multiple lock resources. The problem can be solved by ensuring that the order in which they acquire locks is consistent. The code is modified as follows:

#include <iostream>           //std::cout
#include <thread>             //std::thread
#include <mutex>              //std::mutex, std::unique_lock
#include <condition_variable> //std::condition_variable
#include <vector>

//Lock resource 1
std::mutex mtx1;
//Lock resource 2
std::mutex mtx2;

// Function of thread A
void taskA()
{
	//Ensure that thread A acquires lock 1 first
	std::lock_guard<std::mutex> lockA(mtx1);
	std::cout << "thread  A Acquire lock 1" << std::endl;

	//Thread A attempted to acquire lock 2
	std::lock_guard<std::mutex> lockB(mtx2);
	std::cout << "thread  A Acquire lock 2" << std::endl;

	std::cout << "thread  A Release all lock resources and end the operation!" << std::endl;
}

// Function of thread B
void taskB()
{
	//Thread B acquires lock 1
	std::lock_guard<std::mutex> lockA(mtx1);
	std::cout << "thread  B Acquire lock 1" << std::endl;

	//Thread B attempted to acquire lock 2
	std::lock_guard<std::mutex> lockB(mtx2);
	std::cout << "thread  B Acquire lock 2" << std::endl;

	std::cout << "thread  B Release all lock resources and end the operation!" << std::endl;
}
int main()
{
	//Create producer and consumer threads
	std::thread t1(taskA);
	std::thread t2(taskB);

	//Main the main thread waits for all child threads to finish executing
	t1.join();
	t2.join();

	return 0;
}


About my other blog about multithreading: I talked about it many times in the columns "learning C + + and" learning Linux system ".

Tags: C++ Multithreading

Posted on Thu, 28 Oct 2021 09:35:41 -0400 by renegade44