一. Windowsx消息响应机制

系统消息对列 ->  当前应用程序的消息对列 -> GetMessage()从对列中取出消息 -> TranslateMessage()翻译函数 -> DispatchMessage()分发函数 --->Afxunclproc回调函数(所有的函数都有一个回调函数,即消息入口点函数)---消息发送到窗口---->   pWnd -> windoproc  ----消息映射表-------->  处理函数

 tips:SendMessage()不进入程序的消息对列,但进入系统的消息对列

Sleep时,效果,可以执行其他消息吗 ? 进度条变慢:sleep(0);让出本次时间片,sleep(100):睡100s,醒来后再轮转到它才行 。 sleep时可以执行其他消息因为没有从消息对列中拿消息所以不能执行

下面的做法虽然可以在跑进度条的时候,接收其他消息,但二者不能同时执行,创建线程则可以。

???

复制代码
    while(1)
    {
        MSG msg;
        proctrl.StepIt();
        //--------------可以并发处理别的消息------------
        if(GetMessage(&msg,0,0,0))//从队列中取得消息
        {
            TranslateMessage(&msg);//翻译消息
            DispatchMessage(&msg);//分发消息
        }
        //---------并发:轮换时间片,不能同时执行----------
    }

二. 创建线程

建立线程有三种方式:CreateThread() ,WindowsAPI   此方法的退出方式为ExitThread

         AfxBeginThread() ,MFC

            _beginthreadex() ,C++类中封装的函数 :由申请空间和CreateThread()两部分组成 , 释放则由ExitThread()和释放空间两部分组成

线程:进程中的执行单元 , 分配CPU的基本单位。

线程栈:存储在线程中创建的变量(线程退出,线程栈被销毁掉)

内核对象:计数器 (初始值为2,当为0时,释放内核对象)1.线程退出(-1)2.关闭句柄(-1)(CloseHandle()) ; openThread()计数器+1

     挂起计数器  :  SuspendThread()挂起计数器+1;ResumeThread()挂起计数器-1

     信号:线程退出则有信号,否则无信号(WAIT_TIMEOUT)。

函数:

m_hThread = CreateThread(NULL, //安全属性
                  0 ,//线程栈  默认1MB
                  &ThreadProc, //函数地址
                 this, //线程参数
                  0 ,//创建标志  0 立即运行  CREATE_SUSPENDED  挂起
                  NULL //线程ID
           );

挂起线程:

 SuspendThread (m_hThread);//挂起计数器+1

恢复线程:

ResumeThread(m_hThread);//线程存在则恢复线程(挂起计数器-1),挂起计数器不能为负

线程退出:

    //1.正常退出
    m_bFlagQuit = false;
    //2.强制退出
    //ExitThread()退出调用它的线程
    //判断线程是否已经退出,如果没有( 无信号),则强制杀死
    if(WAIT_TIMEOUT ==  WaitForSingleObject(m_hThread,100))
        TerminateThread(m_hThread,-1);

    if(m_hThread)
    {
        CloseHandle(m_hThread);//关闭句柄
        m_hThread = NULL;
    }

三 . 进程间通信:事件

另一个进程使当前进程退出:事件

    hSignal = CreateEvent(NULL,//安全属性
                    TRUE,//复位属性(自动/手动)TRUE为人工,FALSE为自动
                    FALSE,//初始化(无信号)
                    _T("Event"));//名字

在另一个进程中打开:置为有信号

    HANDLE hEvent = OpenEvent(EVENT_ALL_ACCESS , FALSE , _T("Event"));//第一个为权限,第三个为事件名称(唯一)
    if(hEvent)
        SetEvent(hEvent);

线程执行函数:

DWORD WINAPI ThreadProc(LPVOID lpParameter)//线程要执行的函数
{
    CThreadDlg* pthis = (CThreadDlg*)lpParameter;
    while(1)
    {
        if(WAIT_OBJECT_0 == WaitForSingleObject(pthis->hSignal , 100))
                //等待100ms,事件有信号则返回
            break;
        pthis->proctrl.StepIt();
        Sleep(100);
    }

    return 0;
}

重置信号:

ResetEvent(hSignal);//重置信号(自动则不需要)

四. 用户界面线程:UI线程

创建用户界面线程:AfxBeginThread()//窗口没了,线程就没了,

          相当于创建了一个线程,在线程执行的函数中,创建了一个窗口,比如:MyDlg dlg;   dlg.DoModal();

Dialog继承自Cwnd,MyThread,继承自CWinThread

CWinApp类就是继承自CWinThread,pMainWnd指针(线程主窗口)指向对话框,将对话框和主线程连接起来

仿照主窗口和主线程,写一个我们自己的UIThread

当点击窗口右上角的关闭标识的时候,1>WM_CLOSE 2>WM_DESTROY 3>WM_QIUT

???用户界面线程(UI线程)与工作者线程的区别:工作者线程没有消息对列。

五. PostMessage 和SendMessage

六. //调用约定
//WINAPI (C++) stdcall 参数入栈顺序从右--左 函数本身清理


//WINAPIV (C) cdecl 参数入栈顺序从右--左 由调用者清理空间 支持可变的参数个数

七.线程同步

可以跨进程使用的:互斥量,事件,信号量

内核对象:都有一个变量:使用计数

  一.原子访问:指的是一个线程在访问某个资源的同时,能够保证没有其他线程会在同一时刻访问同一资源。Interlocked系列。

    volatile,:防止编译优化(从寄存器中取值,相对于从外存取值更节省时间,但造成了不能取到的值不能及时更新),对特殊地址的稳定访问。

    缺点:只能实现对一个32位或者64位值的单独访问,不能实现复杂的功能。

    InterlockedExchangeAdd(&g_x,1);

    InterlockedIncrement(&g_x);

    InterlockedDecrement(&g_x);

  二 . 关键段(临界区):同一时刻只一个线程访问代码段

      IniticalizeCriticalsection(&g_cs); //初始化CRITICAL_SECTION 中的成员变量

      EnterCriticalSection(&g_cs); //线程用它来检查占用标志的函数

      LeaveCriticalSection(&g_cs); //离开“卫生间”,门上重新置为无人

      DeleteCriticalsection(&g_cs); //删除事件的内核对象,以及为CRITICAL_SECTION初始化的

      当有线程正在执行临界区代码时,系统会为想要访问该代码段的的线程创建一个事件的内核对象,将其由用户模式切换到内核模式,处于等待(挂起)状态,当线程执行完代码段时,系统会从一些想要访问该代码段的线程中,将其中一个线程置为可调度状态。

      由于用户模式和内核模式切换大概1000个CPU周期,太浪费时间:

      (1)异步方式:TryEnterCriticalSection线程在访问时,如果不能访问资源,那么它继续做其他事情,而不用等待。

          if(!TryEnterCriticalSection(&pthis->m_cs))//异步
            continue;

      (2)用旋转锁。因此,当调用EnterCriticalSection的时候,它会用一个旋转锁不断的循环,尝试在一段时间内获得访问权。只有当尝试失败的时候,线程才会切换到内核模式并进入等待状态。为了在使用关键段的时候同时使用旋转锁,我们必须调用下面的函数来初始化关键段。

        bool InitializeCriticalSectionAndSpinCount( &pthis->m_cs, 1);

        SetCriticalSectionSpinCount(  &pthis->m_cs,1)//改变旋转锁的次数

      CCriticalSection类:

      1. 定义一个类变量 :CCriticalSection critical_section;
      2. 加锁:critical_section.Lock();
      3. critical_section.Unlock();

    三.互斥量:

    互斥对象包含一个使用计数、线程ID以及一个递归计数。

    线程ID:用来标识当前占用这个互斥量的是系统中那个线程。如果为false 则此互斥量是触发状态,否则,当前线程有对互斥量的拥有权。直到他释放,其他线程才可以拥有。

    递归计数:表示这个线程占用该互斥量的次数。

    1. 创建互斥量

         HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes, //安全属性

                    BOOL bInitialOwner, //false 则此互斥量是触发状态(可以拿到互斥量),否则,当前线程有对互斥量的拥有权。 

                   LPCTSTR lpName //互斥量的名字);

      2.在线程函数中抢互斥量的拥有权,一旦拥有,则其他线程不可以再去抢夺

          WaitForSingleObject(pthis->m_mutex,INFINITE);

          直到此线程调用

          ReleaseMutex(pthis->m_mutex);

        其他线程可以继承,抢夺对互斥量的拥有权。

    关键段和互斥量的差别:

      1.互斥量可以跨进程,关键段只能在同一进程的各个线程之间。当同一个进程中时,使用关键段(节省资源更有效率)

      2.关键段是用户模式下的同步对象,互斥量是内核对象,

      这种切换是非常耗时的:在X86平台上,一个空的系统调用大概会占用200个CPU 周期—当然,这还不包括执行被调用函数在内核模式下的实现代码。但是造成内核对象比用户模式下的同步机制慢几个数量级的原因,是伴随调度新线程而来的刷新高速缓存以及错过高速缓存(即未命中)。???

    互斥量不同于其他内核对象的地方:

    系统会检查想要获得互斥量的线程的线程ID与互斥量对象的内部记录的线程ID是否相同。如果线程ID 一致,那么系统会让线程保持可调度状态—即使该互斥量尚未触发。每次线程成功地等待了一个互斥量,互斥量对象的递归计数会递增,使递归计数大于1的唯一途径是利用这个例外,让线程多次等待同一个互斥量。 ReleaseMutex( HANDLE hMutex);函数会将对象的递归计数减1,。如果线程成功地等待了互斥量对象不止一次,那么线程必须调用ReleaseMutex相同的次数才能使对象的递归计数变成0.当递归计数为0的时候,函数还会将线程ID设为0,这样就触发了对象。

    简单的来说,就是当多次等待信号量,就要释放多次。

    互斥量的等待和释放要在同一个线程中,因为互斥量会记录线程ID,必须由等待到的线程释放

    四.事件:人工时间,自动事件

    CEvent 类

    CEvent(BOOL bInitiallyOwn=FALSE,//初始状态为无信号

              BOOL bManualReset=FALSE,//创建的事件为自动事件

              LPCTSTR lpszName=NULL,

              LPSECURITY_ATTRIBUTES lpsaAttribute=NULL);

     ResetEvent()将 事件设为无信号状态。人工事件要自己重置,否则一直保持有信号状态

     SetEvent()将事件置为有信号,自动事件执行完该语句,系统自动将事件置为无信号,不需要该函数。

    五.信号量:CSemaphore 类  

      CSemaphore (LONG lInitialCount=1,//当前可用资源数,(初始值为最大值)

                     LONG lMaxCount=1,//最大资源计数

                     LPCTSTR pstrName=NULL,

                     LPSECURITY_ATTRIBUTES lpsaAttributes=NULL);

       ReleaseSemaphore()当前可用资源数+1

八.内核对象:系统提供的用户模式下代码与内核模式下代码进行交互的基本接口

一个内核对象是一块内核分配的内存,它只能被运行在内核模式下的代码访问。内核对象记录的数据在整个系统中只有一份,所以它们也称为系统资源。

引入内核对象以后,系统可以方便地完成下面 4 个任务:
(1) 为系统资源提供可识别的名字。
(2) 在进程之间共享资源和数据。
(3) 保护资源不会被未经认可的代码访问。
(4) 跟踪对象的引用情况。这使得系统知道什么时候一个对象不再被使用了,以便释放它占用的空间。

内核对象是进程内的资源,使用计数属性指明进程对特定内核对象的引用次数,当系统发 现引用次数是 0 时,它就会自动关闭资源。事实上这种机制是很简单的,一个进程在第一次创 建内核对象的时候,系统为进程分配内核对象资源,并将该内核对象的使用计数属性初始化为1;以后每次打开这个内核对象,系统就会将使用计数加 1,如果关闭它,系统将使用计数减 1,减到 0 就说明进程对这个内核对象的所有引用都已关闭,系统应该释放此内核对象资源。

  内核对象的数据结构仅能够从内核模式访问。应用程序必须使用API 函数访问内核对象。调用函数创建内核对象时, 函数会返回标识此内核对象的句柄。可以想象此句柄是一个能够被进程中任何线程使用的一个 不透明的值,许多 API 函数需要以它作为参数,以便系统知道要操作哪一个内核对象。为了使系统稳定,这些句柄是进程相关的,也就是仅对创建该内核对象的进程有效。如果 将一个句柄值通过某种机制传给其他进程中的线程,那么,该线程以此句柄为参数调用相关函数时就会失败。

 

原文地址:https://www.cnblogs.com/Lune-Qiu/p/9042246.html