zoukankan      html  css  js  c++  java
  • 一. 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 函数需要以它作为参数,以便系统知道要操作哪一个内核对象。为了使系统稳定,这些句柄是进程相关的,也就是仅对创建该内核对象的进程有效。如果 将一个句柄值通过某种机制传给其他进程中的线程,那么,该线程以此句柄为参数调用相关函数时就会失败。

     

  • 相关阅读:
    .NET XmlNavigator with Namespace
    编程要素
    【FOJ】1962 新击鼓传花游戏
    【POJ】1389 Area of Simple Polygons
    【POJ】2482 Stars in Your Window
    【HDU】3265 Posters
    【HDU】1199 Color the Ball
    【HDU】3642 Get The Treasury
    【HDU】4027 Can you answer these queries?
    【HDU】1542 Atlantis
  • 原文地址:https://www.cnblogs.com/Lune-Qiu/p/9042246.html
Copyright © 2011-2022 走看看