当前位置: 首页 > news >正文

网站内容管理平台怀宁县住房和城乡建设局网站

网站内容管理平台,怀宁县住房和城乡建设局网站,在哪里申请网站,人力资源网站Win32线程控制只有是围绕线程这一内核对象的创建、挂起、恢复、终结以及通信等操作#xff0c;这些操作都依赖于Win32操作系统提供的一组API和具体编译器的C运行时库函数。本篇围绕这些操作接口介绍在Windows系统下的多线程编程要点#xff0c;后续将进一步涉及多线程通信的同…Win32线程控制只有是围绕线程这一内核对象的创建、挂起、恢复、终结以及通信等操作这些操作都依赖于Win32操作系统提供的一组API和具体编译器的C运行时库函数。本篇围绕这些操作接口介绍在Windows系统下的多线程编程要点后续将进一步涉及多线程通信的同步互斥等议题。 1线程的创建CreateThread 每个线程必须拥有一个进入点函数线程从这个进入点开始运行。主线程的进入点是main/WinMain函数如果想在进程中创建一个辅助线程则必须为该辅助线程指定一个进入点函数这个函数称为线程函数。 线程函数的定义如下 typedef DWORD (WINAPI* ThreadProc)(LPVOID lpParam); // 线程函数名称ThreadProc可以是任意的 WINAPI是一个宏名在 windef.h文件中有如下的声明。 #define WINAPI __stdcall __stdcall 是Windows标准 C/C函数的调用方法。从底层上说使用这种调用方法参数的进栈顺序和标准 C调用__cdecl 方法是一样的都是从右到左但是__stdcall 采用自动清栈的方式而__cdecl 采用的是手工清栈方式。 Windows 规定凡是由它来负责调用的函数都必须定义为__stdcall 类型。 ThreadProc是一个回调函数即由Windows系统来负责调用的函数所以此函数应定义为__stdcall类型。注意如果没有显式说明的话函数的调用方法是__cdecl。 Windows创建新线程的API是CreateThread由该函数创建的线程将在调用者的虚拟地址空间内执行。函数原型如下 // The CreateThread function creates a thread to execute within the virtual address space of the calling process. // To create a thread that runs in the virtual address space of another process, use the CreateRemoteThread function. HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD SIZE_T dwStackSize,                       // initial stack size LPTHREAD_START_ROUTINE lpStartAddress,    // thread function LPVOID lpParameter,                       // thread argument DWORD dwCreationFlags,                    // creation option LPDWORD lpThreadId                        // thread identifier ); 参数一为线程的安全属性一般设为NULL表示使用默认安全属性。 参数二为线程堆栈大小一般设为NULL表示使用默认堆栈大小对应VC的/STACK:链接器选项。VC6默认的堆栈大小为1M可通过“Project SettingsàLinkàStack allocations”设置堆栈大小VC2005中可在“项目属性à配置属性à链接器à系统”中设置堆栈大小。 参数三为线程函数的地址传递函数指针或函数名ThreadProc。 参数四为传递给线程函数的参数即ThreadProc的参数。其为LPVOID类型对复杂的参数采用结构体或类按址传递。 参数五为线程创建参数例如线程创建后是否立即启动的开关选项。 参数六为内核给新创建的线程分配的线程ID号为输出参数。 用户编写多线程程序时一般关注参数三和参数四足矣其他可采用默认参数。 此函数执行成功后将返回新建线程的线程句柄。lpStartAddress参数指定了线程函数的地址新建线程将从此地址开始执行直到 return 语句返回线程运行结束把控制权交给操作系统。 线程内核对象kthread 线程内核对象就是一个包含了线程状态信息的数据结构。每一次对 CreateThread 函数的成功调用系统都会在内部为新的线程分配一个内核对象。系统提供的管理线程的函数其实就是依靠访问线程内核对象来实现管理的。在WinDbg中可通过lkd dt nt!_kthread查看线程内核对象数据结构这里涉及到线程上下文Context、使用计数Usage Count和暂停计数Suspend Count等重要概念。 1线程上下文 线程的上下文本质上是一组处理器的寄存器有正在执行程序中的指针及堆栈指针。上下文及其转换的过程根据处理器的结构不同会有所不同参考《线程的数据结构》。在WinDbg中可以通过lkd dt nt!_context命令来观察上下文的数据结构。WINNT.H中定义了_CONTEXT结构。 大约每经 20msWindows 查看一次当前存在的所有线程内核对象。在这些对象中只有一少部分是可调度的没有处于暂停状态Windows 选择其中的一个内核对象将它的CONTEXT上下文装入 CPU的寄存器这一过程称为上下文切换。 用户可调用GetThreadContext查看当前线程的用户模式的上下文信息调用SetThreadContext改变线程上下文待下次调度进CPU时生效。其中ContextFlags参数通过异或掩码指定欲查看的寄存器。_KTHREAD::ContextSwitches为线程已切换的次数。 2使用计数 Usage Count成员记录了线程内核对象的使用计数这个计数说明了此内核对象被打开的次数。线程内核对象的存在与 Usage Count 的值息息相关当这个值是0 的时候系统就认为已经没有任何进程在引用此内核对象了于是线程内核对象就要从内存中撤销。 只要线程没有结束运行Usage  Count 的值就至少为 1。在创建一个新的线程时CreateThread 函数返回了线程内核对象的句柄相当于打开一次新创建的内核对象这也会促使 Usage Count 的值加1。所以创建一个新的线程后初始状态下 Usage Count 的值是2。之后只要有进程打开此内核对象就会使Usage Count的值加1。比如当有一个进程调用OpenThread函数打开这个线程内核对象后Usage Count 的值会再次加 1。 // The OpenThread function opens an existing thread object. HANDLE OpenThread( DWORD dwDesiredAccess,  // access right BOOL bInheritHandle,    // handle inheritance option DWORD dwThreadId        // thread identifier ); 由于对这个函数的调用会使 Usage Count 的值加1所以在使用完它们返回的句柄后一定要调用 CloseHandle 函数进行关闭。关闭内核对象句柄的操作就会使 Usage Count 的值减 1。 还有一些函数仅仅返回内核对象的伪句柄并不会创建新的句柄当然也就不会影响Usage Count 的值。如果对这些伪句柄调用 CloseHandle 函数那么 CloseHandle 就会忽略对自己的调用并返回 FALSE。对进程和线程来说这些函数有 // The GetCurrentProcess function retrieves a pseudo handle for the current process. HANDLE GetCurrentProcess(VOID); // The GetCurrentThread function retrieves a pseudo handle for the current thread. HANDLE GetCurrentThread(VOID); 前面提到新创建的线程在初始状态下 Usage  Count 的值是 2。此时如果立即调用CloseHandle 函数来关闭CreateThread返回的句柄的话Usage Count 的值将减为 1但新创建的线程是不会被终止的。待线程函数返回系统会使 Usage Count 的值由1减为0线程的生命周期到此为止系统将撤销此线程内核对象释放其所占内存。 如果不关闭句柄的话Usage Count 的值将永远不会是 0系统将永远不会撤销它占用的内存这就会造成内存泄漏当然线程所在的进程结束后该进程占用的所有资源都要释放。 3暂停计数 暂停计数参考下面的多线程状态控制。 4主辅线程的执行同步 一般主线程应该后于辅助线程退出如果主线程先退出辅助线程尚在执行将会出现意想不到的结果因此主线程必须对辅助线程具有完全的控制权。 一个可执行对象有两种状态未受信nonsignaled和受信signaled状态。线程内核对象只有当线程过程运行结束时才达到受信状态。可调用WaitForSingleObject/WaitForMultipleObjects函数在线程内核对象HANDLE上等待以便主辅线程同步。 2线程的状态控制SuspendThread/ResumeThread、Sleep 线程在创建后和终止前之间的状态用户可感知或控制的状态主要有运行和暂停中断两种对应的操作为挂起Suspend和恢复Resume。 线程内核对象中的Suspend Count_KTHREAD::SuspendCount用于指明线程的暂停计数。 当调用CreateProcess创建进程的主线程或CreateThread函数时线程的内核对象被创建了它的暂停计数被初始化为1即出于暂停状态这可以阻止新创建的线程被立即调度进CPU中。因为线程初始化需要时间当线程完全初始化好了之后CreateProcess或CreateThread检查dwCreationFlags参数是否传递了CREATE_SUSPEND标志如果传递了这些函数就返回同时新线程处于暂停状态。如果尚未传递该标志那么线程的暂停计数将被递减为0。当线程的暂停计数是 0的时候该线程就进入可调度状态。 创建线程的时候若指定CREATE_SUSPEND标志则用户有机会在线程执行任何代码之前改变线程的运行环境如后面讨论的优先级。然后需要调用ResumeThread函数减少线程的暂停计数至0使线程恢复运行进入可调度状态。 // The ResumeThread function decrements a threads suspend count. When the suspend count is decremented to zero, the execution of the thread is resumed. DWORD ResumeThread( HANDLE hThread   // handle to thread ); 后续可调用SuspendThread函数来暂停一个线程的运行因为该API传递的是句柄故该函数可跨进程调用即在一个线程中暂停另一个线程。与ResumeThread相反SuspendThread函数的调用会增加线程的暂停计数。 // The SuspendThread function suspends the specified thread. DWORD SuspendThread( HANDLE hThread   // handle to thread ); 只有当线程的暂停计数是 0的时候线程才能进入可调度状态。因此必须注意SuspendThread/ResumeThread函数调用次数的匹配以便正确控制。 SuspendThread/ResumeThread函数对于线程状态的控制具有很强的针对性另外一种简单的替代方案是调用Sleep函数让调用线程睡一会儿给其他的线程一个调度机会从而提供一种线程切换缓冲机制。 // The Sleep function suspends the execution of the current thread for the specified interval. // To enter an alertable wait state, use the SleepEx function. VOID Sleep( DWORD dwMilliseconds   // sleep time ); Sleep函数在线程的while(1) 死循环中非常实用因为一个线程如果while(1)轮回则CPU使用率一般会飙升置系统与卡死状态。特殊地Sleep(0)表示调用线程主动放弃时间片的剩余部分它强制系统调度其他线程。但是系统有可能重新调度刚刚调用了Sleep的那个线程。 由于该调用本身占用时钟周期也会让当前线程放弃时间片交出控制权从而使别的线程有机会执行。当然可直接调用SwitchToThread()执行线程切换。 3线程的优先级控制GetThreadPriority/SetThreadPriority 线程的状态转换、优先级及调度方案请参考《线程的调度》。 对于一个线程我们可以调用GetThreadPriority函数来获取其优先级。 // The GetThreadPriority function retrieves the priority value for the specified thread. This value, together with the priority class of the threads process, determines the threads base-priority level. int GetThreadPriority( HANDLE hThread   // handle to thread ); 系统可以动态地调整线程的优先级当系统希望这个线程处理窗口消息、I/O调用或是系统发现3-4秒内这个线程一直迫切地需要一个时间片时它会将这个线程的优先级调整为15并可以运行双倍的时间片。对线程优先级的动态调整是通过调用SetThreadPriority函数实现的。 // The SetThreadPriority function sets the priority value for the specified thread. This value, together with the priority class of the threads process, determines the threads base priority level. BOOL SetThreadPriority( HANDLE hThread, // handle to the thread int nPriority   // thread priority level ); 对于一般的应用程序开发按照常规的优先级配置即可很少涉及优先级的操作。 4线程的停止ExitThread/TerminateThread 线程过程的结束主要有两种情形即自然终止和人为中止。 自然终止主要是指线程函数自然返回的情况是寿终正寝的圆寂。人为中止是通过暴力手段将尚未完成使命的线程谋杀是死于非命的夭折。 线程自然终止时会发生下列事件 l  在线程函数中创建的所有 C对象将通过它们各自的析构函数被正确地销毁。 l  该线程使用的堆栈将被释放。 l  系统将线程内核对象中 Exit Code退出代码的值由 STILL_ACTIVE 设置为线程函数的返回值。 l  系统将递减线程内核对象中 Usage Code使用计数的值。 线程结束后的退出代码可以被其他线程用GetExitCodeThread函数检测到所以可以当做自定义的返回值来表示线程的执行结果。 人为中止主要有两种手段 1调用ExitThreadCRT中的exit结束当前线程调用线程 // The ExitThread function ends a thread. VOID ExitThread( DWORD dwExitCode   // exit code for this thread ); ExitThread 函数会中止当前线程的运行促使系统释放掉所有此线程使用的资源。但是线程函数中申请的C资源典型的如C类class却不能得到正确地清除。这是因为C对象的析构函数是通过atexit挂接到线程中在exit退出时main/WinMain或线程过程返回之后调用。若线程过程被终止则CRT越过atexit从而使C资源不能正确释放。所以结束线程最好的方法还是让线程自然返回。 2调用TerminateThread进行跨线程中止。 // The TerminateThread function terminates a thread. BOOL TerminateThread( HANDLE hThread,    // handle to thread DWORD dwExitCode   // exit code ); 因为该API传递的是句柄故该函数可跨线程调用即在一个线程中中止另一个线程。 这是一个被强烈建议避免使用的函数因为一旦执行这个函数程序无法预测目标线程会在何处被中止其结果就是目标线程可能根本没有机会来做清除工作如线程中打开的文件和申请的内存都不会被释放。另外使用TerminateThread 函数中止线程的时候系统不会释放线程使用的堆栈。所以建议读者在编程的时候尽量让线程自己退出如果主线程要求某个线程结束可以通过各种方法通知线程线程收到通知后自行退出。只有在迫不得已的情况下才使用 TerminateThread 函数终止线程。 无论是自然终止还是人为中止它们都将使使用计数减1。此时我们通过调用GetExitCodeThread函数来获取线程函数的返回值。最后必须调用CloseHandle关闭线程句柄使使用计数再减1。至此该线程内核对象结束生命的旅程。 总之始终应该让线程正常退出即使它的线程函数自然返回。通知线程退出的方法很多如设置全局变量、使用事件对象等这涉及到下一节线程间的通信问题。 5用CRT的_beginthreadex代替操作系统的CreateThread 1为IDE选择正确的RTLRun Time Library 在VC6中“Project Settings à C/C à CategoryCode Generationà Use run-time library”共有六个选项供选择 /MLSingle-Threaded* /MLdDebug Single-Threaded /MTMultithreaded /MTdDebug Multithreaded /MDMultithreaded DLL /MDdDebug Multithreaded DLL 其中/ML[d]和/MT[d]主要针对常规多线程应用程序的RTL版本选择/MD[d]主要针对多线程LIB或DLL工程中的RTL版本选择。 VC6常规工程的默认选项为/ML[d]MFC工程的默认选项为/MD[d]。对于需要多线程支持的工程中如果不设定/MT或/MD选项则可能出现“error LNK2001: unresolved external symbol __beginthreadex”或“error C2065: _beginthreadex : undeclared identifier”。 在VC2005中“项目属性 à 配置属性 à C/C  à 代码生成”中提供了四种选择 多线程/MT 多线程调试/MTd 多线程DLL/MD 多线程调试DLL/MDd VC2005工程默认选项为/MD[d]。 不同的链接选项链接器将选择不同的链接库进行链接。参考《C Run-Time Libraries (CRT)》和《/MD, /ML, /MT, /LD   (Use Run-Time Library)》。 2用C运行时库的_beginthreadex代替操作系统的CreateThread来创建线程 在实际的开发过程中一般不直接使用Windows系统提供的CreateThread函数创建线程而是使用 C/C运行期函数_beginthread(ex)/_endthread(ex)。 使用_beginthread无法创建带有安全属性的新线程无法创建暂停的线程也无法获得线程ID。_endthread的情况类似它不带参数这意味这线程的退出代码必须硬编码为0。一般不调用_beginthread/_endthread,而是调用其扩展版本_beginthreadex/_endthreadex。 事实上C/C运行期库提供CreateThread加强版的_beginthreadex是为了多线程同步的需要。在早期的单线程C运行库中有许多的全局变量如errno、strerror等它们可以用来表示线程当前的一些状态。 但是在多线程程序设计中每个线程必须有惟一的状态否则这些变量记录的信息就不会准确了。比如全局变量errno 用于表示调用运行期函数失败后的错误代码。如果所有线程共享一个errno 的话在一个线程产生的错误代码就会影响到另一个线程。为了解决这个问题每个线程都需要有自己的errno 变量。 要想使运行期为每个线程都设置状态变量必须在创建线程的时候调用运行期提供的_beginthreadex让运行期设置了相关变量后再去调用Windows系统提供的 CreateThread函数。 _beginthreadex的参数与CreateThread函数对应函数的参数和数据类型都是C Run-time Library中的类型在使用时候需要强制类型转换。 // /Microsoft Visual Studio/VC98/CRT/SRC/THREADEX.C unsigned long __cdecl _beginthreadex( void *security, unsigned stacksize, unsigned (__stdcall* initialcode)(void*), void * argument, unsigned createflag, unsigned *thrdaddr) { _ptiddata ptd;                  /* pointer to per-thread data */ // …… ptd-_initaddr  (void *)initialcode; ptd-_initarg  argument; ptd-_thandle  (unsigned long)(-1L); // …… CreateThread(security, stacksize, _threadstartex, (LPVOID)ptd,  // pointer to per-thread data(_ptiddata ptd) createflag, thrdaddr)); // …… } 线程启动函数为_threadstartex其参数为线程局部存储TLS结构_ptiddata ptd其中包含了线程函数ptd-_initaddr和线程参数ptd-_initarg。关于线程局部存储TLS参考后续议题。实际上_ptiddata结构中定义了_terrno、_token、_errmsg等单线程全局变量。 struct _tiddata { unsigned long   _tid;           /* thread ID */ unsigned long   _thandle;       /* thread handle */ int     _terrno;                /* errno value */ unsigned long   _holdrand;      /* rand() seed value */ char *      _token;             /* ptr to strtok() token */ char *      _errmsg;            /* ptr to strerror()/_strerror() buff */ void *      _initaddr;          /* initial user thread address */ void *      _initarg;           /* initial user thread argument */ } typedef struct _tiddata * _ptiddata; // _threadstartex() - New thread begins here static unsigned long WINAPI _threadstartex(void *ptd) { // …… // Call fp initialization, if necessary if ( _FPmtinit ! NULL ) (*_FPmtinit)(); // …… _endthreadex(((unsigned (WINAPI *)(void*))(((_ptiddata)ptd)-_initaddr))(((_ptiddata)ptd)-_initarg)); } 线程启动函数_threadstartex中进行相关的初始化_FPmtinit后开始调用ptd-_initaddr(ptd-_initarg)执行真正的线程过程。传递线程过程返回码调用_endthreadex函数。 // _endthreadex() - Terminate the calling thread void __cdecl _endthreadex(unsigned retcode) _endthreadex函数释放线程局部存储TLS数据_ptiddata ptd调用ExitThread结束当前线程的运行。故这里有了不调用ExitThread的另一个理由即它会阻止线程的_ptiddata内存的释放。故建议使用_endthreadex替代ExitThread调用当然这也是不应该提倡的做法。 参考《_beginthreadex和CreateThread》。
http://www.w-s-a.com/news/575674/

相关文章:

  • 网络营销方式及流程广州seo工作
  • 专业商城网站制作免费网页设计成品
  • 韩国优秀设计网站找做网站找那个平台做
  • 贵州省清镇市建设学校网站国家企业信用信息公示系统官网河北
  • 游戏界面设计网站网站建设问一问公司
  • 织梦网站模板如何安装教程视频国外哪些网站可以注册域名
  • 用群晖做网站网站中文名称注册
  • 做一个企业网站需要哪些技术app开发公司名字
  • 网站建设有技术的公司图片在线设计平台
  • 建公司网站的详细步骤关于进一步加强网站建设
  • 丰宁县有做网站的吗?维护一个网站一年多少钱
  • 杭州网站设计渠道wordpress购物主题
  • 山东政务网站建设文字logo免费设计在线生成
  • 韩雪个人网站唐山网络运营推广
  • 查建设工程业绩在哪个网站网站建设优化服务如何
  • 江苏省建设工程安全监督网站商洛网站制作
  • 海淀网站建设wzjs51网页设计页面配色分析
  • 网站的备案流程图垦利网站制作
  • 行业用品网站怎么建设外链买东西的网站都有哪些
  • 淘宝做促销的网站集团门户网站建设策划
  • 网站排行榜查询怎样把个人介绍放到百度
  • vps 网站上传河北省招投标信息网
  • 武进网站建设咨询网站定制公司选哪家
  • 郑州市建设投资集团公司网站深圳企业网站建设推荐公司
  • 天津个人网站备案查询dz网站恢复数据库
  • 关于网站建设的期刊文献宣传片文案
  • 物业网站模板下载wordpress+菜单大小
  • 网站建设案例教程视频空间刷赞网站推广
  • 网站建设借鉴做外贸球衣用什么网站
  • 网站建设的前途微信公众号制作网站