1. 概念
- 进程:一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。
- 线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
- 并发:并发指的是两个或多个独立的活动在同一时段内发生。并发在生活中随处可见:比如在跑步的时候同时听音乐,在看电脑显示器的同时敲击键盘等。同一时间段内可以交替处理多个操作,强调同一时段内交替发生。
- 并行:同一时刻内同时处理多个操作,强调同一时刻点同时发生。
2. 常用API
头文件#include<thread>
1.thread
| API | 描述 | 注意 |
|---|
| thread.join() | 加入线程(会阻塞主线程,模拟同步操作) |
| thread.detach() | 加入线程(不会阻塞主线程,模拟异步操作) |
| thread.joinable() | 是否可加入线程,返回bool |
| thread.get_id() | 获取线程的ID |
| thread.hardware_concurrency() | 获取硬件并发的数量 |
| thread.swap() | 交换线程 |
| thread.native_handle() | 获取原生handle,为windows多线程中CreateThread的返回值,使用这个handle从而可以实现线程的挂起唤醒 |
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | voidthreadFunc01() {
cout <<"thread join1"<< endl;
this_thread::sleep_for(chrono::seconds(2));
}
voidthreadFunc02() {
cout <<"thread join2"<< endl;
this_thread::sleep_for(chrono::seconds(2));
}
voidtest01() {
// 创建线程
std::threadthread1(threadFunc01);
std::threadthread2(threadFunc02);
//thread.join(); //join 会阻塞主线程 同步操作
//thread.detach(); //detach 不会阻塞主线程 异步操作
boolbJoinAble = thread1.joinable();
thread::id threadId = thread1.get_id();
//hardware_concurrency 硬件并发的数量
intthreadNum = thread1.hardware_concurrency();
cout <<"hardware_concurrency:"<< threadNum << endl;
//应用 线程的预分配。
for(inti = 0; i < thread1.hardware_concurrency(); i++) {
std::threadthreadRef(threadFunc01);
threadRef.detach();
}
thread1.swap(thread2);
thread1.join();
}
|
向线程里传递参数的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | // 向线程里传递参数的方法
#include<string>
voidthreadFunc03(intnum,conststring& str) {
cout <<"num = "<< num <<" str = "<< str << endl;
}
structFObject {
voidRun(conststring& str) {
cout << str << endl;
}
};
voidtest02() {
// 通过函数绑定
threadnewThread1(threadFunc03, 10,"Unreal");
newThread1.detach();
// 通过lambda绑定
inta = 50;
threadnewThread2([&](intnum,conststring& str) {
cout <<"a = "<< a <<" num = "<< num <<" str = "<< str << endl;
}, 1,"Unreal");
newThread2.detach();
// 绑定对象
FObject objectRef;
threadnewThread3(&FObject::Run, objectRef,"Unreal");
newThread3.detach();
}
|
2.互斥锁mutex
头文件#include<mutex>
| API | 描述 | 注意 |
|---|
| mutex.lock() | 上锁 |
| mutex.unlock() | 解锁 |
| mutex.try_lock() | 判断可不可以加锁,返回bool | 可以用该方法建立非阻塞模式 |
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include<mutex>
mutex lockRef;
voidthreadFunc04(intnum,conststring& str) {
// 进入该线程锁住该线程,其他线程想要进入该线程需要排队
lockRef.lock();
cout <<"thread join4"<< endl;
this_thread::sleep_for(chrono::seconds(2));
// 解锁
lockRef.unlock();
}
voidtest03() {
std::threadthread1(threadFunc04, 10,"Unreal");
std::threadthread2(threadFunc04, 5,"Unity");
std::threadthread3(threadFunc04, 20,"Cocos");
thread1.detach();
thread2.detach();
thread3.detach();
}
|
使用类加锁的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #include<mutex>
mutex lockRef;
structFEvent {
FEvent() {
m.lock();
}
~FEvent()
{
m.unlock();
}
staticmutex m;
};
mutex FEvent::m;
#define LOCK_SCOPE FEvent Event
voidthreadFunc04(intnum,conststring& str) {
LOCK_SCOPE;//加上锁,并且过了这个作用域自动解锁(析构)
cout <<"thread join4"<< endl;
this_thread::sleep_for(chrono::seconds(2));
}
voidtest03() {
std::threadthread1(threadFunc04, 10,"Unreal");
std::threadthread2(threadFunc04, 5,"Unity");
std::threadthread3(threadFunc04, 20,"Cocos");
thread1.detach();
thread2.detach();
thread3.detach();
}
|
try_lock()
1 2 3 4 5 6 7 8 | voidthreadFunc04(intnum,conststring& str) {
boolbLock = FEvent::m.try_lock();
if(bLock) {
LOCK_SCOPE;//加上锁,并且过了这个作用域自动解锁(析构)
cout <<"thread join4"<< endl;
this_thread::sleep_for(chrono::seconds(2));
}
}
|
使用try_lock()可以进行判断能不能上锁,不能上锁的话,就不用执行上锁后的代码,防止其他线程阻塞在该线程。
lock_guard
lock_guard是一种锁类,作用和我们上面自定义的锁类FEvent相同,创建的时候锁住目标线程,释放的时候解锁。
1 2 | // 声明方式
lock_guard<mutex>ref;
|
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | template<class_Mutex>
classlock_guard {// class with destructor that unlocks a mutex
public:
usingmutex_type = _Mutex;
explicitlock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) {// construct and lock
_MyMutex.lock();
}
lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) {// construct but don't lock
}
~lock_guard() noexcept {
_MyMutex.unlock();
}
lock_guard(constlock_guard&) =delete;
lock_guard& operator=(constlock_guard&) =delete;
private:
_Mutex& _MyMutex;
};
|
unique_lock
作用和lock_guard相同,唯一的不同之处,lock_guard开放的API只有析构函数,而unique_lock开放的API非常多,即自由度比lock_guard高,可以定义锁的行为。
1 2 3 4 5 6 7 8 9 10 11 12 | voidtest05() {
// defer_lock 关键字为延迟锁,即创建该对象时不会锁住该线程,什么时候锁需要自定义
std::unique_lock<mutex>lockRef2(FEvent::m,defer_lock);
std::unique_lock<mutex>lockRef2(FEvent::m,chrono::seconds(2));//锁两秒
//....执行
lockRef2.lock();
lockRef2.unlock();
boolbLock1 = lockRef2.try_lock();//尝试上锁
lockRef2.try_lock_for(chrono::seconds(2));//锁2s
mutex *lockRef3 = lockRef2.release();//释放锁,同时会返回被释放的这个锁的指针对象
boolbLock2 = lockRef2.owns_lock();//当前是否被锁住
}
|
应用:
1 2 3 4 5 6 7 8 9 10 11 12 | voidtest05() {
//std::lock_guard<mutex>lockRef1(FEvent::m);
// defer_lock 关键字为延迟锁
std::unique_lock<mutex>lockRef2(FEvent::m,defer_lock);
lockRef2.lock();
lockRef2.mutex();
boolbLock = lockRef2.owns_lock();
std::unique_lock<mutex>lockRef3;
lockRef2.swap(lockRef3);
std::unique_lock<mutex>lockRef4 = move(lockRef3);
lockRef4.unlock();
}
|
3. 挂起和唤醒
头文件#include<windows.h>
1111111
111111
111111
11111
111111
测试代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include<windows.h>
voidthreadFunc05() {
while(true)
{
Sleep(10);
cout <<"threadFunc05"<< endl;
}
}
voidtest04() {
threadthread1(threadFunc05);
// 挂起线程
SuspendThread(thread1.native_handle());
Sleep(2);
// 唤醒线程
ResumeThread(thread1.native_handle());
}
|
如何高效将主线程资源进行转移:
1 2 3 4 5 6 7 8 9 10 | voidthreadFunc06(constchar* str) {
cout << str << endl;
}
voidtest04() {
// 如何高效转移线程资源
// 使用std::move
threadthread2(threadFunc06, move("Unreal"));// 使用move避免了拷贝
threadthread3 = move(thread2);
thread3.detach();
}
|
3. 应用场景
3.1 call_once执行一次的函数
通过使用该函数,用来防止多线程的多次触发。
1 2 3 4 5 6 7 8 9 10 11 12 | once_flag tag;
voidcallonceTest() {
call_once(tag, [&]() {
cout <<"Do once"<< endl;
});
}
voidtest06() {
for(inti = 0; i < 10; i++) {
threadthread1(callonceTest);
thread1.detach();
}
}
|
3.2 condition_variable条件锁
使用需要包含头文件#include<condition_variable>
可以使用条件锁来达到同步的作用,即当满足一定的条件后才解锁某个线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include<condition_variable>
condition_variable condition_lock;
mutex mutexLock;
voidconditionFuncTest() {
unique_lock<mutex>lock(mutexLock);
condition_lock.wait(lock);//锁住该线程
cout <<"Run"<< endl;
}
voidtest12() {
std::threadthreadRef(conditionFuncTest);
threadRef.detach();
Sleep(3000);//3s后再激活
condition_lock.notify_one();
}
|
3.3 future获取线程的计算结果
通过使用future可以得到"未来"线程被调用的时候计算得返回值,使用时需要包含头文件#include<future>。
声明方式:
1 2 | // async为创建该线程的方式为异步 funName 函数名 args为传入的函数参数
std::future<string>newFuture = std::async(launch::async, funName,args...);
|
应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include<future>
string getString(intnum) {
return"Unreal";
}
voidtest08() {
std::future<string>newFuture = std::async(launch::async, getString, 10);
//std::future<string>newFuture = std::async(launch::deferred, getString, 10); // 睡一秒再执行
Sleep(1000);
string str = newFuture.get();//get只能调用一次 调第二次会崩溃
// 防止崩溃的写法
if(newFuture.valid()) {
string str = newFuture.get();
}
}
|
3.4 promise主线程如何将数据发送数据到其他线程
通过使用promise(承诺)来进行进程之间的交互,常配合std::future使用。其作用是在一个线程t1中保存一个类型typename T的值,可供相绑定的std::future对象在另一线程t2中获取。
测试代码:
1 2 3 4 5 6 7 8 9 10 11 | // promise
string promiseTest(future<string>& future) {
cout << future.get() << endl;
return"Unreal";
}
voidtest09() {
promise<string> promiseRef;
future<string>future1 = promiseRef.get_future();
future<string>future2 = std::async(launch::async, promiseTest, std::ref(future1));//future 不支持值拷贝 需要传递引用
promiseRef.set_value("Unreal is the best game engine in the world");
}
|
但这里也有一个问题需要思考,如果需要发送数据到多个线程,是不是需要一个个的创建上面的代码呢。这里就引出了多线程之间共享状态这个解决方法。
3.5 future.share()多线程之间共享状态
通过future.share()我们可以很方便的使多个线程之间共享状态。
现在来看看没有使用该函数的话我们要共享状态的话需要这么写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | string promiseTest(future<string>& future) {
cout << future.get() << endl;
return"Unreal";
}
voidtest09() {
promise<string> promiseRef;
future<string>future1 = promiseRef.get_future();
future<string>future2 = promiseRef.get_future();
future<string>future3 = promiseRef.get_future();
future<string>future4 = std::async(launch::async, promiseTest, std::ref(future1));//future 不支持值拷贝 需要传递引用
future<string>future5 = std::async(launch::async, promiseTest, std::ref(future2));//future 不支持值拷贝 需要传递引用
future<string>future6 = std::async(launch::async, promiseTest, std::ref(future3));//future 不支持值拷贝 需要传递引用
promiseRef.set_value("Unreal is the best game engine in the world");
}
|
使用了future.share()函数后:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | string promiseTest02(shared_future<string> future) {
cout << future.get() << endl;
return"Unreal";
}
voidtest09() {
promise<string> promiseRef;
future<string>future1 = promiseRef.get_future();
// shared_future
shared_future<string> sharedFutrue1 = future1.share();
future<string>future2 = std::async(launch::async, promiseTest02, sharedFutrue1);//shared_future 可以用拷贝传递
future<string>future3 = std::async(launch::async, promiseTest02, sharedFutrue1);
future<string>future4 = std::async(launch::async, promiseTest02, sharedFutrue1);
promiseRef.set_value("Unreal is the best game engine in the world");
}
|
3.6 线程packaged_task
packaged_task和promise非常相似,packaged_task<F>是对promise<T= std::function<F>>中T= std::function<F>这一可调对象(如函数、lambda表达式等)进行了包装,简化了使用方法。并将这一可调对象的返回结果传递给关联的future对象。
绑定Lambda
1 2 3 4 5 6 7 8 9 10 11 12 | voidtest10() {
//绑定lambda
packaged_task<int(int,int)> task1([](inta,intb) ->int{
returna + b;
});
task1(1, 4);
this_thread::sleep_for(chrono::seconds(1));
if(task1.valid()) {
auto f1 = task1.get_future();
cout << f1.get() << endl;
}
}
|
绑定普通函数
1 2 3 4 5 6 7 8 9 10 11 12 13 | intpackagedTest(inta,intb) {
returna + b;
}
voidtest10() {
//绑定函数
packaged_task<int(int,int)>task2(packagedTest);
task2(10, 5);
this_thread::sleep_for(chrono::seconds(1));
if(task2.valid()) {
auto f2 = task2.get_future();
cout << f2.get() << endl;
}
}
|
使用std::bind进行函数绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 | intpackagedTest(inta,intb) {
returna + b;
}
voidtest10() {
// bind
packaged_task<int(int,int)>task3(std::bind(packagedTest,1,2));
task3(10, 5);//因为bind使用了占位符 所以这里传入的10 5失效了
this_thread::sleep_for(chrono::seconds(1));
if(task3.valid()) {
auto f3 = task3.get_future();
cout << f3.get() << endl;//1+2
}
}
|
3.7 时间约束
1 2 3 4 5 6 7 | voidtest11() {
//休眠2s
this_thread::sleep_for(chrono::seconds(2));
// 休眠现在的时间加上2s
chrono::steady_clock::time_point timePos = chrono::steady_clock::now() + chrono::seconds(2);
this_thread::sleep_until(timePos);
}
|
4. Windows多线程
使用WindowsAPI进行多线程的编写,需要包含头文件
4.1 Windows创建线程
使用CreateThread()创建线程
1 2 3 4 5 6 7 8 9 10 11 12 | DWORDWINAPI funcThread(LPVOIDlpPram) {
// DWORD 类型为unsigned long
// LPVOID 类型为void
cout <<"Unreal!"<< endl;
Sleep(1000);
return0l;
}
voidwindowsThreadTest01() {
HANDLEhandleRef = CreateThread(nullptr,0, funcThread,nullptr,0,nullptr);
Sleep(2000);
CloseHandle(handleRef);//使用之后需要关闭handle
}
|
其中传入的参数为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /*
WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateThread(
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, 和线程安全有关 一般为null
_In_ SIZE_T dwStackSize, 线程栈的大小
_In_ LPTHREAD_START_ROUTINE lpStartAddress, 被线程执行的回调函数
_In_opt_ __drv_aliasesMem LPVOID lpParameter, 传入线程的参数
_In_ DWORD dwCreationFlags, 创建线程的标志 参数0 代表立即启动该线程
_Out_opt_ LPDWORD lpThreadId 传出的线程ID
);
*/
|
4.2 Windows互斥锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // windows互斥锁
HANDLEhMutex = nullptr;
DWORDWINAPI funcThread02(LPVOIDlpParam) {
cout <<"Unreal"<< endl;
WaitForSingleObject(hMutex, INFINITE);
Sleep(5000);
ReleaseMutex(hMutex);
return0l;
}
voidwindowsThreadTest02() {
hMutex = CreateMutex(nullptr,false, L"Mutex");
HANDLEhandleRef1 = CreateThread(nullptr, 0, funcThread02, nullptr, 0, nullptr);
HANDLEhandleRef2 = CreateThread(nullptr, 0, funcThread02, nullptr, 0, nullptr);
CloseHandle(handleRef1);
CloseHandle(handleRef2);
}
|
传入的参数为:
1 2 3 4 5 6 7 8 9 10 11 | /*
WINBASEAPI
_Ret_maybenull_
HANDLE
WINAPI
CreateMutexW(
_In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, 和线程安全有关一般为null
_In_ BOOL bInitialOwner, 有没有该锁的控制权
_In_opt_ LPCWSTR lpName 锁名字
);
*/
|
4.3 Windows挂起和唤醒线程
通过使用SuspendThread(HandleRef)和ResumeThread(HandleRef)来挂起和唤醒线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // windows 挂起唤醒
DWORDWINAPI funcThread03(LPVOIDlpParam) {
while(true) {
Sleep(500);
cout <<"IsRunning"<< endl;
}
return0l;
}
voidwindowsThreadTest03() {
HANDLEhRef = CreateThread(nullptr, 0, funcThread03, nullptr, 0, nullptr);
SuspendThread(hRef);
Sleep(2000);
ResumeThread(hRef);
CloseHandle(hRef);
}
|
总结
本篇文章就到这里了,希望能够给你带来帮助