(深入探讨线程设计)以及MsgWaitForMultipleObjects函数的使用要点

关于MsgWaitForMultipleObjects 的使用[主线程中WaitForSingleObject/WaitForMultipleObjects的替代方案] 在这两天的工作中,遇到了一个问题,在主进程中通过 CreateProcess 打开另外一个进程,然后使用 WaitForSingleObject 来进行调用进程的阻塞,等待被调用进程的返回,但是在被调用进程运行期间,如果对调用进程操作,将会出现一些奇怪的现象,就像是所有在开启子进程时的各种事件全部被快速地执行了。如下图 Demo 所示: 这是由于 WaitForSingleObject 阻塞... 阅读详情
 

使用多线程技术可以显著地提高程序性能,本文就讲讲在程序中如何使用工作线程,以及工作线程与主线程通讯的问题。

一 创建线程

       使用MFC提供的全局函数AfxBeginThread()即可创建一个工作线程。线程函数的标准形式为 UINT MyFunProc(LPVOID );此函数既可以是全局函数,也可以是类的静态成员函数。之所以必须是静态成员函数,是由于类的非静态成员函数,编译器在编译时会自动加上一个this指针参数,如果将函数设置为静态的成员函数,则可以消除this指针参数。如果想在线程函数中任意调用类的成员变量(此处指的是数据成员,而不是控件关联的成员变量),则可以将类的指针作为参数传递给线程函数,然后经由该指针,就可以调用类的成员变量了。

//线程函数,类的静态成员函数

 

UINT CThreadTest::TH_SetProgress(LPVOID lpVoid)

{

       CThreadTest *pTest=(CThreadTest *)lpVoid;

       pTest->SetProgress();

       return 0;

}


//类的成员函数,此函数执行实际的线程函数操作,却可以自如的调用成员数据

void CThreadTest::SetProgress()
{
int nCount=0;
       while (1)
       {
              m_progress.SetPos(nCount); //设置进度条进度
//            this->SendMessage(WM_SETPROGRESSPOS,nCount,0);//也可以采用这种方式设置
              nCount++;
              if (g_exitThread)
              {
                    return;
              }
              Sleep(200);
       }
}


 

二 线程函数体的设计

有过多线程设计经验的人都有体会,多线程设计最重要的就是要处理好线程间的同步和通讯问题。如解决不好这个问题,会给程序带来潜藏的隐患。线程的同步可以利用临界区、事件、互斥体和信号量来实现,线程间的通讯可利用全局变量和发消息的形式实现。其中事件和临界区是使用得比较多的工具。请看下面的线程函数体:

UINT AnalyseProc(LPVOID   lVOID)
{

       if(WAIT_OBJECT_0== WaitForSingleObject(m_eventStartAnalyse.m_hThread,INFINITE))
       {
              while (WAIT_OBJECT_0 == WaitForSingleObject(m_eventExitAnalyse.m_hThread,0))
              {

                     DWORD dRet=WaitForSingleObject(m_eventPause.m_hThread,0);

                     if (dRet == WAIT_OBJECT_0)
                     {
                           //暂停分析
                           Sleep(10);}                     }
                     else if (dRet == WAIT_TIMEOUT)
                     {
                            //继续分析
                           //
                     }

              }
       }
       return 0;

}


 

上面的线程函数用到了三个事件变量eventStartAnalyse、eventExitAnalyse和eventPause,分别用来控制线程函数的启动、退出以及暂停。再配以WaitForSingleObject函数,就可以自如的控制线程函数的执行,这是在线程函数体内应用事件变量的典型方式,也是推荐的方式。

无论是工作线程还是用户界面线程,都有消息队列,都可以接收别的线程发过来的消息也可以给别的线程发送消息。给工作线程发消息使用的函数是PostThreadMessage()。此函数的第一个参数是接收消息的线程的ID。此函数是异步执行的,机制和PostMessage一样,就是把消息抛出后就立即返回,不理会消息是否被处理完了。

这里还有着重强调一点,线程消息队列是操作系统帮我们维护的一种资源,所以它的容量也是有限制的。笔者曾经做过实验,在5~6秒事件内调用PostThreadMessage往线程消息队列里发送5万多条消息,可是由于线程函数处理消息的速度远慢于发送速度,结果导致线程消息队列里已经堆满了消息,而发送端还在发消息,最终导致消息队列溢出,很多消息都丢失了。所以,如果你要在短时间内往线程消息队列里发送很多条消息,那就要判断一下PostThreadMessage函数的返回值。当消息队列已经溢出时,此函数返回一个错误值。根据返回值,你就可以控制是否继续发送。

工作线程给主线程发消息使用的是SendMessage和PoseMessage函数。这两个函数的区别在于SendMessage函数是阻塞方式,而PoseMessage函数是非阻塞方式。如果不是严格要求工作线程与主线程必须同步执行,则推荐使用PoseMessage。不要在线程函数体内操作MFC控件,因为每个线程都有自己的线程模块状态映射表,在一个线程中操作另一个线程中创建的MFC对象,会带来意想不到的问题。更不要在线程函数里,直接调用UpdataData()函数更新用户界面,这会导致程序直接crash。而应该通过发送消息给主线程的方式,在主线程的消息响应函数里操作控件。上面提到的SetProgress函数和AnalyseProc函数均为线程函数,但它们都不能接收别的线程发过来的消息,虽然它们都可以给主线程发消息。它们要想能够接收别的线程发过来的消息,则必须调用GetMessage或PeekMessage函数。这两个函数的主要区别在于:

GetMessage函数可以从消息队列中抓取消息,当抓取到消息后,GetMessage函数会将此条消息从消息队列中删除。而且,如果消息队列中没有消息,则GetMessage函数不会返回,CPU转而回去执行别的线程,释放控制权。GetMessage返回的条件是抓取的消息是WM_QUIT。

PeekMessage函数也可以从消息队列中抓取消息,如果它的最后一个参数设置为PM_NOREMOVE,则不从消息队列中删除此条消息,此条消息会一直保留在消息队列中。如果它的最后一个参数是PM_REMOVE,则会删除此条消息。如果消息队列中没有消息,则PeekMessage函数会立刻返回,而不是像GetMessage一样就那样等在那儿。PeekMessage函数就像是窥探一下消息队列,看看有没有消息,有的话就处理,没有就离开了。这一点也是两个函数的最大不同。下面的代码演示了在线程函数中使用这两个函数的三种方式,这三种方法可以达到同样的效果:

void CThreadTest::SetSlider()
{

 // 在线程函数里启动一个时钟,每50毫秒发送一个WM_TIMER消息

       int nTimerID=::SetTimer(NULL,1,50,NULL);

        int nSliderPos=0;

 

       MSG msg;

       while (1)

       {

//方式一    使用GetMessage函数  

/*           if (::GetMessage(&msg,NULL,0,0))

              {
                   switch(msg.message)
                     {

                     case WM_TIMER:

                            {

                                   nSliderPos++;

                         ::SendMessage(this->m_hWnd,WM_SETSLIDERPOS,nSliderPos,0);

                            }                         

                            break;

                     case WM_QUIT_THREAD: //自定义消息

                            {

                                   ::KillTimer(NULL,1);

                                   return;
                            }                  

                         break;

                     default:

                         break;
                     }

              }    

 

 */

 

//方式二   使用PeekMessage函数  

 

/*           if (::PeekMessage(&msg,NULL,0,0,PM_REMOVE))
              {
                     switch(msg.message)
                     {
                   case WM_TIMER:
                            {

                                   nSliderPos++;

                                   ::SendMessage(this->m_hWnd,WM_SETSLIDERPOS,nSliderPos,0);
                            }                         

                            break;

                     case WM_QUIT_THREAD: //自定义消息
                            {
                                  ::KillTimer(NULL,1);

                                   return;
                            }                  

                         break;

                     default:

                         break;

                     }

              }

              else

              {

                       //必须有此操作,要不然当没有消息到来时,线程函数相当于陷

//入空循环,cpu的占有率会飙升

                     Sleep(20);

              }

*/


 

//方式三   同时使用PeekMessage和GetMessage函数  

 

   if (::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))
              {
                     if(::GetMessage(&msg,NULL,0,0))
                     {
                            switch(msg.message)
                            {
                            case WM_TIMER:
                                   {
                                      nSliderPos++;   
                                       ::SendMessage(this->m_hWnd,WM_SETSLIDERPOS,nSliderPos,0);
                                   }                         
                                   break;
                            case WM_QUIT_THREAD: //自定义消息
                                   {
                                          ::KillTimer(NULL,1);
                                          return;
                                   }                  
                                   break;
                            default:
                                   break;
                            }
                     }
              }
              else
              {
                    Sleep(20);
              }
       }
}


           前面已经介绍过了,不建议线程函数里用SendMessage给主线程发消息,因为这个函数是同步操作,就是如果SendMessage函数不执行完,是不会返回的,这样线程函数就无法继续执行。有时这种操作容易导致工作线程和主线程死锁,这个我们后面会谈到,会介绍一种解决方法。

三 线程的退出

线程的退出有多种方式,比如可以调用TerminateThread()函数强制线程退出,但不推荐这种方式,因为这样做会导致线程中的资源来不及释放。最好的也是推荐的方式,是让线程函数自己退出。就像上面介绍的SetProgress()函数中,用全局变量g_exitThread使线程退出。

而AnalyseProc用WAIT_OBJECT_0 ==WaitForSingleObject(m_eventExitAnalyse.m_hThread,0)这种方式来退出线程,还有在SetSlider函数中利用发送自定义消息WM_QUIT_THREAD的方式令线程退出。这些都是可以使用的方法。

       当主线程要退出时,为了能保证线程的资源能全部地释放,主线程必须等待工作线程退出。线程对象和进程对象一样,也是内核对象,而且线程对象的特点是当线程退出时,线程内核对象会自动变为有信号状态,能够唤醒所有正在等待它的线程。我们通常都习惯于使用WaitForSingleObject等函数来等待某个内核对象变为有信号状态,但是我想说的是,在主线程中不要使用WaitForSingleObject和WaitForMultipleObjects两个函数等待线程退出,其原因就是有导致程序死锁的隐患,特别是线程函数里调用了SendMessage或是直接操作了MFC对象,更易出现此种现象。下面的函数是一个在主线程中用来等待SetProgress()线程函数退出的函数:

 

//退出线程

void CThreadTest::OnButton2() {        g_exitThread=TRUE; //设置全局变量为真,令线程退出 #if 1        WaitForSingleObject(m_pThread1->m_hThread,INFINITE); //无限等待 #else        DWORD dRet;        MSG msg;        while (1)        {               dRet=::MsgWaitForMultipleObjects(1,&m_pThread1->m_hThread,FALSE,INFINITE,QS_ALLINPUT);               if (dRet == WAIT_OBJECT_0+1)               {                     while (PeekMessage(&msg,NULL,0,0,PM_REMOVE))                      {                             TranslateMessage(&msg);

                            DispatchMessage(&msg);                      }               }               else               {                      break;               }        } #endif    }


 

在上面的函数中我用#if #else #endif这组预编译指令控制函数的执行代码,如果我令#if 1,则执行WaitForSingleObject函数,如果我令#if 0,则执行DWORD dRet路径。首先令#if  1,测试会发现,程序死锁了。原因是当程序执行到WaitForSingleObject函数时,主线程挂起,等待线程函数退出,此时CPU切换到线程函数体内执行,如果执行到if (g_exitThread)处,则线程函数顺利退出,可如果执行到m_progress.SetPos(nCount)处,由于SetPos函数是在主线程中完成的操作,Windows是基于消息的操作系统,很多操作都是靠发消息完成的,由于主线程已经挂起,所以没有机会去消息队列中抓取消息并处理它,结果导致SetPos函数不会返回,工作线程也被挂起,典型的死锁。如果不用m_progress.SetPos,而改用this->SendMessage(…),其结果是一样的。此时如果用了PostMessage,则工作线程会顺利退出,因为PostMessage是异步执行的。由此可见,在主线程中用WaitForSingleObject等待工作线程退出是有很大隐患的。

       为解决这一问题,微软特提供了一个MsgWaitForMultipleObjects函数,该函数的特点是它不但可以等待内核对象,还可以等消息。也就是当有消息到来时,该函数也一样可以返回,并处理消息,这样就给了工作线程退出的机会。

DWORD MsgWaitForMultipleObjects(

DWORD nCount, //要等待的内核对象数目

LPHANDLE pHandles, //要等待的内核对象句柄数组指针

BOOL fWaitAll, //是等待全部对象还是单个对象

DWORD dwMilliseconds,//等待时间 

DWORD dwWakeMask );//等待的消息类型


 

下面就详解一下该函数的参数使用方法:

DWORD nCount:要等待的内核对象的数目。如果等待两个线程退出,则nCount=2;

LPHANDLE pHandles:要等待的内核对象句柄数组指针。

 

如果只要等待一个线程退出,则直接设置该线程句柄的指针即可:

MsgWaitForMultipleObjects(1,&m_pThread->m_hThread,…)

 

如果要等待两个线程退出,则使用方法为:

HANDLE hArray[2]={ m_pThread1->m_hThread , m_pThread2->m_hThread };

MsgWaitForMultipleObjects(2,hArray,…)

 

BOOL fWaitAll: TRUE-表示只有要等待的线程全部退出后,此函数才返回,

               FALSE-表示要等待的线程中任意一个退出了,或是有消息到达了,此函数均会返回。

在上面的OnButton2()函数中,我要等待一个线程退出,将fWaitAll设置为FALSE,目的是无论是线程真的退出了,还是有消息到达了,该函数都能返回。

如果将该fWaitAll设置为TRUE,那么函数返回的唯一条件是线程退出了,即便是有消息到来了,该函数也一样不会返回。

 

DWORD dwMilliseconds:等待的事件,单位是毫秒。可以设置为INFINITE,无穷等待

 

DWORD dwWakeMask:等待的消息类型,通常可以设置为QS_ALLINPUT。此宏表示的是可以等待任意类型的消息。当然,也可以指定等待的消息类型。

 

#define QS_ALLINPUT        (QS_INPUT         | \

                            QS_POSTMESSAGE   | \

                            QS_TIMER         | \

                            QS_PAINT         | \

                            QS_HOTKEY        | \

                            QS_SENDMESSAGE)


 

返回值:DWORD dRet 通过函数返回值,可以得到一些有效信息。函数返回值依fWaitAll设置的不同而有所不同。下面是函数返回值的几种常见类型:

dRet = 0xFFFFFFFF :   表示函数调用失败,可用GetLastError()得到具体的出错信息;

dRet =WAIT_OBJECT_0+nCount:表示有消息到达了;

 

如果fWaitAll设置为TRUE

dRet = WAIT_OBJECT_0,表示所有等待的核心对象都激发了,或是线程都退出了;

如果fWaitAll设置为FALSE

dRet = WAIT_OBJECT_0 ~ WAIT_OBJECT_0+nCount-1:表示等待的内核对象被激发了,index=dRet - WAIT_OBJECT_0,表示hArray[]数组中索引为index的那个对象被激发了。

 

当函数由于消息到来而返回,则需要用户主动去消息队列中将消息抓取出来,然后派发出去,这样该消息就会被处理了。其具体的操作就是:

while (PeekMessage(&msg,NULL,0,0,PM_REMOVE))

{

       TranslateMessage(&msg);

       DispatchMessage(&msg);

}


 

下面再看一个用这个函数等待两个线程退出的例子:

//关闭线程1和2

void CThreadTest::OnButton6() {       …        …        DWORD dRet=-2;        HANDLE hArray[2];        hArray[0]=m_pThread1->m_hThread;        hArray[1]=m_pThread2->m_hThread;        MSG msg;        int nExitThreadCount=0;       //标记已经有几个线程退出了        BOOL bWaitAll=FALSE;        int nWaitCount=2;    //初始等待的线程数目        while (1)        {               dRet=MsgWaitForMultipleObjects(nWaitCount,hArray,bWaitAll,INFINITE,QS_ALLINPUT);               if (dRet == WAIT_OBJECT_0+ nWaitCount)               {                      TRACE("收到消息,函数返回值为%d \n",dRet);                      while (PeekMessage(&msg,NULL,0,0,PM_REMOVE))                      {                             TranslateMessage(&msg);                             DispatchMessage(&msg);                      }                              }               else if (dRet >= WAIT_OBJECT_0 && dRet < WAIT_OBJECT_0+ nWaitCount)               {                      nExitThreadCount++;                      if (nExitThreadCount == 1)                      {                             TRACE("一个线程退出了\n");                             int nIndex=dRet-WAIT_OBJECT_0;                             hArray[nIndex]=hArray[nWaitCount-1];                             hArray[nWaitCount-1]=NULL;                             nWaitCount--;                      }                      else                      {                             TRACE("两个线程都退出了\n");                             break;                      }               }               else               {                      DWORD dErrCode=GetLastError();                      …                      break;               }        } }

 

 

 


 

在上面这个例子中,我将bWaitAll设置为FALSE,目的是当我要等待的两个线程中由一个退出了,或是有消息到来了,此函数都可以退出。如果我将此参数设置为TRUE,那么,当且仅当我要等待的两个线程均退出了,这个函数才会返回,这种使用方法有是程序陷入死锁的危险,故应避免。无论是等待一个还是多个线程,只需将此参数设置为FALSE即可,然后通过函数返回值判断究竟是那个返回了,还是消息到达了即可。这一要点前面已有陈述,此处再强调一遍。

通过函数返回值可以得知究竟哪个线程退出了,当要等待的两个线程中的一个已经退出后,则应该从新设置等待函数的参数,对等待的句柄数组进行整理。

这组语句就是用来从新设置参数的,其过程就是将等待的总数目减一,并将刚退出的线程的句柄设置为NULL,移到数组的最末位置。

 

{

int nIndex=dRet-WAIT_OBJECT_0;

hArray[nIndex]=hArray[nWaitCount-1];

hArray[nWaitCount-1]=NULL;

nWaitCount--;

}


上面介绍了线程函数的设计以及在主线程中等待工作线程退出的方法,着重介绍了MsgWaitForMultipleObjects函数的使用要点,希望对大家有所帮助,也希望大家能提宝贵意见,补我之不足,愿与大家共同进步。


使用MsgWaitForMultipleObjects在主线程等待子线程退出 使用MsgWaitForMultipleObjects在主线程等待子线程退出 在主线程中使用WaitForSingleObject等待子线程的退出,涉及到sendmessage相关的消息驱动函数就会造成死锁的问题。在这种场景下,就需要使用MsgWaitForMultipleObjects来处理了。 static void WaitObjectAndMsg(HANDLE hHandle) { while (TRUE) { DWORD ret; MSG msg; 阅读详情

相关推荐

MsgWaitForMultipleObjects函数的一种用法

工程是VS2010,具体描述了如何在主线程中用MsgWaitForMultipleObjects等待子线程结束的同时,处理子线程发送的界面消息。

MsgWaitForMultipleObjects

用户模式:WaitForMultipleObject 内核模式:MsgWaitForMultipleObjects 用户模式的线程同步机制效率高,如果需要考虑线程同步问题,应该首先考虑用户模式的线程同步方法。但是,用户模式的线程同步有限制,对于多个进程之间的线程同步,用户模式的线程同步方法无能为力。这时,只能考虑使用内核模式。 用户模式与内核模式线程同步机制比较:

Super_Desk的博客 3025

YOLOv5 改进点总览:轻量化、高精度的背后有哪些魔改?

YOLOv5是由Ultralytics团队开发的高效目标检测模型,其核心改进包括:主干网络采用CSPDarknet53提升梯度传播效率;特征融合使用PANet增强小目标识别;输出结构采用解耦头设计优化定位和分类任务。此外,YOLOv5通过CIoU损失函数、Mosaic数据增强和SimOTA标签分配机制进一步提升性能。模型支持多种推理格式(ONNX/TensorRT等),并具备自动锚框匹配和多尺度训练功能,使其在轻量化(yolov5s仅7MB)与高性能(最高51.2% mAP)间取得平衡,但缺乏正式论文支撑是

guoguozgw的博客 1805

MsgWaitForMultipleObjects函数

MsgWaitForMultipleObjects函数该函数的特点是它不但可以等待内核对象,还可以等消息。也就是当有消息到来时,该函数也一样可以返回,并处理消息,这样就给了工作线程退出的机会。<br /><br />DWORD MsgWaitForMultipleObjects(<br />DWORD nCount, //要等待的内核对象数目<br />LPHANDLE pHandles, //要等待的内核对象句柄数组指针<br />BOOL fWaitAll, //是等待全部对象还是单个对象<br />D

fendouzhe的专栏 2038

【技术解析】AlexNet——从ReLU到Dropout的深度学习关键突破

本文深入解析AlexNet在深度学习领域的关键突破,重点探讨了ReLU激活函数和Dropout技术的革命性应用。作为卷积神经网络的里程碑,AlexNet通过ReLU解决梯度消失问题,利用Dropout有效防止过拟合,为现代深度学习模型奠定了重要基础。这些创新技术至今仍在计算机视觉和自然语言处理等领域发挥重要作用。

weixin_33719619的博客 272

fNIRS 公开数据集整理

关注“心仪脑”查看更多脑科学知识的分享。 关键词:数据整理、fNIRS 之前我们向大家推送了 Public Neuroscience Dataset 系列主题的第一期:《EEG公开数据集》,我们同时意识到大家对于神经科学公开数据集的需求。本期是该系列主题的第二期,我们在延续第一期初衷的基础上,整理了功能性近红外光谱(fNIRS)技术的公开数据集。 fNIRS 是一种非侵入性、便携且价格低廉的光学神经成像技术,它具有可观的空间和时间分辨率。因fNIRS技术问世较EEG晚了许多,所以应用此技术开展研究发.

psybrain的博客 4271

线程函数的设计以及MsgWaitForMultipleObjects函数的使用要点

<br />ref : http://blog.youkuaiyun.com/qingfeng_happy5/archive/2008/12/14/3515283.aspx<br /> <br />线程函数的设计以及MsgWaitForMultipleObjects函数的使用要点<br /> <br />使用多线程技术可以显著地提高程序性能,本文就讲讲在程序中如何使用工作线程,以及工作线程与主线程通讯的问题。<br /> <br />一 创建线程<br /> <br />       使用MFC提供的全局函数AfxBe

stwstw0123的专栏 643

Win11系统重装全记录:从备份Office激活到搞定RAID阵列,我的微星笔记本复活记

本文详细记录了Win11系统重装的完整过程,从数据备份到RAID阵列配置,特别针对微星笔记本的特殊硬件驱动问题提供了解决方案。文章涵盖了Office激活状态备份、BIOS设置、驱动安装顺序等关键步骤,帮助用户顺利完成系统重装并优化性能。

weixin_31421613的博客 309

【MFC】主线程等待子线程退出函数(MsgWaitForMultipleObjects)

01、目录 目录01、目录02、函数介绍2.1 函数创建2.2 函数返回值2.3 函数使用事项03、示例04、小结 02、函数介绍 MsgWaitForMultipleObjects 功能:阻塞时仍可以响应消息,“对象被激发”或“消息到达队列”时被唤醒而返回 相关API:WaitForMultipleObjects(),此API接收到消息后不会被唤醒和返回。 MsgWaitForMultipleObjects()多接收一个参数,允许指定哪些消息是观察对象。 2.1 函数创建 DWORD MsgWait

Cain Xcy的博客 2532

放弃FFT全频计算!在STM32F407上用DFT单点计算提速50%的优化技巧

本文详细介绍了在STM32F407上通过DFT单点计算实现比FFT快50%的优化技巧,特别适用于生物阻抗分析、振动监测等只需关注单一频点的嵌入式场景。通过预计算相位角、查表法和混合精度计算等策略,显著提升计算效率和内存利用率,同时保持高精度。

weixin_28738397的博客 191

Ubuntu系统下主板GPU温控pwm风扇

在Linux系统下——以Ubuntu为例,配置主板sys_fan等接口连接pwm温控风扇转速根据NVIDIA GPU温度,为服务器GPU等无主动散热显卡提供散热。

罗思哲的博客 5032

线程函数的设计以及MsgWaitForMultipleObjects函数的使用要点 ----- 转

使用多线程技术可以显著地提高程序性能,本文就讲讲在程序中如何使用工作线程,以及工作线程与主线程通讯的问题。 一创建线程 使用MFC提供的全局函数AfxBeginThread()即可创建一个工作线程。线程函数的标准形式为UINTMyFunProc(LPVOID );此函数既可以是全局函数,也可以是类的静态成员函数。之所以必须是静态成员函数,是由于类的非静...

diaoqu4574的博客 182

LoRa无线通信技术介绍(六)SX127X芯片收发程序讲解

接收LORA芯片SX127X的收发程序。

DT程序员的博客 1751

【MFC】 使用MsgWaitForMultipleObjects解决后台线程退出时SendMessage响应问题

问题 某些MFC后台线程可能在主程序退出时仍然处于工作状态。主程序退出前,一般需要等待后台线程先退出。处理这类问题的机制通常是,主程序在OnClose()函数中通过激活事件通知后台线程退出。 在这种情况下,后台线程退出时调用SendMessage()函数发送消息,主程序已不能响应。因为,此时主程序进入OnClose()函数后,消息响应机制已经关闭。 使用WaitForSingleObject...

sunriver2000的专栏 1643

【大语言模型 85】IA3与其他PEFT方法对比

在大语言模型时代,参数高效微调(PEFT)技术成为了降低微调成本的关键。本文深入解析IA3(Infused Adapter by Inhibiting and Amplifying Inner Activations)方法,并与LoRA、AdaLoRA、Prefix Tuning等主流PEFT技术进行全面对比,通过理论分析、代码实现和实验验证,帮助读者理解不同PEFT方法的优劣势和适用场景,为实际项目中的技术选型提供科学依据。

熵数实验室 753

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

在用户线程/主线程中推荐MsgWaitForMultipleObjects代替WaitForSingleObject和WaitForMultipleObjects()函数

多线程同步,采用WaitForSingleObject和WaitForMultipleObjects()函数出现卡死现象,采用MsgWaitForMultipleObjects代替可以解决此类问题。

cbNotes的专栏:不积硅步,无以至千里;不积小流,无以成江海! 7866

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

上一篇: (完整版)原因可能是堆被损坏,这也说明 中或它所加载的任何DLL 中有bug】的解决...
下一篇: 开源日志库<log4cplus+VS2008使用>整理
雪岢奇
博客等级 码龄17年 310粉丝 60原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值