C++ multi-threading study notes

Threaded sleep is very stable, but threadless sleep is unstable


Thread calls class method:


Call the method with parameters:


When the parameter is a reference:


Detach separates the thread. The detached thread is performed at the same time as the main thread. Join will cause the main thread to suspend and execute the process that comes in from the join.

Detach must be called while the main thread is still running. In other words, the main thread cannot end before the detach thread ends. At the same time, the detach function parameters cannot be local variables. In other words, it cannot The variable is destroyed before the called function ends.


When the same resource is referenced by multiple threads at the same time, in order to prevent resource preemption, use mutex or mutex lock.

Header file#include "mutex"


lock_guard<type> variable name (lock variable);

Function, in order to prevent deadlock from occurring, it can automatically lock and unlock locks


unique_lock<type> variable name (lock name, variable parameter);

Delayed locking, defining data directly like this will cause confusion

Must be manually locked 

Directly using the variable parameter std::adopt_lock will also directly cause data confusion. 

std::adopt_lock is only used to take over the previous lock mtx, so mtx needs to be locked before this line before it is actually locked.


#include <condition_variable>

Used for the main thread, sub-threads execute in sequence

If you need to execute threads one by one, you can write like this

#include "iostream"
#include "chrono"
#include "thread"
#include "condition_variable"
#include "mutex"

using namespace std;

mutex mtx;
condition_variable cv;
bool sub_run = false;
int number = 0;

class A {
public:
	void add(int &b) {
		while (b < 10) {
			unique_lock<mutex> queLock(mtx);
			cv.wait(queLock, [&] { return !(number - 1); });
			b++; 
			cout << " add " << b << endl;
			this_thread::sleep_for(chrono::milliseconds(10));
			number = 2;
			cv.notify_all();
		}
	}
};

void Dec(int& c) {
	while (c < 10) {
		unique_lock<mutex> uniLock(mtx);
		cv.wait(uniLock, [&] { return !number; });
		c--;
		cout << " Dec " << c << endl;
		this_thread::sleep_for(chrono::milliseconds(10));
		number = 1;
		cv.notify_all();
	}
}

int main() {
	A a;
	int num = 5;
	thread th(&A::add, &a, ref(num));
	thread th1(Dec, ref(num));
	while (num < 10) {
		unique_lock<mutex> mainUniLock(mtx);
		cv.wait(mainUniLock, [&] { return !(number - 2); });
		num++;
		cout << " Main " << num << endl;
		this_thread::sleep_for(chrono::milliseconds(10));
		number = 0;
		cv.notify_all();
	}
	th.join();
	th1.join();
	cout << num << endl;

	return 0;
}


nofity_one() will only randomly wake up one thread running in it


call_once(once_flag, this_thread::get_id());

Header file: #include "mutex"

Function: The thread can only call this method once

Only called once

No limit on main thread 


How to get the final num result before joining?

promise     future

Header file: #include "future"


Custom start thread function:

Header file #include "future"

packaged_task<function type> variable name (function name);

Mainly can be used with promise and future


async

Header file #include "future"

The function call will be made after fvalue.get()


Atomic operations

Allows unlimited concurrent programming. Every atomic operation involving the same object is indivisible with respect to any other atomic operation. Atomic objects do not have data races.

Header file: #include "atomic"

Locks are used to carefully control operations at the expense of performance. At this time, atomic variables are used.

Unlocked:

Lock:

Atomic operations:


The difference between C++11 multi-threading std::async and std::thread_c++11 thread asy-CSDN blog

Reference: 60 tool library-tuple_bilibili_bilibili


// Fill out your copyright notice in the Description page of Project Settings.


#include "TaskTest.h"
#include "chrono"
#include "Kismet/KismetSystemLibrary.h"
#include "mutex"
#include "thread"

// Sets default values
ATaskTest::ATaskTest()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void ATaskTest::BeginPlay()
{
	Super::BeginPlay();
	int a = 1;
	std::thread th(&ATaskTest::ThreadDo, this, std::ref(a));
	th.join();
	
}

// Called every frame
void ATaskTest::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

std::mutex mtx;
void ATaskTest::ThreadDo(int& value) {
	while (value < 10000) {
		mtx.lock();
		//std::this_thread::sleep_for(std::chrono::milliseconds(10));
		UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
		value++;
		mtx.unlock();
	}
	UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
}


// Fill out your copyright notice in the Description page of Project Settings.


#include "TaskTest.h"
#include "chrono"
#include "Kismet/KismetSystemLibrary.h"
#include "mutex"
#include "thread"

// Sets default values
ATaskTest::ATaskTest()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

// Called when the game starts or when spawned
void ATaskTest::BeginPlay()
{
	Super::BeginPlay();
	std::thread th(&ATaskTest::ThreadDo, this, std::ref(a));
	th.detach();
}

// Called every frame
void ATaskTest::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

std::mutex mtx;
void ATaskTest::ThreadDo(int& value) {
	while (value < 10000) {
		mtx.lock();
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
		UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
		value++;
		mtx.unlock();
	}
	UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
}


// Fill out your copyright notice in the Description page of Project Settings.


#include "TaskTest.h"
#include "chrono"
#include "Kismet/KismetSystemLibrary.h"
#include "mutex"
#include "thread"

// Sets default values
ATaskTest::ATaskTest()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

void ATaskTest::BeginPlay()
{
	Super::BeginPlay();
	std::thread th(&ATaskTest::ThreadDo, this, std::ref(a));
	std::thread th1(&ATaskTest::ThreadDo1, this, std::ref(a));
	th.detach();
	th1.detach();
}

std::mutex mtx;
void ATaskTest::ThreadDo(int& value) {
	while (value < 10000) {
		std::lock_guard<std::mutex> guardLock(mtx);
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
		UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
		value++;
	}
	UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
}

void ATaskTest::ThreadDo1(int& value) {
	while (value < 10000) {
		std::lock_guard<std::mutex> guardLock(mtx);
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
		UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
		value--;
	}
	UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
}

Will increase and decrease


// Fill out your copyright notice in the Description page of Project Settings.


#include "TaskTest.h"
#include "chrono"
#include "Kismet/KismetSystemLibrary.h"
#include "mutex"
#include "thread"

// Sets default values
ATaskTest::ATaskTest()
{
 	// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
	PrimaryActorTick.bCanEverTick = true;

}

void ATaskTest::BeginPlay()
{
	Super::BeginPlay();
	std::thread th(&ATaskTest::ThreadDo, this, std::ref(a));
	std::thread th1(&ATaskTest::ThreadDo1, this, std::ref(a));
	th.detach();
	th1.detach();
}

std::mutex mtx;
void ATaskTest::ThreadDo(int& value) {
	while (value < 10000) {
		std::unique_lock<std::mutex> uniLock(mtx, std::defer_lock);
		uniLock.lock();
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
		UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
		value++;
	}
	UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
}

void ATaskTest::ThreadDo1(int& value) {
	while (value < 10000) {
		std::lock_guard<std::mutex> guardLock(mtx);
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
		UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
		value--;
	}
	UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
}

Unique_lock is the same as lock_guard. It will be automatically unlocked after the function ends, but unique_lock has more operations, such as delayed locking, takeover lock, and wait lock (wait does not accept lock_guard). If these operations are not needed, lock_guard is better. The more operations, the better. The higher the consumption


std::mutex mtx;
void ATaskTest::ThreadDo(int& value) {
	while (value < 10000) {
		mtx.lock();
		std::unique_lock<std::mutex> uniLock(mtx, std::adopt_lock);
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
		UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
		value++;
	}
	UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
}

void ATaskTest::ThreadDo1(int& value) {
	while (value < 10000) {
		std::lock_guard<std::mutex> guardLock(mtx);
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
		UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
		value--;
	}
	UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
}


std::mutex mtx;
std::condition_variable cv;
bool isFirst = false;
void ATaskTest::ThreadDo(int& value) {
	while (value < 10000) {
		mtx.lock();
		std::unique_lock<std::mutex> uniLock(mtx, std::adopt_lock);
		cv.wait(uniLock, [=] {return !isFirst; });
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
		UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
		value++;
		isFirst = true;
		cv.notify_all();
	}
	UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
}

void ATaskTest::ThreadDo1(int& value) {
	while (value < 10000) {
		std::unique_lock<std::mutex> guardLock(mtx);
		std::this_thread::sleep_for(std::chrono::milliseconds(10));
		cv.wait(guardLock, [=] {return isFirst; });
		UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
		value--;
		isFirst = false;
		cv.notify_all();
	}
	UKismetSystemLibrary::PrintString(this, FString::FromInt(value));
}

Execute one at a time within while, use std::condition_variable


std::atomic_int a(1);
void ATaskTest::ThreadDo() {
    while (a < 10000) {
        a++;
        //UKismetSystemLibrary::PrintString(this, FString::FromInt(a));
    }
}

void ATaskTest::ThreadDo1() {
    while (a < 10000) {
        a--;
        //UKismetSystemLibrary::PrintString(this, FString::FromInt(a));
    }
}

I haven't found a way to print atomic_int yet, but I can edit breakpoints and have corresponding effects.

Guess you like

Origin blog.csdn.net/qqQQqsadfj/article/details/132781804