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

     

  • 相关阅读:
    EXCel操作类
    C#中的excel操作类//集合了几个别人的类
    企业库 AccessHelper
    企业库 SqlHelper
    ASP.NET Excel操作类
    WPF使用MVVM(一)属性绑定
    WPF使用ttf图标字体
    WPF自定义界面WindowChrome
    WPF使用IconFont中的Symbol图标
    字体图标查看工具(Character Map UWP)
  • 原文地址:https://www.cnblogs.com/Lune-Qiu/p/9042246.html
Copyright © 2020-2023  润新知