c++多线程并发

什么是多线程?

在知道多线程之前,需要了解下面的概念。

什么是进程?

进程是指在系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。

什么是线程?

进程要想执行任务,必须得有线程(每1个进程至少要有1条线程),线程是进程的基本执行单元,一个进程(程序)的所有任务都在线程中执行。

进程就好比一家工厂,线程就好比生产线,单线程是有一条生产线,多线程是有多条生产线

线程的特点

1.线程内核对象

操作系统对进程和线程的管理都是通过相应的内核对象实现的。它会为每个线程都分配并初始化这种数据结构。在该数据结构中,包含一组对线程进行描述的属性。数据结构中还包含所谓的线程上下文。上下文是一个内存块,其中包含CPU的寄存器集合。

2.线程控制块

线程的实体包括程序、数据和线程控制块TCB ( Thread Control Block)线程是动态概念, TCB包括以下信息:

  1. 线程状态。
  2. 当线程不运行时,被保存的现场资源。
  3. 一组执行堆栈。
  4. 存放每个线程的局部变量主存区。
  5. 访问同一个进程中的主存和其它资源。

3.独立调度和分派的基本单位

线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。CPU是以线程为单位执行,创建出来的线程不一定马上执行,调度后允许线程运行一个“时间片",单核处理器一个时间片只执行一个线程,而多核处理器可允许一个进程中所有线程都能并发执行。

4.上下文切换

线程是由CPU进行调度的,CPU的一个时间片内只执行一个线程上下文内的线程,当CPU由执行线程A切换到执行线程B的过程中会发生一些列的操作,如"保存线程A的执行现场"然后"载入线程B的执行现场",这个过程称之为"上下文切换(context switch)",这个上下文切换过程会消耗资源,应该尽量减少上下文切换的发生。(时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停的切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是十几毫秒(ms))
引起上下文切换的原因
1.时间片用完,CPU正常调度下一个任务
2.被其他优先级更高的任务抢占
3执行任务碰到IO阻塞,调度器挂起当前任务,切换执行下一个任务
4.用户代码主动挂起当前任务让出CPU时间
5.多任务抢占资源,由于没有抢到被挂起
6.硬件中断

5.共享进程资源

在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在: 所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址; 此外还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。

线程的状态


就绪状态表示线程已准备好了,如果CPU调度了当前线程,这个线程就可以被运行,那么就进入到了运行状态,如果线程要进行一些IO操作,读写文件,就会停止当前线程,此线程会阻塞,进入阻塞状态,在线程的运行过程中,sleep,也会进入阻塞状态,如果线程执行完毕,或强制退出,或遇到异常,就会进入死亡状态。处于阻塞状态的进程,如果sleep完成或者IO操作完成或同步锁被激活,就会进入就绪状态等待CPU的调度。如果一个线程别其他线程抢占之后,这个线程就会回到就绪状态。

多线程API

windows下多线程相关的API接口函数

Part One

头文件
#include<Windows.h>
CreateThread创建线程
HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE IpStartAddress, LPVOID IpParameter DWORD dwCreationFlags, LPDWORD IpThreadId );
参数
IpThreadAttributes: 指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows NT中,设为NULL使用默认安全性。
dwStackSize: 设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。
IpStartAddress: 指向线程函数的指针,函数名称没有限制,但是必须以下列形式声明: DWORD WINAPI ThreadProc (LPVOID IpParam)
IpParameter:向线程函数传递的参数,是个void*的指针,不需传递参数时,为NULL。
dwCreationFlags : 线程标志,可取值如下
( 1 )CREATE SUSPENDED: 创建一个挂起的线程,它无法运行直到调用ResumeThread()。
( 2 )0: 表示创建后立即激活
( 3 )STACK_SIZE PARAM_IS_A_RESERVATION: 未指定此标记,使用dwStackSize指定提交的大小。
IpThreadld: 保存新线程ID,若不想返回线程ID,设置值为NULL。
返回值 :成功返回线程句柄,失败返回NULL ,调用 GetLastError()获知失败原因。

CreateThread是Windows API中在主线程的基础,上创建-一个 新线程。创建成功之后会返回一个hThread的handle,内核对象的计数加1,CloseHandle之后,引用计数减1,当变为0时,系统删除内核对象。handle仅仅是线程的一个“标识”。

GetCurrentThreadid获取线程ID
DWORD WINAPI GetCurrentThreadld( VOID );
返回获取当前的线程ID。
CloseHandle关闭线程句柄
BOOL CloseHandle( HANDLE hObject );
只是关闭了一个线程句柄,引用计数减1,表示我不对这个句柄对应的线程做任何干预(诸如waitforsingleobject之类), 但并没有结束线程。成功返回TRUE, 失败返回FALSE,调用GetLastError()获知失败原因。
SuspendThread挂起指定的线程
DWORD WINAPI SuspendThread(HANDLE hThread);
hThead:需要挂起的句柄。
ResumeThread恢复被挂起的线程的执行。
DWORD WINAPI ResumeThread(HANDLE hThread);
hThead:需要挂起的句柄。
Sleep休眠线程的执行
VOID WINAPI Sleep(DWORD dwMilliseconds);
dwMilliseconds 毫秒数

相关代码(可以研究试一下)

#include <iostream>
#include <windows.h>
using namespace std;
//线程处理函数,windows标准调用约定_stdcall,函数名就是函数指针

DWORD  WINAPI ThreadFun(LPVOID lpThreadParameter);
int main()
{
    DWORD threadid = 0;
    HANDLE hThread = CreateThread(NULL, 0, ThreadFun, (void*)"hello", 0, &threadid);
    
    if (hThread == NULL)
    {
        cout << "线程创建失败" << GetLastError() << endl;
    }
    cout << "线程的句柄: " << hThread << endl;
    cout << "子线程的id: " << threadid << endl;
    cout << "主线程ID: " << GetCurrentThreadId() << endl;
    
    //关闭线程句柄,引用计数-1,并没有结束线程
    //CloseHandle(hThread);//表示以后不在引用句柄,有这句话,就没有SuspendThread(hThread)了

    getchar();//main是主线程,主线程结束,子线程还没 得及循坏,也跟着结束了,所以用getchar()来迫使主线程不关闭
    
    //挂起线程
    SuspendThread(hThread);

    getchar();

    //恢复线程
    ResumeThread(hThread);

    getchar();
    return 0;
}
DWORD  WINAPI ThreadFun(LPVOID lpThreadParameter)
{
    char* str = (char*)lpThreadParameter;
    while (1)
    {
        cout << "线程处理函数中:" << str << endl;
        cout << "子线程 ID: " << GetCurrentThreadId() << endl;
        Sleep(1000);//休眠1000毫秒
    }
    return 0;
}

输出结果不尽相同,因为主线程和子线程并发进行。

Part Two

WaitForSingleObject等待一个内核对象变为已通知状态
DWORD WaitForSingleObject( HANDLE hObject, DWORD dwMilliseconds );
hObject :指明一个内核对象的句柄
dwMilliseconds :等待时间

该函数需要传递一个内核对象句柄,如果该内核对象处于未通知状态,则该函数导致线程进入阻塞状态;如果该内核对象处于已通知状态,则该函数立即返回WAIT_OBJECT_0。第二个参数指明要等待的时间(毫秒),INFINITE表示无限等待,如果第二个参数为0, 那么函数立即返回。如果等待超时,该函数返WAIT_TIMEOUT。如果该函数失败,返回WAIT_FAILED。
未通知状态是线程的句柄所关联的线程还没有退出,线程函数还没有结束完,WaitForSingleObject一直阻塞在这里,不让程序继续向下运行。
如果线程函数结束,线程句柄处于已通知状态。程序继续向下运行

相关代码

#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI ThreadFun (LPVOID lpThreadParameter);
int main()
{
    cout<<"main thread start"<<endl;
    //创建一个线程
    HANDLE hThread = CreateThread(NULL,0,ThreadFun,NULL,0,NULL);
    //无限等待,除非子线程运行结束
    WaitForSingleObject(hThread,INFINITE);
    cout<<"main Thread end !"<<endl;
    return 0;
}
DWORD WINAPI ThreadFun (LPVOID lpThreadParameter)
{
    int n=0;
    while (n<6)
    {
        n++;
        cout<<"son of thread say Hello!"<<n<<endl;
        Sleep(1000);
    }
    return 0;
}
//子线程和主线程并行运行的

终止线程
终止线程运行的最佳方法是让它的线程函数return返回。除此之外,终止线程有两个函数:

ExitThread()避免使用
TerminateThread()必须避免

ExitThread()
VOID ExitThread ( DWORD dwExitCode );
可以让线程调用ExitThread函数,以便强制线程终止运行: 如果使用这两种方法退出线程,则不会执行线程函数的return语包,所以就不会调用线程,函数作用域内申请的类对象的析构函数,会造成内存泄露。

TerminateThread()在线程外终止一个线程,用于强制终止线程
BOOL Terminate Thread( HANDLE hThread, DWORD dwExitCode );
hThread :被终止的线程的句柄
dwExitCode :退出码
返回值,成功返回非零,失败返回0
调用GetL astError()获知原因。

Terminate Thread能够撤消任何线程,hThread参数用于标识被终止运行的线程的句柄。dwExitCode参数就是线程终止的退出码。同时,线程的内核对象的使用计数也被递减。注意Terminate Thread函数是异步运行的函数,当函数返回时,并不能保证线程被撤消。如果需要确切地知道该线程已经终止运行,使用WaitForSingleObject或者类似的函数,windows核心编程:一个设计良好的应用程序决不会使用这个函数,因为被终止运行的线程收不到它被“杀死"的通知,导致线程不能正确地清除。

GetExitCodeThread获取线程结束码

BOOL GetExitCodeThread(
HANDLE hThread,
LPDWORD lpExitCode 
);

hThread : 由Create' Thread()传回的线程handle
lpExitCode : 指向一个DWORD, 用于接收结束代码(exitcode)
返回值:
成功返回TRUE,失败返回FALSE,可以调用GetLastError()找出原因。如果线程G结束,那么线程的结束代码会被放在lpExitCode参数中带回来。如果线程尚未结束,IpExitCode带回来的值是STILL_ACTIVE。在调用GetExitCodeThread()之前,要注意不要调用CloseHandle关闭掉线程句柄GetExitCodeThread()可以在调用WaitForSingleObject()等待线程结束之后调用。

相关代码

#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI ThreadFun (LPVOID lpThreadParameter);
int main()
{
    cout<<"main thread start"<<endl;
    //创建一个线程
    HANDLE hThread = CreateThread(NULL,0,ThreadFun,NULL,0,NULL);
    DWORD code;
    GetExitCodeThread(hThread,&code);
    if (code == STILL_ACTIVE)//如果线程没有结束,返回STILL_ACTIVE
    {
        cout<<"Son of thread isn't end"<<endl;
    }
    //无限等待,除非子线程运行结束
    WaitForSingleObject(hThread,INFINITE);
    GetExitCodeThread(hThread,&code);
    cout<<"Exit code : "<<code<<endl;
    cout<<"main Thread end !"<<endl;
    return 0;
}
DWORD WINAPI ThreadFun (LPVOID lpThreadParameter)
{
    int n=0;
    while (n<6)
    {
        n++;
        cout<<"son of thread say Hello!"<<n<<endl;
        Sleep(1000);
        if(n == 3)
        {
            //退出线程,退出码为886
            ExitThread(886);
        }
    }
    return 0;
}
//子线程和主线程并行运行的

WaitForMultipleObjects等待多个内核对象变为已通知状态

DWORDb WaitForMultipleObjects(
DWORD dwCount, //等待的内核对象个数
CONST HANDLE* phObjects, //一个存放被等待的内核对象句柄的数组
BOOL bWaitAll, //是否等到所有内核对象为已通知状态后才返回
DWORD dwMilliseconds); //等待时间

返回值:
该函数失败, 返回WAIT FAILED ;如果超时,返回WAI下TIMEOUT ;如果bWaitAll参数为TRUE,函数成功则返回WAIT_OBJECT_0
如果bWaitAll为FALSE,函数成功则返回数组的索引|指明是哪个内核对象收到通知。

该函数的第一个参数指明等待的内核对象的个数,可以是0到MAXIMUM_WAIT_OBJECTS( 64 )中的一个值。phObjects参数是一个存放等待的内核对象包柄的数组。bWaitAll参数如果为TRUE,则只有当等待的所有内核对象为已通知状态时函数才返回,如果为FALSE,则只要一个内核对象为已通知状态,则该函数回,dwMilliseconds参数为等待的时间(毫秒)

相关代码

#include <windows.h>
#include <iostream>
using namespace std;
DWORD WINAPI ThreadFun (LPVOID lpThreadParameter);
int main()
{
    cout<<"main thread start"<<endl;
    //创建线程
    HANDLE hThread_1 = CreateThread(NULL,0,ThreadFun,(LPVOID)"A",0,NULL);
    HANDLE hThread_2 = CreateThread(NULL,0,ThreadFun,(LPVOID)"B",0,NULL);
    HANDLE hThread_3 = CreateThread(NULL,0,ThreadFun,(LPVOID)"C",0,NULL);

    HANDLE handleArr[] = {hThread_1, hThread_2, hThread_3};

    //无限等待所有线程结束
    //DWORD ret = WaitForMultipleObjects(3,handleArr,true,INFINITE);
    //cout<<"ret = "<<ret<<endl;

    //等待任意一个线程结束就返回
    //DWORD ret = WaitForMultipleObjects(3,handleArr, false,INFINITE);
    //cout<<"ret is SuoYin of thread : "<<ret<<endl;

    //设置等待超时时间1秒
    DWORD ret = WaitForMultipleObjects(3,handleArr,false,1000);
    cout<<"WAIT_TIMEOUT = "<<WAIT_TIMEOUT<<endl;
    cout<<"main thread end"<<endl;
    return 0;
}
DWORD WINAPI ThreadFun (LPVOID lpThreadParameter)
{
    char *name = (char*) lpThreadParameter;
    if (strcmp(name,"A") == 0)
    {
        Sleep(10000);
    }
    else if(strcmp(name,"B")==0)
    {
        Sleep(3000);
    } else{
        Sleep(8000);
    }
    cout<<"son of thread end : "<<name<<endl;

    return 0;
}

Part Three

《windows核心编程》:

CreateThread函数是用来创建线程的Windows函数。不过,如果你正在编写 C/C ++代码,决不应该调用CreateThread.相反,应该使用Visual C+ +运行期库函数_beginthread
在CreateThread API创建的线程中使用sprintf,malloc,strcat等涉及CRT存储堆操作的CRT库函数是很危险的容易造成线程的意外中止。而使用_beginthread_beginthreadex创建的线程中可以安全的使用CRT函数,但是必须在线程结束的时候相应的调用_endthread_endthreadex.
如果你在线程函数中进行以下操作,你就应该使用_beginthread_endthread:
(1)使用malloc()和free(),或是new和delete
(2)使用stdio.h或io.h里面声明的任何函数
(3)使用浮点变量或浮点运算函数
(4)调用任何一个使用了静态缓冲区的runtime函数,比如:asctime(),strtok()或rand()
头文件
#include <process.h>
_beginthread创建线程

uintptr_t _beginthread(
void(*start_address)(void*),
unsigned stack_size,
void *arglist
);

参数:
start address: 线程函数的入地址。对于_beginthread,线程函数的调用约定是 cdecl
stack size :堆栈大小,设置0为系统默认值
arglist :传递给线程的参数列表,无参数时为NULL

返回值:
成功返回新创建的线程的句柄,需reinterpret_cast 强制转换,但如果新创建的线程退出的速度太快,可能无法返回一个有效句柄.失败返回- 1 ,比如资源不足或者堆栈大小不正确errno设置为EINVAL。

相关代码

#include <process.h>
#include <iostream>
#include <windows.h>
using namespace std;
void ThreadFun (void *);
int main()
{
    cout<<"Main thread start"<<endl;
    HANDLE hThread = (HANDLE) _beginthread(ThreadFun,0,(HANDLE)"hello");
    WaitForSingleObject(hThread,INFINITE);
    cout<<"Main thread end"<<endl;
}
void ThreadFun (void *param)
{
    int n = 0;
    while (n<6)
    {
        n++;
        Sleep(1000);
        cout<<n<<" print"<<endl;
    } 
    cout<<"Son of thread end"<<endl;
}
#include <process.h>
#include <iostream>
#include <windows.h>
using namespace std;
void ThreadFun (void *);
int main()
{
    cout<<"Main thread start"<<endl;
    //推荐使用
    HANDLE hThread = (HANDLE) _beginthread(ThreadFun,0,(HANDLE)"hello");
    WaitForSingleObject(hThread,INFINITE);
    cout<<"Main thread end"<<endl;
}
void ThreadFun (void *param)
{
    char *p = (char *)param;
    int n = 0;
    while (n<6)
    {
        n++;
        Sleep(1000);
        cout<<n<<" print "<<p<<endl;
        if (n==3)
        {
            //结束_beginthread创建的线程,别用ExitThread
            _endthread();
        }
    } 
    cout<<"Son of thread end"<<endl;
}

多线程模拟火车站售票

相关代码

#include <Windows.h>
#include <process.h>
#include <iostream>
using namespace std;
int num = 100;
void __cdecl Thread1(void *);
void __cdecl Thread2(void *);
int main()
{
    cout<<"Begin sell tickets"<<endl;
    HANDLE t1 = (HANDLE) _beginthread(Thread1,0,(HANDLE)"Ticket counter A");
    HANDLE t2 = (HANDLE) _beginthread(Thread2,0,(HANDLE)"Ticket counter B");
    HANDLE T [] = {t1, t2};
    WaitForMultipleObjects(2,T,true,INFINITE);
    cout<<"Tickets sell out"<<endl;
}
void __cdecl Thread1(void *param)
{
    char *name = (char *)param;
    while (num > 0)
    {
        num--;
        Sleep(10);
        cout<<name<<" sell "<<"the "<<num<<" of tickets"<<endl;
    }
}
void __cdecl Thread2(void *param)
{
    char *name = (char *)param;
    while (num > 0)
    {
        num--;
        Sleep(10);
        cout<<name<<" sell "<<"the "<<num<<" of tickets"<<endl;
    }
}

思考:为什么窗口出现卖票产生为零的情况

因为当num=1时,CPU恰好执行到输出票的情况,这个时候线程时间片到了,就切换到另外一个线程,另外一个线程同样遇到这个问题,回来在第一个线程输出1票,之后在另外一个线程输出0票。

多线程的同步和互斥

异步互不打扰
同步接力赛
互斥独木桥
在WIN32中同步机制主要有以下几种:
( 1 )临界区(Critical Section)
( 2 )信号量(Semaphore)
( 3 )互斥量(Mutex)
( 4 )事件(Event)
用户模式下的方法有:
原子操作,临界区( Critical Section ),通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
内核模式下的方法有:
1、互斥量:为协调共同对一个共享资源的单独访问而设计。
2、信号量:为控制一个具有有限数量用户资源而设计。
3、事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。

多线程同步-临界区Critical_Section

每个线程中访问临界资源的那段程序称为临界区( Critical Section ) 每次只准许一个线程进入临界区,进入后不允许其他线程进入。不论是硬件临界资源,还是软件临界资源,多个线程必须互斥地对它进行访问。(公用电话亭就是一个临界区)
临界区结构对象
CRITICAL_SECTION Section;

临界区在使用时以CRITICAL SECTION结构对象保护共享资源,如果有多个线程试图同时访问临界区,那么在有个线程进入后、其他试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。

初始化临界区
InitializeCriticalSection( &Section );
//进入临界区
EnterCriticalSection( &Section );
//离开临界区
LeaveCriticalSection( &Section );
//尝试进入临界区
if (TryEnterCriticalSection(&g_ cs) )
//进入临界区
LeaveCriticalSection(&g_ cs);
//删除临界区
DeleteCriticalSection( &Section );

如果EnterCriticalSection的代码已经被占用,会使调用线程置于阻塞状态,那么该线程可能很长时间内不能被调度。而TryEnterCriticalSection函数决不允许调用线程进入等待状态。它的返回值能够指明调用线程是否能够获得对资源的访问权。TryEnterCriticalSection发现该资源已经被另一个线程访问,它就返回FALSE ,否则返回TRUE。运用这个函数,线程能够迅速查看它是否可以访问某个共享资源,如果不能访问,那么它可以继续执行某些其他操作,而不必进行等待。

相关代码

#include <Windows.h>
#include <process.h>
#include <iostream>
using namespace std;
int num = 100;
//1.临界区结构
CRITICAL_SECTION Section;
void __cdecl Thread1(void *);
void __cdecl Thread2(void *);
int main()
{
    //2.初始化临界区
    InitializeCriticalSection(&Section);
    cout<<"Begin sell tickets"<<endl;
    HANDLE t1 = (HANDLE) _beginthread(Thread1,0,(HANDLE)"Ticket counter A");
    HANDLE t2 = (HANDLE) _beginthread(Thread2,0,(HANDLE)"Ticket counter B");
    HANDLE T [] = {t1, t2};
    WaitForMultipleObjects(2,T,true,INFINITE);
    cout<<"Tickets sell out"<<endl;
    //5.删除临界区
    DeleteCriticalSection(&Section);
}
void __cdecl Thread1(void *param)
{
    char *name = (char *)param;


    while (num > 0)
    {
        //3.进入临界区,禁止其他线程访问
        EnterCriticalSection(&Section);
        if (num > 0)
        {
            cout << name << " sell " << "the " << num-- << " of tickets" << endl;
        }
        //4.离开临界区
        LeaveCriticalSection(&Section);
        Sleep(1);
    }
}
void __cdecl Thread2(void *param)
{
    char *name = (char *)param;
    while (num > 0)
    {
        //进入临界区,禁止其他线程访问
        //EnterCriticalSection(&Section);
        if (TryEnterCriticalSection(&Section)) 
        {
            if (num > 0) 
            {
                cout << name << " sell " << "the " << num-- << " of tickets" << endl;
            }
            //离开临界区资源
            LeaveCriticalSection(&Section);
            Sleep(1);
        }
    }
}

多线程同步-线程死锁

产生
多线程以及多进程改善了系统资源的利用率并提高了系统的处理能力。然而,并发执行也带来了新的问题——死锁。
死锁是指多个线程因竞争资源而造成的一种僵局(互相等待) ,若无外力作用,这些进程都将无法向前推进。
线程死锁产生的必要条件:
( 1 )互斥条件:一个资源每次只能被一个进程使用。
( 2 )请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
( 3 )不可剥夺条件:进程E获得的资源,在末使用完之前不能强行剥夺。
( 4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
银行家算法
避免死锁算法中最有代表性的算法是Dijkstra E.W于1968年提出的银行家算法,防止死锁的机构只能确保上述四个条件之不出现 ,则系统就不会发生死锁。
银行家算法是一种最有代表性的避免死锁的算法。通过这个算法可以用来解决生活中的实际问题,如银行贷款等。银行家在客户申请的贷款应先计算此借款的安全性若借款不会导致银行进入不安全状态,则借出,否则不予借出。银行家就好比操作系统,资金就是资源,客户就相当于要申请资源的进程。

相关代码

#include <iostream>
#include <process.h>
#include <Windows.h>
using namespace std;
CRITICAL_SECTION cs1,cs2;
void ThreadFun1 (void *param);
void ThreadFun2 (void *param);

int main()
{
    InitializeCriticalSection(&cs1);
    InitializeCriticalSection(&cs2);
    HANDLE t1 = (HANDLE) _beginthread(ThreadFun1,0,(LPVOID)"A");
    HANDLE t2 = (HANDLE) _beginthread(ThreadFun2,0,(LPVOID)"B");
    HANDLE T[] = {t1, t2};
    WaitForMultipleObjects(2,T,true,INFINITE);
}
void ThreadFun1 (void *param)
{
    char* name = (char*) param;
    //进入1区域之后,任何线程无法进入1区域
    EnterCriticalSection(&cs1);
    cout << name << " entry section ONE"<<endl;
    Sleep(3000);
    cout << name << " want entry section TWO" << endl;
    EnterCriticalSection(&cs2);
    LeaveCriticalSection(&cs2);
    LeaveCriticalSection(&cs1);
}
void ThreadFun2 (void *param)
{
    char* name = (char*) param;
    //进入2区域之后,任何线程无法进入2区域
    EnterCriticalSection(&cs2);
    cout << name << " entry section TWO"<<endl;
    Sleep(3000);
    cout << name << " want entry section ONE" << endl;
    EnterCriticalSection(&cs1);
    LeaveCriticalSection(&cs1);
    LeaveCriticalSection(&cs2);
}

多线程同步-信号量Semaphore

临界区( Critical Section )一次只允许一个线程访问资源,而信号量( Semaphore )允许N个线程在同一时刻访问同一资源
1.使用CreateSemaphore ( )创建信号量时要指出允许的最大资源计数和当前可用资源计数。
2.一般将当前可用资源计数设置为最大值, 每增加一个线程对资源的访问,可用资源计数减1,只要当前可用资源计数是大于0 ,就可发出信号。当可用计数减小到0则说明当前占用资源线程数E达所允许的最大值,不允许其他线程的进入,此时无法发出信号。
3.线程在处理完共享资源后,通过ReleaseSemaphore ( )函数将当前可用资源计数加1

信号量的场景和停车场的场景类似

CreateSemaphore创建-一个信号量

HANDLE WINAPI CreateSemaphore(
LPSECURITY_ATTRIBUTES IpSemaphoreAttributes
LONG IInitialCount,
LONG IMaximumCount,
LPCTSTR IpName
);

参数
IpSemaphoreAttributes : 安全属性,如果为NULL则默认
IInitialCount : 信号量的初始值,要>=0且<=第三个参数
IMaximumCount : 信号量的最大值
IpName : 表示信号 量的名称,传入NULL表示匿名信号量。
返回值:成功返回句柄,如果创建的信号量和已有的信号量重名,那么返回已经存在的信号量句柄,失败返回NULL
ReleaseSemaphore对指定的信号量增加指定的值

BOOL ReleaseSemaphore(
HANDLE hSemaphore,
LONG lReleaseCount,
LPLONG IpPreviousCount
);

参数
hSemaphore :信号量句柄
ReleaseCount :信号量对象在当前基础上所要增加的值
lpPreviousCount :向返回信号量上次值的变量的指针
返回值:成功返回TRUE失败返回FALSE

相关代码

#include <Windows.h>
#include <stdio.h>
//使用c++容易使文字夹杂在一起,不知道原因
HANDLE hSemaphore = INVALID_HANDLE_VALUE;
DWORD WINAPI ThreadFun1(LPVOID Parameter);
struct Car
{
    char name[20];
    DWORD time;
};
int main()
{
    //只有三个停车位资源
    hSemaphore = CreateSemaphore(NULL, 3, 3, L"Stop_station");

    HANDLE hArray[5] = { INVALID_HANDLE_VALUE };
    for (int i = 0; i < 5; ++i)
    {
        Car* pCar = new Car;
        sprintf_s(pCar->name, "车辆%c", 'A' + i);
                
        pCar->time = 3 + i * 2;
        //创建车辆线程
        hArray[i] = CreateThread(NULL, 0, ThreadFun1, (LPVOID)pCar, 0, NULL);
        //Sleep(1000);
    }
    //等待所有线程执行完毕
    WaitForMultipleObjects(5, hArray, true, INFINITE);
    return 0;
}
DWORD WINAPI ThreadFun1(LPVOID Parameter)
{
    //如果有剩余停车位(有信号状态),就放行
    WaitForSingleObject(hSemaphore, INFINITE);
    Car* pCar = (Car*)Parameter;
    printf("%s进入停车场!停车%d秒\n", pCar->name, pCar->time);
    Sleep(pCar->time * 1000);
    printf("%s离开停车场!\n", pCar->name);
    //释放一个停车位(信号量+1)
    ReleaseSemaphore(hSemaphore, 1, NULL);
    return 0;
}

利用Semaphore实现进程只有一个实例
有些程序不允许运行同一个程序的多个实例运行(例如以独占方式使用串行口、端口等)。这就引出了进程互斥的问题, Semaphore实现进程互斥的方法:进程在第一次启动时成功创建命名Semaphore实例成功,此后进程实例第二次创建GetL astError会返ERROR ALREADY EXISTS ,从而保证进程在系统中只能存在一个实例。

//创建互斥量
HANDLE hSemaphore = CreateSemaphore(NULL 3, 3, "sample");
//检查错误代码
if (GetLastError() == ERROR_ALREADY_EXISTS)
//如果已有互斥量存在则释放句柄
CloseHandle(hSemaphore );
//程序退出
return FALSE;
}

相关代码

#include <Windows.h>
#include <stdio.h>
int main()
{
    //利用semphore实现程序只允许一个进程
    HANDLE hSemaphore = CreateSemaphore(NULL, 3, 3, L"Stop_station");
    if (GetLastError() == ERROR_ALREADY_EXISTS)
    {
        printf("程序已经运行,请不要开启多个进程!\n");
        getchar();
        CloseHandle(hSemaphore);
        return 0;
    }

    printf("程序第一次启动\n");
     
    getchar();
    return 0;
}

多线程同步-互斥量(Mutex)

C++使用内核对象互斥体(Mutex)来实现线程同步锁。当两个或更多线程需要同时访问一个共享资源时,Mutex可以只向一个线程授予对共享资源的独占访问权。如果一个线程获取了互斥体,则要获取该互斥体的第二个线程将被挂起,直到第一个线程释放该互斥体。

CreateMutex创建一个互斥量

HANDLE CreateMutex(
LPSECURITY ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR IpName
);

参数:
lpMutexAttributes :安全属性,-般设为NULL
bInitialOwner : TRUE表示调用互斥对象的线程获得互斥对象的所有权如果为FALSE则不拥有
IpName :表示互斥量的名称,传入NULL表示匿名互斥量。
返回值:成功返回句柄,如果创建已有的互斥量,那么返回已经存在的信号量句柄
GetL astError也会设为ERROR ALREADY EXISTS失败返回NULL
ReleaseMutex释放线程拥有的互斥体的控制权

BOOL WINAPI ReleaseMutex(
HANDLE hMutex
);

参数 :
hMutex :互斥体的句柄
返回值
TRUE表示成功,FALSE表示失败。

相关代码

#include <Windows.h>
#include <stdio.h>
int num = 100;
//1.临界区结构
void __cdecl Thread1(void*);
void __cdecl Thread2(void*);
HANDLE hMutex = INVALID_HANDLE_VALUE;
int main()
{
    hMutex = CreateMutex(NULL, FALSE, L"停车场");
    printf("Begin sell tickets\n");
    HANDLE t1 = (HANDLE)_beginthread(Thread1, 0, (HANDLE)"Ticket counter A");
    HANDLE t2 = (HANDLE)_beginthread(Thread2, 0, (HANDLE)"Ticket counter B");
    HANDLE T[] = { t1, t2 };
    WaitForMultipleObjects(2, T, true, INFINITE);
    printf("Tickets sell out\n");
}
void __cdecl Thread1(void* param)
{
    char* name = (char*)param;
    while (num > 0)
    {
        if (num > 0)
        {
            printf("%s sell the %d of tickets\n", name, num--);
        }
        ReleaseMutex(hMutex);
        Sleep(1);
    }
}
void __cdecl Thread2(void* param)
{
    char* name = (char*)param;
    while (num > 0)
    {
        {
            if (num > 0)
            {
                printf("%s sell the %d of tickets\n", name, num--);
            }
            ReleaseMutex(hMutex);
            Sleep(1);
        }
    }
}

利用mutex实现进程只有一个实例
有些程序不允许运行同一个程序的多个实例运行(例如以独占方式使用串行口、端口等)。这就引出了进程互斥的问题,
mutex实现进程互斤的方法:进程在第一次启动时成功创建命名mutex实例成功,此后进程实例第二次创建GetLastError会返回ERROR_ALREADY_EXISTS,从而保证进程在系统中只能存在一一个实例。

相关代码

#include <Windows.h>
#include <stdio.h>
int main()
{
    //创建互斥体实现一个程序只允许一个实例(进程)
    HANDLE hMutex = CreateMutex(NULL, FALSE, L"停车场");
    if (GetLastError() == ERROR_ALREADY_EXISTS)
    {
        printf("程序已运行,请退出!\n");
        getchar();
        CloseHandle(hMutex);
        return 0;
    }

    printf("程序第一次运行\n");
    getchar();
    return 0;
}

多线程同步-事件(Event)

事件(Event)是WIN32提供的最灵活的线程间同步方式,根据状态变迁方式的不同,事件可分为两类:
手动设置 :这种对象只可能用程序手动设置,在需要该事件或者事件发生时,采用SetEvent及ResetEvent来进行设置。
自动恢复: 一旦事件发生并被处理后,自动恢复到没有事件状态,不需要再次设置。

CreateEvent创建事件对象

HANDLE CreateEvent(
LPSECURITY ATTRIBUTES lpEventAttributes,
BOOL
bManualReset,
BOOL
blnitialState,
LPCTSTR IpName
);

参数:
lpEventAttributes :是否可被子进程继承,如果设NULL,
此句柄不能被继承。
bManualReset :指定将事件对象创建成手动复原还是自动
复原。如果是TRUE,那么必须用ResetEvent函数来手将
事件的状态复原到无信号状态。如果设置为FALSE,当一个
等待线程被释放以后,系统将会自动将事件状态复原为无信
号状态。
blnitialState :指定事件对象的初始状态。如果为TRUE ,初
始状态为有信号状态;否则为无信号状态。
IpName :表示事件的名称,传入NULL表示匿名事件。
SetEvent把指定事件对象的状态设置为有信号状态
BOOL SetEvent(HANDLE hEvent);
参数:
hMutex :互斥体的包柄
返回值:
TRUE表示成功,FAL SE表示失败。
ResetEvent把指定事件对象的状态设置为无信号状态
BOOL ResetEvent(HANDLE hEvent);
参数:
hMutex :事件的句柄
返回值:
TRUE表示成功,FALSE表示失败。

相关代码

#include <Windows.h>
#include <process.h>
#include <stdio.h>
int num = 100;
void __cdecl Thread1(void*);
void __cdecl Thread2(void*);
HANDLE hEvent = INVALID_HANDLE_VALUE;
int main()
{
    //创建事件,此刻为有信号状态
    //自动重置信号状态,初始化为有信号状态,线程可以直接获取
    hEvent = CreateEvent(NULL, FALSE, TRUE,  L"event");
    printf("Begin sell tickets\n");
    HANDLE t1 = (HANDLE)_beginthread(Thread1, 0, (HANDLE)"Ticket counter A");
    HANDLE t2 = (HANDLE)_beginthread(Thread2, 0, (HANDLE)"Ticket counter B");
    HANDLE T[] = { t1, t2 };
    WaitForMultipleObjects(2, T, true, INFINITE);
    printf("Tickets sell out\n");
    getchar();
}
void __cdecl Thread1(void* param)
{
    char* name = (char*)param;

    while (num > 0)
    {
        //如果事件对象为有信号状态(没有线程拥有它) , 则线程可以获取它后继续执行
        //自动重置的事件对象,调用了WaitForSingleObject函数之后,自动重置为无信号

        WaitForSingleObject(hEvent, INFINITE);
        if (num > 0)
        {
            printf("%s sell the %d of tickets\n", name, num--);
        }
        SetEvent(hEvent);
        Sleep(1);
    }
}
void __cdecl Thread2(void* param)
{
    char* name = (char*)param;
    while (num > 0)
    {
        WaitForSingleObject(hEvent, INFINITE);
        if (num > 0)
        {
            printf("%s sell the %d of tickets\n", name, num--);
        }
        SetEvent(hEvent);
        Sleep(1);
    }
}

利用event实现进程只有一个实例有些程序不允许运行同一个程序的多个实例运行(例如以独占方式使用串行口、端口等)。这就引出了进程互斥的问题,event实现进程互斥的方法:进程在第一次启动时成功创建命名event实例成功,此后进程实例第二次创建Getl astError会返回ERROR_ALREADY_EXISTS ,从而保证进程在系统中只能存在一个实例。

//创建事件对象,
HANDLE hEvent = CreateEvent(NUL FALSE,FALSE,"'Sample07");
/检查错误代码
if (GetL astError() == ERROR ALREADY EXISTS)
{
//如果已有事件对象存在则释放句柄
CloseHandle(hEvent );
//程序退出
return FAL SE;
}

相关代码

#include <Windows.h>
#include <stdio.h>
int main()
{
    HANDLE hEvent = CreateEvent(NULL, FALSE, TRUE, L"event");
    if (GetLastError() == ERROR_ALREADY_EXISTS)
    {
        printf("程序已经运行了,退出!\n");
        getchar();
        CloseHandle(hEvent);
        return 0;
    }
    printf("程序第一次运行");
    getchar();
    return 0;
}

多线程同步-PV操作

P操作: passeren,译为通过"
V操作: vrijigeven,译为"释放"

P操作定义如下:
1 ) sem减1。
2 )若sem>=0,则P操作返回,该线程程可以"通过”并继续执行。(说明仓库还有位置)
3 )若sem<0,则该线程被阻塞,进入操作系统的阻塞队列。(说明仓库没有位置)
V操作定义如下:
1 ) sem加1 (释放)。
2 )若sem>0 ,则V操作返回,该线程继续执行。
3 )若sem<=0,则从阻塞队列中唤醒一一个阻塞在该信号量上的线程,然后再返回原线程(调用V操作的线程)继续执行。
生产者消费者问题
生产者消费者问题( Producer-consumer problem )是一个多线程经典案例,该问题描述了两个线程(“生产者”和“消费者”)使用一块缓冲区。生产者生成数据放到缓冲区中消费者在缓冲区取出数据。问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区空时取出数据。

相关代码

#include <Windows.h>
#include <queue>
#include <process.h>
using namespace std;

//有一个仓库
queue<int> store;
int StoreSize = 3; //仓库可以放三个货物

int ID = 1;//	货物起始ID

//随机事件数组,模拟随机生产与消费速度
int arr1[10] = { 1,3,2,3,2,5,3,6,4,1 };
int arr2[10] = { 1,2,1,2,1,1,1,1,2,6 };

//需要两个event来通知
HANDLE hEvent1 = INVALID_HANDLE_VALUE;//有货物时通知消费者,取货物
HANDLE hEvent2 = INVALID_HANDLE_VALUE;//仓库仓库为空时,通知生产者开始生产

//生产者
void ProducerThread(LPVOID param);
//消费者
void ConsumerThread(LPVOID param);
int main()
{

	hEvent1 = CreateEvent(NULL, TRUE, TRUE, L"事件对象1");//需要先开始生产(第三个参数)
	hEvent2 = CreateEvent(NULL, TRUE, FALSE, L"事件对象2"); //一开始仓库没货
	uintptr_t t1 = _beginthread(ProducerThread, 0, NULL);
	uintptr_t t2 = _beginthread(ConsumerThread, 0, NULL);
	HANDLE hArray[] = { (HANDLE)t1, (HANDLE)t2 };
	//无线等待两个线程运行完成
	WaitForMultipleObjects(2, hArray, TRUE, INFINITE);
	CloseHandle(hEvent1);
	CloseHandle(hEvent2);
	return 0;
}
//生产者
void ProducerThread(LPVOID param)
{
	while (TRUE)
	{
		//看event是否允许生产
		WaitForSingleObject(hEvent1, INFINITE);
		if (store.size() < StoreSize)//有仓库空位才生产
		{
			int id = ID++;//货物ID号
			printf("生产货物: %d\n", id);
			store.push(id);//把货物放置仓库中

			Sleep(arr1[id%10]*1000);//生产有时快,有时慢
		}
		else//仓库满了,需要停止生产
		{
			ResetEvent(hEvent1);//把事件设为无信号状态
			printf("仓库已满,不能继续生产货物\n");
		}
		if (store.size() > 0)//仓库有货物,可以通知消费者取货物
		{
			SetEvent(hEvent2);//让消费者的事件对象为有信号
		}
	}
}

//消费者
void ConsumerThread(LPVOID param)
{
	while (TRUE)
	{
		//看event2是否允许取货
		WaitForSingleObject(hEvent2, INFINITE);
		if (store.size() > 0)
		{
			int id = store.front();//获取第一个货物; 先进先出
			store.pop();//卸走货物,腾出仓库空位
			printf("			取出货物: %d\n", id);
			Sleep(arr2[id % 10] * 1000);
		}
		else//仓库空了
		{
			ResetEvent(hEvent2);//设为无信号,不能取货物
			printf("	  !仓库空了!\n");
		}
		if (store.size() < 3)//仓库没满,可以继续生产,通知生产者继续生产货物
		{
			SetEvent(hEvent1);
		}
	}
}

线程本地存储(Thread Local Storage)

如果一个变量是全局的, 那么所有线程访I问的是同一份,某一个线程对其修改会影响其他所有线程。如果我们需要一个变量在每个线程都能访问、并且值在每个线程中互不影响,这就是TLS。
线程局部存储在不同的平台有不同的实现,可移植性不太好。好在线程局部存储的实现并不难,最简单的办法就是建立一个全局表,通过当前线程ID去查询相应的数据,因为各个线程的ID不同,查到的数据自然也不同了。
静态TLS
_declspec(thread) DWORD data=0;声明了_declspec(thread)的变量,会为每个线程创建一个单独的拷贝。
静态TLS的原理
在x86 CPU上,将为每次引|用的静态TLS变量生成3 个辅助机器指令。如果在进程中创建了另一个线程,那么系统就要将它捕获并且自动分配另-一个内存块以便存放新线程的静态TLS变量。新线程只拥有对它自己的静态TLS变量的访问权, 不能访问属于其他线程的TLS变量。

相关代码

#include <windows.h>
#include <stdio.h>

DWORD WINAPI THreadFun1(LPVOID param);
DWORD WINAPI THreadFun2(LPVOID param);
//声明为静态TLS(线程本地存储变量)
_declspec(thread)  int N = 0;//每个线程中的值互不影响
int main()
{
	HANDLE hTherad1 = CreateThread(NULL, 0, THreadFun1, NULL, 0, NULL);
	HANDLE hTherad2 = CreateThread(NULL, 0, THreadFun2, NULL, 0, NULL);
	HANDLE hArray[] = { hTherad1 ,hTherad2 };
	WaitForMultipleObjects(2, hArray, TRUE, INFINITE);
	return 0;
}
DWORD WINAPI THreadFun1(LPVOID param)
{
	while (TRUE)
	{
		//每个线程中同一个索引位置的值只属于自己,与其他线程没有关系
		printf("NUM : %d\n", ++N);
		Sleep(1000);
	}
}
DWORD WINAPI THreadFun2(LPVOID param)
{
	while (TRUE)
	{
		printf("NUM : %d\n", ++N);
		Sleep(1000);
	}
}

动态TLS
TEB结构中,有个指针指向线程TLS数组,称为tls_ array ,利用这个数组指针可以管理线程相关的局部变量,这个数组由TLS MINIMUM AVAIL ABLE个元素组成,在WINNT.H文件中该值被定义为64个。
TIsAlloc函数
DWORD WINAPI TlsAlloc(void);
系统对进程中的位标志进行检索并找到一个FREE标志,然后系统会将该标志从FREE改为INUSE并让TlsAlloc返回该标志在位数组中的索引。
返回值:
成功返回索引,失败返回TLS_OUT_OF_INDEXES。
TlsSetValue函数设置变量值

BOOL WINAPI TIsSetValue(
DWORD dwTIsIndex,
L PVOID
lpTlsValue
)

参数
dwTIsIndex :索引值,表示在数组中的具体位置
IpTlsValue :要设置的值当一个线程调用TIsSetValue函数成功时,它会修改自己的PVOID数组,但它无法修改另一个线程的TLS值。
TlsGetValue函数获取变量值

LPVOID WINAPI TlsGetValue(
DWORD dwTlsIndex
);

参数:
dwTlsIndex :索引值,表示在数组中的具体位置返回在索引|为dwTIsIndex的TLS元素中保存的值,TlsGetValue只会查看属于调用线程的数组。

相关代码

#include <windows.h>
#include <stdio.h>

DWORD WINAPI THreadFun1(LPVOID param);
DWORD WINAPI THreadFun2(LPVOID param);
DWORD Tlsindex = 0;
int main()
{
	Tlsindex = TlsAlloc();
	if (Tlsindex == TLS_OUT_OF_INDEXES)
	{
		printf("分配TLS索引失败\n");
		return 0;
	}
	TlsSetValue(Tlsindex,(void *)".cpp");
	char* p = (char*)TlsGetValue(Tlsindex);
	printf("Main: %s\n", p);
	HANDLE hTherad1 = CreateThread(NULL, 0, THreadFun1, NULL, 0, NULL);
	HANDLE hTherad2 = CreateThread(NULL, 0, THreadFun2, NULL, 0, NULL);
	HANDLE hArray[] = { hTherad1 ,hTherad2 };
	WaitForMultipleObjects(2, hArray, TRUE, INFINITE);
	return 0;
}
DWORD WINAPI THreadFun1(LPVOID param)
{
	TlsSetValue(Tlsindex, (void*)"Hello");
	char* p = (char*)TlsGetValue(Tlsindex);
	while (TRUE)
	{
		printf("Thread 1: %s\n",p);
		Sleep(1000);
	}
}
DWORD WINAPI THreadFun2(LPVOID param)
{
	TlsSetValue(Tlsindex, (void*)"World");
	char* p = (char*)TlsGetValue(Tlsindex);
	while (TRUE)
	{
		printf("Thread 2: %s\n", p);
		Sleep(1000);
	}
}

多线程间消息通讯

多线程之间可以使用WINDOWS消息机制来进行消息通讯,一个线程原本是没有消息队列的,调用任何与消息相关的API会自动初始化线程消息队列相关的数据结构。
自定义windows消息
#define MY_MSG (WMUSER+1)
为了防止用户定义的消息ID与系统的消息ID冲突,Microsoft定义了一个宏#define WM_USER 0x0400小于WM_USER的ID被系统使用,大于WM_USER的ID被用户使用
PostThreadMessage将消息发送到指定线程
BOOL PostThreadMessage(
DWORD idThread,
UINT Msg,
WPARAM wP_aram,
LPARAM lParam
);

目标线程通过GetMessage(方法来接受消息。
参数
idThread :接收消息的线程id
Msg :指定被寄送的消息
wParam :指定附加的消息特定的信息
IParam :指定附加的消息特定的信息
返回值:
成功返回非零值,失败返回值是零。调用GetLastError返回1444可能是线程不存在消息队列。
GetMessage从消息队列中获取消息

BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax
);

目标线程通过GetMessage()方法来接受消息,若无消息则阻塞。
参数:
lpMsg : 接收消息的线程id
hWnd : 取得消息的窗口的柄,当为NULL时获取所属线程的消息
wMsgFilterMin : 指定被检索的最小消息值的整数
wMsgFilterMax :指定被检索的最大消息值的整数
返回值:
如果函数取得WM_QUIT之外的其他消息,返回非零值。否则返回值是零。错误返回-1,调用GetlastError函数获取错误。
PeekMessage窥探消息队列中的消息

BOOL PeekMessage(
LPMSG lpMsg,
HWND hWnd, 
UINT wMSGfilterMin,
UINT wMsgFilterMax,
UINT wRemoveMsg
);

如果消息可得到,返回非零值;如果没有消息可得到,返回值是零。
参数:
lpMsg :接收消息的线程id
hWnd :取得消息的窗口的句柄,当为NULL时获取所属线程的消息
wMsgFilterMin :指定被检索的最小消息值的整数
wMsgFilterMax :指定被检索的最大消息值的整数
wRemoveMsg :此参数可取下列值之一:
PM_NOREMOVE获取消息后不从队列里除掉。
PM_REMOVE获取消息后从队列里除掉。
PM_NOYIELD使系统不释放等待调用程序空闲的线程。
可将PM_NOYIELD随意组合到PM_NOREMOVE或PM_REMOVE。

相关代码

#include <windows.h>
#include <stdio.h>
DWORD WINAPI ThreadFun1(LPVOID param);
DWORD WINAPI ThreadFun2(LPVOID param);
//自定义消息(必须是自定义消息以上的数值)
#define MY_MSG WM_USER + 1
int main()
{
	DWORD ThreadId = 0;
	//获取第一个线程的ID,方便第二个线程给第一个线程发消息
	HANDLE T1 = CreateThread(NULL, 0, ThreadFun1, NULL, 0, &ThreadId);
	HANDLE T2 = CreateThread(NULL, 0, ThreadFun2,(LPVOID)ThreadId, 0,NULL);
	HANDLE hArr[] = { T1,T2 };
	WaitForMultipleObjects(2, hArr, TRUE, INFINITE);
	return 0;
}

DWORD WINAPI ThreadFun1(LPVOID param)
{
	//接受第二个线程发来的消息
	MSG	msg;
	while (GetMessage(&msg, NULL, 0, 0))//如果有消息继续,否则阻塞
	{
		switch (msg.message)
		{
		case MY_MSG:
			printf("收到消息的: %d\n", (int)msg.wParam);
			break;
		default:
			printf("没有收到消息\n");
			break;
		}
	}
	printf("收到WM_QUIT消息,退出\n");
	return 0;
}
DWORD WINAPI ThreadFun2(LPVOID param)
{
	//给第一个线程发消息
	int n = 1;
	DWORD pThreadid = (DWORD)param;
	while (1)
	{
		if (n < 6)
		{
			PostThreadMessage(pThreadid, MY_MSG, (WPARAM)n++, NULL);
			Sleep(1000);

		}
		else
		{
			//让第一个线程退出消息循环
			PostThreadMessage(pThreadid, WM_QUIT, NULL, NULL);
			break;
		}
	}
	return 0;
}
#include <windows.h>
#include <stdio.h>
DWORD WINAPI ThreadFun1(LPVOID param);
DWORD WINAPI ThreadFun2(LPVOID param);
//自定义消息(必须是自定义消息以上的数值)
#define MY_MSG WM_USER + 1
int main()
{
	DWORD ThreadId = 0;
	//获取第一个线程的ID,方便第二个线程给第一个线程发消息
	HANDLE T1 = CreateThread(NULL, 0, ThreadFun1, NULL, 0, &ThreadId);
	HANDLE T2 = CreateThread(NULL, 0, ThreadFun2,(LPVOID)ThreadId, 0,NULL);
	HANDLE hArr[] = { T1,T2 };
	WaitForMultipleObjects(2, hArr, TRUE, INFINITE);
	return 0;
}
                        
DWORD WINAPI ThreadFun1(LPVOID param)
{
	//接受第二个线程发来的消息
	MSG	msg;
	/*while (GetMessage(&msg, NULL, 0, 0))//如果有消息继续,否则阻塞
	{
		switch (msg.message)
		{
		case MY_MSG:
			printf("收到消息的: %d\n", (int)msg.wParam);
			break;
		default:
			printf("没有收到消息\n");
			break;
		}
	}*/
	while (1)
	{	//马上就返回,不阻塞线程
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			switch (msg.message)
			{
			case MY_MSG:
				printf("收到消息的: %d\n", (int)msg.wParam);
				break;
			case WM_QUIT:
				printf("收到WM_QUIT消息,退出\n");
				break;
			default:
				printf("没有收到消息\n");
				break;
			}
		}
	}
	printf("收到WM_QUIT消息,退出\n");
	return 0;
}
  
DWORD WINAPI ThreadFun2(LPVOID param)
{
	//给第一个线程发消息
	int n = 1;
	DWORD pThreadid = (DWORD)param;
	while (1)
	{
		if (n < 6)
		{
			PostThreadMessage(pThreadid, MY_MSG, (WPARAM)n++, NULL);
			Sleep(1000);

		}
		else
		{
			//让第一个线程退出消息循环
			PostThreadMessage(pThreadid, WM_QUIT, NULL, NULL);
			break;
		}
	}
	return 0;
}

c++11多线程简介

C++11新标准中引入五个头文件支持多线程编程,他们分别是:
<thread><atomic><mutex><condition_variable><future>
thread头文件
该头文件主要声明了std::thread 类, 另外std::this_ thread 命名空间也在该头文件中。
atomic头文件
该头文主要声明了std::atomicstd:atomic_flag两个类,另外还声明了一套C风格的原子类型和与C兼容的原子操作的函数。
mutex头文件
该头文件主要声明了与互斥量(mutex)相关的类包括std::mutex系列类、std:lock_guard、类std:unique_lock类等.
condition_variable头文件
该头文件主要声明了与条件变量相关的类,包括std::condition_variablestd::condition_variable_any两个类。
future头文件
该头文件主要声明了Futures类: std::future, shared_future
Providers类: std:promise, std:package_task
Providers函数: std:async()
其他类型: std::future_error, std::future_errc,std::future_status, std::launch

线程thread类

std::thread头文件

#include <thread>
using namespace std;
class thread
{
public:
thread() noexcept = default;
thread(thread&) = delete; //被禁用
thread( const thread&) = delete;
thread(thread&& __ t) noexcept;
template<typename_ Callable, typename..._Args>
explicit thread(_Callable&&_f, _Args&&...__args)
};

thread构造函数
template <class Fn, Class... Args>
explicit thread (Fn&& fn, Args&& ... args);
fn:函数类型
args :参数列表
get_id成员函数
id get_id() const noexcept;
获取线程的id
join成员函数
void join();
The function returns when the thread execution has completed.
当该线程执行完成后才返回。( 即等待子线程执行完毕才继续执行主线程)
joinable成员函数
bool joinable();
检查线程是否可被join
detach成员函数
void detach();
将本线程从调用线程中分离出来,允许本线程独立执行, thread对象失去对它的关联。(但是当主进程结束的时候,即便是detach出去的子线程不管有没有完成都会被强制杀死)我们需要在线程对象被销毁前调用join或detach方法,如果要detach,通常在线程启动后就立即调用detach方法。如果在线程开始之后,调用join之前发生了异常,则可能跳过对join的调用。为了避免应用程序在引发异常的时候被终止,你需要异常时也调用join.

相关代码

#include <thread>
#include <iostream>
using namespace std;
void ThreadFun1(string str);
int main()
{
	//默认构造函数
	thread t1;
	cout << t1.get_id() << endl;//线程ID为0
	cout << t1.joinable() << endl;//为0,表示不可被join
	//使用带参数的构造函数
	thread t2(ThreadFun1, string("Hello"));
	cout << t2.get_id() << endl;
	cout << t2.joinable() << endl;
	//thread对象在销毁之前,必须调用join,否则程序终止,或者detach
	//t2.join();//等待子线程执行完毕,类似于waitforsigleobject函数
	t2.detach();
	getchar();
	return 0;
}
void ThreadFun1(string str)
{
	cout << str << endl;
}

this_thread命名空间

std::thread头文件

#include <thread>
using namespace std::this_thread;

thread的所有辅助函数位于std:this_thread命名空间中。

namespace this thread{
thread:id  get_id()  _NOEXCEPT;
inline void  yield()  _NOEXC EPT
template <class Rep,class Period> inline 
void sleep_for(const chrono::duration<_Rep,_Period>&_Rel _time)
template <class Clock,class Duration> inline 
void sleep_until(const chrono:time_point<_Clock,_Duration> &_Abs_time)
...
}

get id函数
thread::id get_id() noexcept;
用于获取当前线程id.
yield函数
void yield() noexcept;
使当前线程暂停, 等待下次调度.
sleep_for函数
template <class_Rep, class_Period>
void sleep_for(const chrono:.duration<_Rep,_Period> &Rel_time)
用于使当前线程阻塞一个时间段。
std:this
thread:sleep_for(std:chrono:seconds(5);
sleep_until函数
template < class Clock, class Duration>
void sleep_until (const chrono::time_point< Clock,Duration> & abs_time);
使当前线程阻塞直到某个时间点

相关代码

#include <thread>
#include <iostream>
using namespace std;
//this_thread有所有的线程辅助函数 get_id() yield() sleep_for() sleep_until();
using namespace std::this_thread;
using namespace std::chrono;
void ThreadFun(string a);
int main()
{
	thread t1(ThreadFun, string("hello"));
	cout << t1.get_id() << endl;
	//等待子线程完成
	t1.join();
	return 0;      
}

void ThreadFun(string a)
{
	cout << get_id() << endl;//获取线程ID
	while (1)
	{
		cout << a << endl;
		//sleep_for(seconds(1));//让线程睡眠1秒 。和26行效果一样
		sleep_until(system_clock::now() + milliseconds(1000));//当前时间+1秒
	}
}

filag类原子atomic类atomic_flag类

头文件

#include<atomic>
using namespace std;

atomic类
template struct atomic;
atomic模板类,生成一个T类型的原子对象,并提供了系列原子操作函数。
atomic_flag类
atomic_flag一种简单的原子布尔类型,只支持两种操作,test_and_set和clear.

#define ATOMIC_FLAG_INIT {0}
typedef struct atomic flag
bool test and set(memory order Order =
memory order seq cst)volatile_ NOEXCEPT;
void clear(memory order Order =
memory order seq cst)
volatile NOEXCEPT;

如果某个std::atomic_flag对象使用ATOMIC_FLAG_INIT宏初始化,那么可以保证该std::atomic_flag对象在创建时处于clear状态。
test_and_set()函数检查std::atomic_flag标志,如果std:atomic_flag 之前没有被设置过则设置std:atomic_flag 的标志,并返回先前该std::atomic_flag 对象是否被设置过,被设置返回true ,否则false。
test-and-set操作是原子的(因此test-and-set是原子read-modify -write(RMW)操作)

相关代码

#include<iostream>
#include<thread>
#include<atomic>//原子操作头文件
using namespace std;
atomic <int> N = 0;//用atomic保证对N的操作的原子性
void ThreadFun(void)
{
	for (int i = 0; i < 1000000; i++)
		++N;//线程并发导致家操作重叠
}
int main()
{
	thread t1(ThreadFun);
	thread t2(ThreadFun);
	t1.join();
	t2.join();
	cout << N << endl;
	return 0;
}
#include <atomic>
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
using namespace std::this_thread;
atomic <bool> ready = false;//是否枪响
atomic_flag win = ATOMIC_FLAG_INIT;//终点线
void ThreadFun(int id)
{
	while (!ready)
	{
		yield();//让其他线程先执行
	}
	//表示跑的过程
	for (int i = 0; i < 100000; i++)
	{
	}
	//如果没有被设置过,返回false
	if (!win.test_and_set())//调用后会将他设置
	{
		cout << id << "号选手赢得第一名" << endl;
	}
	
}

int main()
{
	//十个赛跑者
	vector <thread> vecPlayers;
	for (int i = 0; i < 10; i++)
	{
		vecPlayers.push_back(thread(ThreadFun, i + 1));
	}
	cout << "十个选手已准备好" << endl;
	//准备发命令预备跑
	for (int i = 3; i > 0; i--)
	{
		//sleep_for(chrono::seconds(1));
		cout <<"	"<< i << endl;
	}
	cout << "	 跑!" << endl;
	sleep_for(chrono::seconds(1));
	ready = true;//可以跑了
	//等待所有选手跑完
	for (thread& t : vecPlayers)
	{
		t.join();
	}
	return 0;
}

互斥类mutex

mutex系列类(四种)

std::mutex 
std::recursive_mutex递归Mutex类
std::timed_mutex定时Mutex类
std:recursive_timed_mutex定时递归

构造函数
std::mutex不允许拷贝构造,也不允许move拷贝,最初产生的mutex对象是处于unlocked状态的。lock(),调用线程将锁住该互斥量。线程调用该函数会发生下面3种情况:
(1).如果该互斥量当前没有被锁住,则调用线程将该互斥量锁住直到调用unlock之前,该线程直拥有该锁。
(2).如果当前互斥量被其他线程锁住,则当前的调用线程被阻塞住。
(3).如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock).
unlock(),解锁,释放对互斥量的所有权。
try_lock() ,尝试锁住互斥量,如果互斥量被其他线程占有则当前线程也不会被阻塞。
调用该函数会出现下面3种情况:
(1).如果当前互斥量没有被其他线程占有,则该线程锁住互斥量,直到该线程调用unlock释放互斥量。
(2).如果当前互斥量被其他线程锁住,则当前调用线程返回false,而并不会被阻塞掉。
(3).如果当前互斥量被当前调用线程锁住,则会产生死锁(deadlock)。
timed_mutex类
stdl::timed_mutex比std::mutex多了两个成员函数, try_lock_for(), try_lock_until()。
try_lock_for函数
try_lock_for函数接受-一个时间范围,表示在这一段时间范围之内线程如果没有获得锁则被阻塞住,如果在此期间其他线程释放了锁,则该线程可以获得对互斥量的锁,如果超时(即在指定时间内还是没有获得锁),则返回false。
recursive_timed_mutex类
std::recursive_timed_mutex与timed_mutex的特性大致相同,允许同一个线程对互斥量多次上锁(即递归上锁) ,来获得对互斥量对象的多层所有权,释放互斥量时需要调用与该锁层次深度相同次数的unlock() ,可理解为lock()次数和unlock()次数相同。

相关代码

//卖票问题
#include <thread>
#include <stdio.h>
#include <string>
#include <mutex>
using namespace std;
using namespace std::this_thread;
using namespace std::chrono;
//定义一个互斥对象
mutex m;
recursive_mutex M;//可以递归加锁
timed_mutex m3;//可以指定锁住时间
int tickets = 100;
void ThreadF1(string  str);
int main()
{
	thread t1(ThreadF1,string("窗口A"));
	thread t2(ThreadF1,string("窗口B"));
	t1.join();
	t2.join();
	return 0;
}
void ThreadF1(string  str)
{
	while (tickets>0)
	{
		//M.lock();//加锁
		//M.lock();//加锁
		if (tickets > 0)
		{
			sleep_for(milliseconds(10));
			printf("%s卖出第%d张票\n", str.c_str(), tickets--);
		}
		//M.unlock();//解锁
		//M.unlock();//解锁
		//sleep_for(milliseconds(10));
	} 
}

guard类

头文件

#include <mutex>
using namespace std;

std::lock_ guard类
st:lock_guard 类采用RAII手法管理某个锁(Lock)对象, 其功能是在对象构造时将mutex加锁,析构时对mutex解锁,这样一个栈对象保证了在异常情形下mutex可以在lock_guard对象析构被解锁(注:类似shared_ptr 等智能指针管理动态分配的内存资源)。
std::lock_ guard类定义
template < class _Mutex>
class lock_guard
{ ...
模板参数Mutex代表互斥量类型,例如std:mutex类型 ,_Mutex型的对象只需满足lock和unlock两种操作。

lock guard对象构造时,传入的Mutex对象会一直保持上锁状态,在lock_guard 对象被析构时它所管理的Mutex对象会自动解锁,无需手动调用lock和unlock对Mutex进行上锁和解锁操作,因此这也是最简单安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的Mutex对象可以正确进行解锁操作,极大地简化了程序员编写与Mutex相关的异常处理代码。
lock_guard最大的特点就是安全易于使用,通过lock_guard 对象管理的Mutex哪怕异常抛出的时候可以得到正确地解锁。

相关代码

#include <iostream>
#include <mutex>
#include <thread>
#include <stdexcept>
using namespace std;
using namespace std::this_thread;
mutex m;

void Fun1();
void Fun2();
int main()
{
	thread t1(Fun1);
	thread t2(Fun2);
	t1.join();
	t2.join();
	return 0;
}
void Fun1()
{
	try 
	{
		for (int i = 0; i < 6; i++)
		{

			m.lock();
			if (i == 3) throw logic_error("出错");
			cout << "Aoutput:" << i << endl;
			m.unlock();//抛出异常时,导致没有unlock,使得B无法获取mutex,死锁
			sleep_for(chrono::seconds(1));
		}
	}
	catch(exception &e)
	{
		cout << "错误:" << e.what() << endl;
	}
}
void Fun2()
{
	//try
	//{
		for (int i = 0; i < 6; i++)
		{

			m.lock();
			//if (i == 3) throw logic_error("出错");
			cout << "Boutput:" << i << endl;
			m.unlock();
			sleep_for(chrono::seconds(1));
		}
	//
	//catch (exception& e)
	//{
	//	cout << "错误:" << e.what() << endl;
	//}
}
#include <iostream>
#include <mutex>
#include <thread>
#include <stdexcept>
using namespace std;
using namespace std::this_thread;
mutex m;

void Fun1();
void Fun2();
int main()
{
	thread t1(Fun1);
	thread t2(Fun2);
	t1.join();
	t2.join();
	return 0;
}
void Fun1()
{
	try 
	{
		for (int i = 0; i < 6; i++)
		{

			sleep_for(chrono::seconds(1));
			//m.lock();
			lock_guard<mutex> lck(m);
			if (i == 3) throw logic_error("出错");
			cout << "Aoutput:" << i << endl;
			//m.unlock();//抛出异常时,导致没有unlock,使得B无法获取mutex,死锁
		}
	}
	catch(exception &e)
	{
		cout << "错误:" << e.what() << endl;
	}
}
void Fun2()
{
	//try
	//{
		for (int i = 0; i < 6; i++)
		{

			m.lock();
			//if (i == 3) throw logic_error("出错");
			cout << "Boutput:" << i << endl;
			m.unlock();
			sleep_for(chrono::seconds(1));
		}
	//
	//catch (exception& e)
	//{
	//	cout << "错误:" << e.what() << endl;
	//}
}

lock类

头文件

#include <mutex>
using namespace std; 

std:unique_lock类
unique_lock 对象以独占所有权的方式( unique_owership )管理mutex对象的上锁和解锁操作,所谓独占所有权,就是没有其他的unique_lock对象同时拥有某个mutex对象的所有权。它与st::lock_ guard功能相似但功能更加灵活的管理mutex对象,unique_lock内部持有mutex的状态。
unique_ lock比lock guard优势在于:
unique_lock 不一定要拥有mutex,可建立空的unique_lock.unique_lock 不可复制( non-copyable ), 但它可以转移( movable )。所以它可以被函数回传 ,可放到 STL container 里。unique_ lock 提供lock()、unlock() 等函数,可用来加解锁mutex。unique lock本身还可以用于std:lock参数,因为其具备lock、unlock、try_ lock成员函数,这些函数不仅完成针对mutex的操作还要更新mutex的状态。
unique_ lock(构造函数
(1) unique_lock() noexcept; //可以构造一个空的unique_lock对象,此时并不拥有任何mutex
(2) explicit unique_lock (mutex type& m);//拥有mutex,并调用mutex.lock()对其上锁
(3) unique_lock (mutex_type& m, try_to_lock_t tag);//tag =try_ lock表示调用mutex.try. lock()尝试加锁
(4) unique_ lock (mutex_type& m, defer lock_t tag) noexcept;//tag=defer lock表示不对mutex加锁,只管理mutex,此时mutex应该是没有加锁的
(5) unique_ lock (mutex_type& m, adopt_lock_t tag);//tag=adopt lock表示mutex在此之前已经被上锁,此时unique lock管理mutex

相关代码

#include <iostream>
#include <mutex>
#include <thread>
#include <stdexcept>
using namespace std;
using namespace std::this_thread;
mutex m;

void Fun1();
void Fun2();
int main()
{
	thread t1(Fun1);
	thread t2(Fun2);
	t1.join();
	t2.join();
	return 0;
}
void Fun1()
{
	try 
	{
		for (int i = 0; i < 6; i++)
		{


			//m.lock();
			unique_lock<mutex> lck(m);
			if (i == 3) throw logic_error("出错");
			cout << "Aoutput:" << i << endl;
			lck.unlock();
			sleep_for(chrono::seconds(1));
			//m.unlock();//抛出异常时,导致没有unlock,使得B无法获取mutex,死锁
		}
	}
	catch(exception &e)
	{
		cout << "错误:" << e.what() << endl;
	}
}
void Fun2()
{
	//try
	//{
		for (int i = 0; i < 6; i++)
		{

			unique_lock<mutex> lck(m);
			//if (i == 3) throw logic_error("出错");
			cout << "Boutput:" << i << endl;
			lck.unlock();
			sleep_for(chrono::seconds(1));
		}
	//
	//catch (exception& e)
	//{
	//	cout << "错误:" << e.what() << endl;
	//}
}

condition_variable条件变量

condition variable有5 个函数

wait阻塞自己,等待唤醒
wait_for阻塞自己,等待唤醒,最多等待一段时间
wait_until阻塞自己,等待唤醒,最多等待到某个时间点
notify_one唤醒一个等待在这个条件变量上的线程
notify_all唤醒所有等待在这个条件变量上的线程

当std::condition_variable对象的某个wait函数被调用的时候它使用std:unique_lock(通过std:mutex)来锁住当前线程。当前线程会一直被阻塞直到另外一个线程在相同tl:condition variable对象上调用了notify函数来唤醒当前线程。
std::condition_variable提供了两种wait()函数。当前线程调用wait()后将被阻塞(此时当前线程应该获得了锁( mutex ),不妨设获得锁lIck) ,直到另外某个线程调用notify_*唤醒了当前线程。在线程被阻塞时,该函数会自动调用lck.unlock()释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。另外旦当前线程获得通知(notified,通常是另外某个线程调用notify 唤醒了当前线程),wait()函数也是自动调用lck.lock(),使得Ick的状态和wait函数被调用时相同。
wait()
当前线程调用wait()后将被阻塞(此时当前线程应该获得了锁( mutex ),自动调用lck.Iock() 使得Ick的状态和wait函数被调用时相同,直到另外某个线程调用notify_
唤醒了当前线程。
wait_for()
wait_for可以指定一个时间段,在当前线程收到通知或者指定的时间rel_time超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其他线程的通知,wait_for返回cv_status::timeout,剩下的处理步骤和wait()类似。
wait_until()
wait_until可以指定一个时间点,在当前线程收到通知或者指定的时间点abs_time超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其他线程的通知,wait_until返回,剩下的处理步骤和wait_for() 类似
notify_one()
唤醒某个等待(wait)线程。如果当前没有等待线程,则该函数什么也不做,如果同时存在多个等待线程,则唤醒某个线程是不确定的(unspecified)。
notify_ all()
唤醒所有的等待(wait)线程。如果当前没有等待线程,则该函数什么也不做。
condition variable_any
和std:.condition_variable几乎完全一样。只不过std.condition_variable_any的wait函数可以接受任何lockable参数,而std::condition_variable只能接受std::unique_lock类型的参数

相关代码

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

using namespace std;
using namespace std::this_thread;
using namespace std::chrono;
condition_variable cv;//仅支持unique_lock<mutex>
condition_variable_any cv1;//接受任何lockable参数作为wait的参数
mutex m;
void Fun(int id);
int main()
{
	thread t1(Fun,1);
	thread t2(Fun,2);
	thread t3(Fun,3);
	sleep_for(seconds(1));
	cv1.notify_one();//唤醒
	t1.join();
	t2.join();
	t3.join();
	return 0;
}
void Fun(int id)
{
	unique_lock<mutex> lck(m);
	cv.wait(lck);//当前线程被阻塞
	cout << id << "线程正在运行" << endl;
}

future类

future类

头文件

#include <future>
using namespace std;

如果你希望一个线程做一些费时任务,然后任务完成后获取线程中产生的结果。
1,在win32中,你可以在你想要结果的地方,调用WaitForSingleObject。
2,C++ 11中我们不能直接从thread.join()得到结果,必须定义一个变量,在线程执行时,对这个变量赋值,然后执行join()过程相对繁琐。
std::future类
stl::future可以从异步任务中获取结果,一
般与std::async配合使用,std:async用于创
建异步任务,实际上就是创建一个线程执行
相应任务,然后std:future对象调用get
(通常在另外一个线程中)获取该值。
std::future构造函数
std:future的拷贝构造函数是被禁用的,只提供默认的构造函数,普通赋值操作也被禁用,只提供了move赋值操作。
如下代码所示:
std::future fut;
//默认构造函数
fut = std:async(do some task); // move-赋值操作。
get成员函数
_Ty get();
调用get会阻塞当前的调用者,直到Provider设置了共享状态的值或异常(此时共享状态的标志变为ready),std:future::get将返回异步任务的值或异常(如果发生了号常)。
std::async函数
stl:async,c+ +11中提供的异步任务高级抽象,包含在头文件中,能让你方便的实现异步地执行一一个任务, 并在需要地时候获取其结果。
std::async比std:thread的优势
1.std:.async返回的future对象,可以方便地等待callable对象执行完成并获取其返回值
2能从实现库的一些高级功能中获益,比如线程池等,并大大减少异常的产生。

相关代码

#include <iostream>
#include <thread>
#include <future>
using namespace std;
using namespace std::this_thread;
using namespace std::chrono;
int work(int a, int b)
{
	cout << "等三秒,别着急" << endl;
	sleep_for(seconds(3));
	return a + b;
}
int main()
{
	future<int> result = async(work, 2, 3);//move赋值操作
	result.wait();
	int sum = result.get();
	cout << "算出的结果是: " << sum << endl;

}

promise类

std::promise用来包装一个值将数据和future绑定起来,为获取线程函数中的某个值提供便利,取值是间接通过promise内部
提供的future来获取的。promise的主要目的是提供一个'Set'操作,以和future的get()对应。

get_future函数
future<_ Ty> get future()
该函数返回一个与promise共享状态相关联的future对象返回的future对象可以访问由promise对象设置在共享状态上的值或者某个异常对象。promise 对象通常会在某个时间点准备好(设置一个值或者一个异常对象),然后在另个线程中future对象使用get获取值。
set_value函数
void set value(const_ Ty&_ Val)
设置共享状态的值,此后promise的共享状态标志变为ready.
set_exception 函数
void set_exception(_XSTD exception_ptr_Exc)
为promise设置异常,此后promise的共享状态变标志变为ready.

相关代码

#include <iostream>
#include <thread>
#include <future>
using namespace std;
using namespace std::this_thread;
using namespace std::chrono;
void work(promise<int> &prom)
{
	cout << "等三秒,别着急" << endl;
	sleep_for(seconds(3));
	cout << "等待成功" << endl;
	//promise 设置 结果值
	prom.set_value(666);//设置结构,future会get到
}
int main()
{
	promise<int> prom;

	future<int> result = prom.get_future();

	thread t1(work,ref(prom)); 
	//ref在thread中是必须要使用的,实现引用传递,在bind函数中如果要传递引用,也要用到ref
	t1.detach();

	int sum = result.get();
	cout << "获取结果: " << sum << endl;
	return 0;
}
#include <iostream>
#include <thread>
#include <future>
using namespace std;
using namespace std::this_thread;
using namespace std::chrono;
void work(promise<int>& prom)
{
	cout << "等三秒,别着急" << endl;
	sleep_for(seconds(3));
	try
	{
		throw runtime_error("运行时,错误");
	}
	catch (...)
	{
		//promise 设置异常
		prom.set_exception(current_exception());
	}
}
int main()
{
	promise<int> prom;
	//future 和 promise 搭配使用,类似于aynsc
	future<int> result = prom.get_future();

	thread t1(work, ref(prom));
	//ref在thread中是必须要使用的,实现引用传递,在bind函数中如果要传递引用,也要用到ref
	t1.detach();
	try
	{
		int sum = result.get();
		cout << "获取结果: " << sum << endl;
	}
	catch (exception& e)
	{
		cout << "结果异常:" << e.what() << endl; 
	}
	return 0;
}

packet_task类

头文件

#include <future>
using namespace std;

std:packaged task它包装了一个可调用的
目标(如function, lambda expression,bind expression, or another function object )以便异步调用,它和promise在某种程度上有点像,promise保存了一个共享状态的值,而packaged task保存的是一个函数。
**get future函数 **
future<_Ty> get future()
获取与共享状态相关联的stdl:future对象。在调用该函数之后,两个对象共享相同的共享状态,具体解释如下:std:packaged task 对象是异步Provider ,它在某-时刻通过调用被包装的任务来设置共享状态的值。stdl:future对象是一个异步返回对象,通过它可以获得共享状态的值, 当然在必要的时候需要等待共享状态标志变为ready.

相关代码

#include <iostream>
#include <thread>
#include <future>
using namespace std;
using namespace std::this_thread;
using namespace std::chrono;
int work(int a, int b)
{
	cout << "正在计算,请等待" << endl;
	sleep_for(seconds(3));
	return a + b;
}
int main()
{
	packaged_task<int(int, int)> pack(work);
	future<int> result = pack.get_future();
	thread T(move(pack), 123, 456);
	
	int sum = result.get();
	cout << "计算完毕,结果为: " << sum << endl;
	T.join();
	return 0;
}

总结

future 访问异步操作的结果,用于不同线程间简单同步
➢async通过创建或者选取一个当前空闲线程执行任务,然后将计算结果保存至与此async相关的future中, 期间只有存取结果值,没有其它的交互,并且是provider持有future,executor执行任务。
➢promise是provider持有,executor持有相关的future,然后provider通过promise设定共享状态的值,future获取该共享值后执行某些任务。
➢packaged_task是一个对象其内部持有callable object,provider创建一个线程,executor执行任务,最后provider通过相关的future获取任务计算结果,和async类似。

stl::future提供了一个访问异步操作结果的机制,它和线程是一个级别的属于低层次的对象,在它之上高一层的是std:packaged task和std::promise ,他们内部都有futurel以便访问异步操作结果
std:packaged_task包装的是一个异步操作,而std:promise包装的是一个值,都是为了方便异步操作的,如果需要获取线程中的某个值,使用std:promise,而需要获一个异步操作的返回值 ,就用std:packaged_task
std::async更像是让这三个对象默契的工作。std:async先将异步操作用std:packaged task包装起来,然后将异步操作的结果放到std:promise中,再通过future.get/wait来获取这个未来的结果,让用户使用更方便!

相关代码

#include <iostream>
#include <thread>
#include <future>
using namespace std;
using namespace std::this_thread;
using namespace std::chrono;
void Work(promise<int>& prom, int a, int b)
{
	cout << "正在计算" << endl;
	sleep_for(seconds(2));
	prom.set_value(a + b);
}
int Work1( int a, int b)
{
	cout << "正在计算" << endl;
	sleep_for(seconds(2));
	return a + b;
}
int Work2(int a, int b)
{
	cout << "正在计算" << endl;
	sleep_for(seconds(2));
	return a + b;
}
int main()
{
	//promise内包装了一个值
	promise<int> prom;
	future<int> result = prom.get_future();//使用get_future成员函数获取future
	thread(Work,ref(prom), 1, 1).detach();
	result.wait();//等待值设置
	cout << "计算结果为: " << result.get() << endl;//获取结果

	//packaged_task包装一个操作
	packaged_task<int(int, int)> task(Work1);//使用get_future成员函数获取future
	future<int> fu = task.get_future();
	thread(move(task), 11, 11).detach();
	fu.wait();//等待值设置
	cout << "计算结果为: " << fu.get() << endl;//获取结果

	//async
	future<int> ff = async(Work2, 111, 111);
	ff.wait();//等待值设置
	cout << "计算结果为: " << ff.get() << endl;//获取结果
	
	return 0;
}
posted @   NeverLateThanBetter  阅读(252)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示