Windows线程池

上一篇博文我们介绍了IO完成端口。得知IO完成端口可以非常智能的分派线程。但是IO完成端口仅对等待它的线程进行分派,创建和销毁线程的工作仍然需要我们自己来做。

我们自己也可以创建线程,但是涉及到线程的编码操作比较复杂,容易出现差错。为了简化程序员的工作,Windows提供了一个线程池机制来简化线程的创建、销毁以及日常管理。这个新线程池可能不适用于所有的情况,但大多数情况下它都能够满足我们的需要。

这个线程池能够帮助我们做一下事情:

一:以异步的方式调用一个函数。

二:每隔一段时间调用一个函数。

三:当内核对象触发时调用一个函数。

四:当异步IO请求完成时调用一个函数。

我们将在后面一一介绍上面各项。

一:以异步方式调用函数。

让线程池执行的函数需要遵循一下原型:

  1. VOID NTAPI ThreadFunc(
  2. PTP_CALLBACK_INSTANCE pInstance,
  3. PVOID pvContext);

定义了线程池线程入口函数,就需要提交请求让线程池执行该函数:

  1. BOOL TrySubmitThreadpoolCallback(
  2. PTP_SIMPLE_CALLBACK pfnCallback,
  3. PVOID pvContext,
  4. PTP_CALLBACK_ENVIRON pche);

该函数将一个工作项添加到线程池队列中。若调用成功,则返回true。否则返回false。

pfnCallback表示线程池线程入口函数。即我们上面定义的函数。

pvContext是传给线程入口函数的参数。

pche可以先传给它NULL。在后面我们还会有详细的介绍。

当我们提交一个请求后,线程池就会创建一个默认的线程池并让线程池的一个线程来调用回调函数。并不需要我们手动调用CreateThread。当线程从入口函数返回时,并不会销毁而是返回到线程池。线程池会不断重复使用各个线程,而不会频繁销毁和新建线程。这显著的提高了性能。

在某些情况下,如内存不足时TrySubmitThreadpoolCallback可能会失败。第一次调用TrySubmitThreadpoolCallback时,系统会在内部分配一个工作项。如果打算提交大量的工作项,出于性能和内存使用方面的考虑,应该手动创建工作项然后多次提交它。

下面的函数创建一个工作项:

  1. PTP_WORK CreateThreadpoolWork(
  2. PTP_WORK_CALLBACK pfnWorkHandler,
  3. PVOID pvContext,
  4. PTP_CALLBACK_ENVIRON pche);

pfnWorkHandler是一个函数指针,当线程池中的线程最终对工作项进行处理时会调用该函数。该函数必须遵循一下函数原型:

  1. VOID CALLBACK WorkCallback(
  2. PTP_CALLBACK_INSTANCE Instance,
  3. PVOID Context,
  4. PTP_WORK Work);

pvContext是传给pfnWorkHandler的参数。

我们可以调用SubmitThreadpoolWork向线程池提交一个请求:

  1. VOID SubmitThreadpoolWork(PTP_WORK pWork);

如果我们项取消已经提交的工作项或是等待工作项处理完毕。可以调用以下函数:

  1. VOID WaitForThreadpoolWorkCallbacks(
  2. PTP_WORK pWork,
  3. BOOL bCancelPendingCallbacks);

此函数将线程挂起,直到工作项处理完毕。

pWork指向工作项。此工作项可以是CreateThreadpoolWork和SubmitThreadpoolWork来创建和提交的。如果工作项尚未被提交,那么等待函数立即返回。

如果传入true给bCancelPendingCallbacks,那么WaitForThreadpoolWorkCallbacks会试图取消pWork标识的工作项。如果线程正在处理此工作项,则不会取消,而等待工作项处理完毕后返回。如果工作项还未被处理,函数会将此工作项标记为已取消,然后立即返回。

如果传入false给bCancelPendingCallbacks那么WaiForThreadpoolWorkCallbacks会将线程挂起,直到工作项处理完毕。

如果用一个PTP_WORK提交了多个工作项,传给bCancelPendingCallbacks为false,那么等待函数会等待所有的工作项都被处理完毕。

当不需要一个工作项时,可以调用CloseThreadpoolWork。

  1. VOID CloseThreadpoolWork(PTP_WORK pwk);

看例子:该例展示了如何使用线程池的工作项。

点击开始提交四次同一个工作项。

当回调函数返回时,程序将向程序发送自定义的TASK_COMPELETED消息。然后该消息处理函数将项下拉列表添加一项。

在MFC中手动添加消息需要一下步骤:

1:定义消息:如#defined TASK_COMPELETED WM_USER+1

2:定义消息处理函数原型:

  1. afx_msg LRESULT OnTaskCompeleted(WPARAM wparam,LPARAM lparam);

3:调用PostMessage发送消息。

  1. PostMessage(this,TASK_COMPELETED,0,(LPARAMm_CurrentTask);

4:在消息映射表中添加消息和消息处理函数的映射:

代码如下:

开始按钮消息处理函数:

  1. void CThreadpoolexap1Dlg::OnBnClickedBtnStart()
  2. {
  3. m_WorkItem=CreateThreadpoolWork(ThreadFunc,this,NULL);
  4. if(m_WorkItem==NULL)
  5. {
  6. MessageBox(TEXT("工作项创建失败"));
  7. return ;
  8. }
  9. // TODO: 在此添加控件通知处理程序代码
  10. SubmitThreadpoolWork(m_WorkItem);
  11. SubmitThreadpoolWork(m_WorkItem);
  12. SubmitThreadpoolWork(m_WorkItem);
  13. SubmitThreadpoolWork(m_WorkItem);
  14. }

线程入口函数:

  1. VOID NTAPI CThreadpoolexap1Dlg::ThreadFunc( PTP_CALLBACK_INSTANCE Instance,PVOID Context,PTP_WORK Work )
  2. {
  3. CThreadpoolexap1Dlg*pdlg=(CThreadpoolexap1Dlg*)Context;
  4. InterlockedIncrement(&pdlg->m_CurrentTask);
  5. DWORD num=pdlg->m_CurrentTask;
  6. CString s;
  7. s.Format(TEXT("【%d】任务%d开始运行!"),GetCurrentThreadId(),pdlg->m_CurrentTask);
  8. pdlg->m_list.InsertString(-1,s);
  9. pdlg->PostMessage(TASK_COMPELETED,0,(LPARAM)num);
  10. }

自定义消息处理函数:

  1. LRESULT CThreadpoolexap1Dlg::OnTaskCompeleted( WPARAM wparam,LPARAM lparam )
  2. {
  3. DWORD num=(DWORD)lparam;
  4. CString s;
  5. s.Format(TEXT("任务 [%d]执行完毕!"),num);
  6. m_list.InsertString(-1,s);
  7. return 0;
  8. }

执行结果:

多次点击开始键发现确实是还是那四个线程在执行。

修改代码:提交六个工作项:

多次点击开始参与执行的仍然是原来的线程。说明在线程池中线程是重用的。

情形二:每隔一段时间调用一个函数

有时候应用程序需要在某些时间执行某些任务。Windows提供了可等待计时器对象,它使我们我们可以非常方便的得到一个时间通知。我们可以为每个需要执行基于时间的任务创建一个可等待的计时器对象,但这是不必要的。线程池函数为我们解决了这些事情。

为了将一个工作项安排在某个时间执行,我们必须定义一个回调函数。该函数会在某个时刻被调用。回调函数原型为:

  1. VOID CALLBACK TimeoutCallback(
  2. PTP_CALLBACK_INSTANCE pInstance,
  3. PVOID pvContext,
  4. PTP_TIMER pTimer);

然后调用下面的函数来通知线程池应在何时调用我们的函数:

  1. PTP_TIMER CreateThreadpoolTimer(
  2. PTP_TIMER_CALLBACK pfnTimerCallback,
  3. PVOID pvContext,
  4. PTP_CALLBACK_ENVIRON pcbe);

这个函数与前面介绍的CreateThreadpoolWork相似。

pfnTimerCallback是一个函数指针。指向前面介绍的回调函数TimeroutCallback。每当线程池调用pfnTimerCallback指向的函数时会将pvContext传给它,并传给pTimer一个由CreateThreadpoolTimer返回的计时器对象指针。

pvContext为传给回调函数参数。

CreateThreadpoolTimer返回计时器对象。该计时器对象由CreateThreadpoolTimer函数创建并返回。

当我们想要向线程池注册计时器时,应该调用SetThreadpoolTimer:

  1. VOID SetThreadpoolTimer(
  2. PTP_TIMER pTimer,
  3. PFILETIME pftDueTime,
  4. DWORD msPeriod,
  5. DWORD msWindowLength);

pTimer用来标识CreateThreadpoolTimer返回的计时器对象。

pftDueTime表示第一次调用回调函数是什么时候。传入一个负值表示一个相对时间。该时间相对于调用SetThreadpoolTimer的时间。传入-1表示立即调用。传入的正值以100ns为单位,从1600年的1月1日开始计算。

msPeriod表示调用回调函数的时间间隔,传入0表示只调用1次。

msWindowLength用来给回调函数的执行增加一些随机性。这使得回调函数会在当前设定的时间到当前设定的触发时间加上msWindowLength设定的时间之间触发。这对于多个计时器来说非常有用。这可以避免多个计时器间的冲突。

在设置计时器之后还可以再次调用SetThreadpoolTimer来对计时器进行修改。同时也可以调用IsThreadpoolSet来确定某个计时器是否已经被设置(即pfnDueTime不为NULL)。

  1. BOOL IsThreadpoolTimerSet(PTP_TIMER pti);

最后我们可以通过WaitForThreadpoolTimerCallbacks来等待一个计时器完成。调用CloseThreadpoolTimer来释放计时器。它们与前面介绍的WaitForThreadpoolWork和CloseThreadpoolWork相似。

例子:该例展示了如何使用线程池计时器函数。程序启动时会将m_TimeToLeft设置为10。接着调用CreateThreadpoolTimer函数来创建线程池函数计时器。并将它传给SetThreadTimer。告诉线程池从第一秒开始,以后每隔一秒调用一次回调函数。注意:由于回调函数是由线程池的线程调用,因此在类中定义时必须是static的。另外之所有回调函数会被多次调用,是因为系统内部每个一段时间会将一个工作项添加到队列中。对于这些工作项线程池可能调用多个线程进行处理。因此要注意采取必要的线程同步机制。本例中采用InterlockedDecrement函数对m_TimeToLeft进行同步。要注意InterlockedDecrement的参数是ULONG类型。传入负值会有问题。

.h文件定义:

  1. <span style="font-size:18px;">   public:
  2.     PTP_TIMER m_pTimer;
  3.     CString m_display;
  4.     ULONG m_TimeToLeft;
  5.    public:
  6.     static VOID CALLBACK TimerCallbackFunc(PTP_CALLBACK_INSTANCE pInstance,PVOID pvContext,PTP_TIMER pTimer);
  7.     afx_msg void OnBnClickedBtnOk();</span>

初始化代码:

  1. <span style="font-size:18px;">   // TODO: 在此添加额外的初始化代码
  2.     m_pTimer=CreateThreadpoolTimer(TimerCallbackFunc,this,NULL);
  3.     if(m_pTimer==NULL)
  4.     {
  5.         MessageBox(TEXT("PTP_TIMER创建失败!!"));
  6.         //return false;
  7.     }
  8.     FILETIME filetime;
  9.     LARGE_INTEGER li;
  10.     li.QuadPart=-(10000000);
  11.        filetime.dwHighDateTime=li.HighPart;
  12.     filetime.dwLowDateTime=li.LowPart;
  13.     SetThreadpoolTimer(m_pTimer,&filetime,1000,0);
  14.     CString s;
  15.     s.Format(TEXT("你还有[%d]s时间拯救地球!!"),10);
  16.     m_display=s;
  17.     UpdateData(false);</span>

按钮消息响应函数:

  1. <span style="font-size:18px;">   void CThreadpoolExap2Dlg::OnBnClickedBtnOk()
  2.    {
  3.     // TODO: 在此添加控件通知处理程序代码
  4.     static bool IsClicked=false;
  5.     if(IsClicked==false)
  6.     {
  7.         CloseThreadpoolTimer(m_pTimer);
  8.         IsClicked=true;
  9.     }
  10.     if(m_TimeToLeft>1)
  11.     {
  12.    
  13.         MessageBox(TEXT("你已拯救地球!!"));
  14.    
  15.     }
  16.     else
  17.     {
  18.    
  19.    
  20.         MessageBox(TEXT("地球已毁灭!!"));
  21.     }</span>

线程池回调函数:

  1. <span style="font-size:18px;">   VOID CALLBACK CThreadpoolExap2Dlg::TimerCallbackFunc( PTP_CALLBACK_INSTANCE pInstance,PVOID pvContext,PTP_TIMER pTimer )
  2.    {
  3.     CThreadpoolExap2Dlg*pdlg=(CThreadpoolExap2Dlg*)pvContext;
  4.    
  5.     DWORD temp=pdlg->m_TimeToLeft;
  6.    
  7.     CString s;
  8.     s.Format(TEXT("你还有[%d]s时间拯救地球!!"),temp-1);
  9.     pdlg->SetDlgItemTextW(IDC_STATIC_DISPLAY,s);
  10.     //pdlg->m_display=s;
  11.     //pdlg->UpdateData(true);
  12.    
  13.     if(temp==1)
  14.     {
  15.         CloseThreadpoolTimer(pdlg->m_pTimer);
  16.         pdlg->MessageBox(TEXT("地球已毁灭!!"));
  17.         return ;
  18.    
  19.     }
  20.     InterlockedDecrement(&pdlg->m_TimeToLeft);
  21.    }</span>

执行结果:

情形三:在内核对象触发时调用一个函数:

在实际使用中我们会发现我们会经常的等待一个内核对象被触发,触发后等待线程又会进入下一轮循环继续等待。Windows线程池提供了一些机制可以简化我们的工作。

如果我们想让内核对象被触发时执行某函数。需要进行以下步骤:

首先编写一个回调函数,它是内核对象被触发时被调用的函数。需要满足一下原型:

VOID CALLBACK WaitCallback(

PTP_CALLBACK_INSTANCE pInstance,

PVOID Context,

PTP_WAIT Wait,

TP_WAIT_RESULT WaitResult);

然后创建CreateThreadpoolWait来将一个内核对象绑定到线程池:

VOID SetThreadpoolWait(

PTP_WAIT pWaitItem,

HANDLE hObject,

PFILETIME pftTimeout);

pWaitItem用来标识CreateTheadpoolWait返回的对象。

hObject用来标识内核对象。当此对象被触发时,回调函数会被调用。

pftTimeout用来表示线程池最长应该花多少时间来等待内核对象被触发。传入0表示不用等待。传入负值表示相对时间传NULL表示无限长的时间。

线程池内部会让一个线程调用WaitForMultipleOBjecs函数。传入SetThreadpoolWait函数注册的一组句柄,并传入false给bWaitAll参数。当任何一个内核对象被触发时,线程池就会被唤醒。

当内核对象被触发或是超出等待时间时,线程池的某个线程就会调用我们的回调函数。

WaitResult用来表示WaitCallback被调用的原因。它可以是以下值:

WAIT_OBJECT_0        超时之前有对象被触发。

WAIT_TIMEOUT      由于超时导致回调函数被触发。

WAIT_ABANDONED_0   如果传入的内核对象是互斥量且被遗弃。回调函数将收到这个值。

一旦线程池调用了我们的回调函数,对应的等待项将进入不活跃状态。所谓不活跃状态:如果想让回调函数在同一个内核对象被触发时再次被调用,我们需要调用SetThreadpoolWait来再次注册。

最后我们同样可以等待一个等待项完成。这可以调用WaitForThreadpoolWaitCallbacks。还可以调用CloseThreadpoolWait来释放一个等待项的内存。

注意:不要让回调函数调用WaitForThreadpoolWork并将自己的工作项作为参数传入,这会导致死锁。

情形四:在异步IO完成时调用一个函数。

我们在上一篇博文中介绍了如何使用IO完成端口来高效的执行异步IO操作,也介绍了如何创建一个线程池并让其中的线程等待IO完成端口。这里我们将介绍线程池如何管理线程的创建和销毁。

在打开一个关联起来文件或设备时,我们必须现将该设备与线程池的IO完成端口,然后告诉线程池在异步IO完成时应该调用哪个函数。

首先我们需要定义回调函数,它需要满足一下原型:

  1. VOID CALLBACK OverlappedCompletionRoutine(
  2. PTP_CALLBACK_INSTANCE pInstance,
  3. PVOID pvContext,
  4. PVOID pOverlapped,
  5. ULONG IoResult;
  6. ULONG_PTR NumberOfBytesTransferred,
  7. PTP_IO  pIo);

当一个IO操作完成时此回调函数会被调用并得到一个指向OVERLAPPED结构的指针。此结构是我们在调用ReadFile后WriteFile时传入的。

IoResult表示IO异步操作的执行结果。如果IO请求成功,将传给回调函数NO_ERROR。

NumberOfBytesTransferred参数传入已传输的字节数。

pIo传入指向线程池IO项的指针。马上介绍。

pInstance后面会有介绍。

定义好回调函数后,我们就需要调用CreateThreadpoolIo来创建一个线程池IO对象。

  1. PTP_IO CreateThreadpoolIo(
  2. HANDLE hDevice,
  3. PTP_WIN32_IO_CALLBACK pfnIoCallback,
  4. PVOID pvContext,
  5. PTP_CALLBACK_ENVIRON pcbe);

hDevice是与IO对象相关联的设备句柄。

pfnIoCallback是前面我们介绍的回调函数指针。

pvContext当然是传给回调函数的参数。

当IO对象创建好之后,我们就可以通过下面的函数来将潜入在IO项的设备与IO完成端口关联起来。

  1. VOID StartThreadpoolIo(PTP_IO pio);

关联之后我们就可以调用ReadFile或WriteFile了。此后当异步IO请求完成后,回调函数将会被调用。

此外我们还可以调用以下函数来停止线程池调用回调函数,此后回调函数将不会被调用:

  1. VOID CancelThreadpoolIo(PTP_IO pio);

CloseThreadpoolIo将取消设备与线程池的关联:

  1. VOID CloseHandlepoolIo(PTP_IO pio);

WaitForThreadpoolIoCallbacks将等待一个待处理的IO请求我完成。

  1. VOID WaitForThreadpoolIoCallback(
  2. PTP_IO pio,
  3. BOOL bCancelPendingCallbacks);

如果传给bCancelPendingCallbacks的值为true,那么当请求完成时,回调函数不会被调用。

对线程池进行定制

在调用CreateThreadpoolWork、CreateThreadpoolTimer,CreateThreadpoolWait或CreateThreadpoolIo时,有一个PTP_CALLBACK_ENVIRON类型的参数。如果传给它NULL则表示我们会将工作项添加到默认的线程池中。一般情况下默认的线程池能够满足大多数情况下的要求。

如果我们想定制我们自己的线程池,可以调用CreateThreadpool来创建新线程池:

  1. PTP_POOL CreateThreadpool(PVOID reserved);

Reserved是保留的,传入NULL即可。

该函数返回一个PTP_POOL值,它表示新创建的线程。

此后我们就可以设置线程池中的最大线程和最小线程了。默认的线程池中线程最少为1个,最多为500。

  1. void SetThreadpoolThreadMinimum(PTP_POOL pThreadpool,DWORD cthrdMin);
  2. void SetThreadpoolThreadMaximum(PTP_POOL pThreadpool,DWORD cthrdMin);

这仅仅是告诉线程池最大和最少线程个数。但实际使用中,线程池会非常智能的决定创建或是销毁线程。只要这样做提高性能有益。

当我们不需要自己定制的线程池时可以调用CloseThreadpool销毁。

  1. VOID CloseThreadpool(PTP_POOL pThreadpool);

调用此函数后线程池所有的线程都被终止。线程池队列中所有尚未开始处理的项将被取消。

创建了线程池之后,并对线程池进行一定的配置后。我们就可以初始化线程池了,以便构造出一个回调环境。回调环境是一个PTP_CALLBACK_ENVIRON结构。该结构中包含了线程池、清理组等字段。我们可以调用InitializeThreadpoolEnvironment对其进行初始化:

  1. VOID InitializeThreadpoolEnvironment(
  2. PTP_CALLBACK_ENVIRON pcbe);

该函数会将回调环境的版本字段置为1,其余字段为0.

初始化回调环境后,还需要将其与线程池进行关联:

  1. VOID SetThreadpoolCallbackpoo(
  2. PTP_CALLBACK_ENVIRON pche,
  3. PTP_POOL pThreadPool);

该函数将回调环境的线程池字段置为pThreadpool指向的线程池。当不调用此函数时,PTP_CALLBACK_ENVIRON结构的线程池字段一直为NULL,当用这个回调环境来添加工作项时,工作项会被添加到默认的线程池中。

当我们不需要回调环境时可以调用DestroyThreadEnvironment来将其销毁:

  1. VOID DestroyThreadEnvironment(PTP_CALLBACK_ENVRION pcbe);

线程池同样提供了将自己销毁的机制。默认的线程池不会被销毁,它与进程的生命期一样长。进程结束后,Windows将负责所有的清理操作。

要想对我们自定义的线程池执行清理操作,首先需要创建一个清理组:

  1. PTP_CLEANUP_GROUP CreateThreadpoolCleanuGroup();

创建清理组后还需要将清理组和线程池相关联:

  1. VOID SetThreadpoolCallbackCleanupGroup(
  2. PTP_CALLBACK_ENVIRON pcbe,
  3. PTP_CLEANUP_GROUP ptpcg,
  4. PTP_CLEANUP_GROUP_CANCEL_CALLBACK pfng);

此函数会设置回调环境的清理组字段为ptpcg指定的清理组。

pfng指向一个回调函数。当清理组被取消时回调函数将会被调用。该回调函数必须满足一下原型:

  1. VOID CALLBACK CleanupGroupCancelback(
  2. PVOID pvObjectContext,
  3. PVOID pvCleanupContext);

当我们调用CreateThreadpoolWork,CreateThreadpoolTimer,CreateThreadpoolWait或CreateThreadpoolIo时,如果最后的指向PTP_CALLBACK_ENVIRON结构的指针不为NULL,那么所创建的项会被添加到对应的回调函数清理组中。它表示线程池中又添加了一项,需要清理。当我们调用CloseThreadpoolWork,CloseThreadpoolTimer,CloseThreadpoolWait或是CloseThreadIo时,等于是隐式的将对应项从清理组中清理。

当我们想清理线程池时可以调用:

  1. VOID CloseThreadpoolCleanupGroupMemebers(
  2. PTP_CLEANUPGROUP ptpcg,
  3. BOOL bCancelPendingCallbacks,
  4. PVOID pvCleanupContext);

如果传入false给bCancelPendignCallbacks,调用此函数的线程会一直等待直到线程池工作组中所有剩余的项当已经处理完毕为止。当传入true给bCancelPendingCallbacks时,所有已提交但未处理的工作项将直接被取消。对于每一个待处理的项,pfng指向的回调函数将会被调用。

当所有的工作项都被取消或被处理后,可以调用CloseThreadpoolCleanupGroup来释放清理组所占的资源:

    1. <span style="font-size:18px;">VOID WINAPI CloseThreadpoolCleanupGroup(
    2. PTP_CLEANUP_GROUP ptpcg);
    3. </span>

《Windows核心编程系列》十一谈谈Windows线程池的更多相关文章

  1. windows核心编程---第一章 谈谈windows中的错误处理机制

        我们写的函数会用返回值表示程序执行的正确与否,使用void,就意味着程序一定不会出错.Bool类型标识true时为真,false时为假.其他类型根据需要可以定义成不同意义.       Win ...

  2. 《windows核心编程系列》十九谈谈使用远程线程来注入DLL。

    windows内的各个进程有各自的地址空间.它们相互独立互不干扰保证了系统的安全性.但是windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作.虽然他们是为调试 ...

  3. 《windows核心编程系列》十八谈谈windows钩子

    windows应用程序是基于消息驱动的.各种应用程序对各种消息作出响应从而实现各种功能. windows钩子是windows消息处理机制的一个监视点,通过安装钩子能够达到监视指定窗体某种类型的消息的功 ...

  4. 《windows核心编程系列》二十一谈谈基址重定位和模块绑定

    每个DLL和可执行文件都有一个首选基地址.它表示该模块被映射到进程地址空间时最佳的内存地址.在构建可执行文件时,默认情况下链接器会将它的首选基地址设为0x400000.对于DLL来说,链接器会将它的首 ...

  5. 《Windows核心编程系列》二十谈谈DLL高级技术

    本篇文章将介绍DLL显式链接的过程和模块基地址重定位及模块绑定的技术. 第一种将DLL映射到进程地址空间的方式是直接在源代码中引用DLL中所包含的函数或是变量,DLL在程序运行后由加载程序隐式的载入, ...

  6. 《windows核心编程系列》二谈谈ANSI和Unicode字符集 .

    http://blog.csdn.net/ithzhang/article/details/7916732转载请注明出处!! 第二章:字符和字符串处理 使用vc编程时项目-->属性-->常 ...

  7. 《windows核心编程系列》十七谈谈dll

    DLL全称dynamic linking library.即动态链接库.广泛应用与windows及其他系统中.因此对dll的深刻了解,对计算机软件开发专业人员来说非常重要. windows中所有API ...

  8. 《windows核心编程系列》十六谈谈内存映射文件

    内存映射文件允许开发人员预订一块地址空间并为该区域调拨物理存储器,与虚拟内存不同的是,内存映射文件的物理存储器来自磁盘中的文件,而非系统的页交换文件.将文件映射到内存中后,我们就可以在内存中操作他们了 ...

  9. 《Windows核心编程系列》十四谈谈默认堆和自定义堆

    堆 前面我们说过堆非常适合分配大量的小型数据.使用堆可以让程序员专心解决手头的问题,而不必理会分配粒度和页面边界之类的事情.因此堆是管理链表和数的最佳方式.但是堆进行内存分配和释放时的速度比其他方式都 ...

  10. Windows核心编程 第十一章 线程池的使用

    第11章 线程池的使用 第8章讲述了如何使用让线程保持用户方式的机制来实现线程同步的方法.用户方式的同步机制的出色之处在于它的同步速度很快.如果关心线程的运行速度,那么应该了解一下用户方式的同步机制是 ...

随机推荐

  1. 个人网站开发***云服务器+Linux+域名***

    作为一个改变世界的程序猿,我们不应该只会埋头写程序修bug还得会点别的, 当然如果要是自己搞个网站玩玩,既可以锻炼技术,没事也可以和圈外的朋友吹吹 牛.因为水平有限,就弄一些最基础的看看喽,不喜勿喷. ...

  2. Python遍历路径下文件并转换成UTF-8编码

    http://www.cnblogs.com/wuyuegb2312/archive/2013/01/11/2856772.html 开始学Python,这篇文章来自于应用需求. os.walk很方便 ...

  3. 【APUE】文件I/O

    Linux的内核将所有外部设备都可以看做一个文件来操作.那么我们对与外部设备的操作都可以看做对文件进行操作.我们对一个文件的读写,都通过调用内核提供的系统调用:内核给我们返回一个file descri ...

  4. Python 点滴 I

    [为什么使用Python] 1. 软件质量:   Python更注重软件质量,一致性,可维护性 2. 开发效率:   相比C/C++/Java这些编译/静态语言,无需编译及链接步骤,Python所须要 ...

  5. 在matlab中生成m序列

    实验环境为matlab2013b 1.首先编写一个mseq.m文件,内容为: function[mseq]=m_sequence(fbconnection)  n=length(fbconnectio ...

  6. 【iOS系列】-iOS查看沙盒文件图文教程(真机+模拟器)

    [iOS系列]-iOS查看沙盒文件图文教程(真机+模拟器) 1:模拟器 1.1 方法1: 程序中打印一下的地址,能直接前往沙盒路径. NSString *path = [NSSearchPathFor ...

  7. Apache Flink 1.5.0 Release Announcement

    Apache Flink: Apache Flink 1.5.0 Release Announcement https://flink.apache.org/news/2018/05/25/relea ...

  8. 系统队列中的Windows错误报告

  9. js权威指南

    有很多js的细节基础不太了解,希望能通过阅读这本书查漏补缺!长期更新!

  10. 编译Android VNC Server【转】

    本文转载自:http://www.cnblogs.com/fengfeng/p/3289292.html 1,在如下地址checkout源代码,我checkout的版本为0.9.7http://cod ...