﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-时间的痕迹-文章分类-多线程</title><link>http://www.cppblog.com/ivenher/category/288.html</link><description /><language>zh-cn</language><lastBuildDate>Mon, 19 May 2008 21:51:29 GMT</lastBuildDate><pubDate>Mon, 19 May 2008 21:51:29 GMT</pubDate><ttl>60</ttl><item><title>什么代码才是线程安全的</title><link>http://www.cppblog.com/ivenher/articles/15815.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Thu, 30 Nov 2006 08:11:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/15815.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/15815.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/15815.html#Feedback</comments><slash:comments>4</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/15815.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/15815.html</trackback:ping><description><![CDATA[对于多线程编程，很多人概念不清，写代码的时候要么是处处加锁，影响性能不说，还容易莫名其妙的死锁，还有人对多线程敬而远之。<br /><br />所以学习多线程编程最重要的不是学习API,而是理解什么才是多线程安全的代码<br /><br />从例子说起<br /><div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #000000">#include </span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">windows.h</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br />#include </span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">process.h</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br /><br /></span><span style="COLOR: #0000ff">long</span><span style="COLOR: #000000"> global1 </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br /></span><span style="COLOR: #0000ff">volatile</span><span style="COLOR: #000000"> </span><span style="COLOR: #0000ff">long</span><span style="COLOR: #000000"> global2 </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br /><br /></span><span style="COLOR: #0000ff">class</span><span style="COLOR: #000000"> MyClass<br />{<br /></span><span style="COLOR: #0000ff">public</span><span style="COLOR: #000000">:<br />    MyClass() : m(</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">)<br />    {<br />        </span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">m;<br />    }<br /><br />    </span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> fun(</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> v)<br />    {<br />        </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> m</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">v; </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">-----------9</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000">    }<br /><br />    </span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> </span><span style="COLOR: #0000ff">set</span><span style="COLOR: #000000">(</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> v)<br />    {<br />        m </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> v;   </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">-------------10</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000">    }<br />    </span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> m;<br />};<br /><br />MyClass global_object; </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">-------------8</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000"><br />unsigned </span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> __stdcall thread_fun1(</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">param)<br />{<br />    </span><span style="COLOR: #0000ff">static</span><span style="COLOR: #000000"> </span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> static2 </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br />    </span><span style="COLOR: #0000ff">static</span><span style="COLOR: #000000"> MyClass static_object; </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">--------6</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> local1 </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br />    <br />    </span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">local1;     </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">-------1</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000">    </span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">static2;    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">-------2</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000">    </span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">global1;    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">-------3</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000">    </span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">global2;    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">-------4</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000">    InterlockedIncrement(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">global1); </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">--------5</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000"><br />    local1 </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> global_object.fun(local1); </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">----------7</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000"><br />    global_object.</span><span style="COLOR: #0000ff">set</span><span style="COLOR: #000000">(local1); </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">---------------11</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000"><br />    </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br />}<br /><br /><br />unsigned </span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> __stdcall thread_fun2(</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">*</span><span style="COLOR: #000000">param)<br />{<br />    </span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">global1;    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">-------3</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000">    </span><span style="COLOR: #000000">++</span><span style="COLOR: #000000">global2;    </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">-------4</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000">    InterlockedIncrement(</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">global1); </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">--------5</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000"><br />    global_object.</span><span style="COLOR: #0000ff">set</span><span style="COLOR: #000000">(</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">); </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">-----------11</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000">    </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br />}<br /><br /><br /></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000"> main()<br />{<br />    HANDLE thread1 </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> (HANDLE)_beginthreadex(</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">thread_fun1,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">); </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">thread 1</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000">    HANDLE thread2 </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> (HANDLE)_beginthreadex(</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">thread_fun1,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">); </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">thread 2</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000">    HANDLE thread3 </span><span style="COLOR: #000000">=</span><span style="COLOR: #000000"> (HANDLE)_beginthreadex(</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&amp;</span><span style="COLOR: #000000">thread_fun2,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">); </span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">thread 3</span><span style="COLOR: #008000"><br /></span><span style="COLOR: #000000">    <br />    WaitForSingleObject(thread1,INFINITE);<br />    WaitForSingleObject(thread2,INFINITE);<br />    WaitForSingleObject(thread3,INFINITE);<br />    <br />    </span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000"> </span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">;<br />}<br /><br /><br /></span></div><br /><br /><strong>1.局部变量局部使用是安全的<br /></strong>为什么?因为每个thread 都有自己的运行堆栈，而局部变量是生存在堆栈中,大家不干扰。<br />所以代码1<br />int local1;<br />++local1;<br />是安全的<br /><br /><strong>2.全局原生变量多线程读写是不安全的<br /></strong>全局变量是在堆(heap)中<br />long global1 = 0;<br />++global2;<br />++这个操作其实分为两部，一个是读，另外一个是写<br /> mov         ecx,global<br /> add         ecx,1<br /> mov         global,ecx<br />所以代码3处是不安全的<br /><br /><strong>3.函数静态变量多线程读写也是不安全的<br /></strong>道理同2<br />所以代码2处也是不安全的<br /><br /><strong>4.volatile能保证全局整形变量是多线程安全的么</strong><br />不能。<br />volatile仅仅是告诫compiler不要对这个变量作优化，每次都要从memory取数值，而不是从register<br />所以代码4也不是安全<br /><br /><strong>5.InterlockedIncrement保证整型变量自增的原子性</strong><br />所以代码5是安全的<br /><br /><strong>6.function static object的初始化是多线程安全的么</strong><br />不是。<br />著名的Meyer Singleton其实不是线程安全的<br />Object &amp; getInstance()<br />{ <br />     static Object o;<br />     return o;<br />}<br />可能会造成多次初始化对象<br />所以代码6处是不安全的<br /><br /><strong>7.在32机器上，4字节整形一次assign是原子的</strong><br />比如<br />i =10; //thread1<br />i=4; //thread2<br />不会导致i的值处于未知状态,要么是10要么是4<br /><br /><br />写好多线程安全的法宝就是封装，使数据有保护的被访问到<br />安全性：<br />局部变量&gt;成员变量&gt;全局变量<img src ="http://www.cppblog.com/ivenher/aggbug/15815.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2006-11-30 16:11 <a href="http://www.cppblog.com/ivenher/articles/15815.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入浅出Win32多线程程序设计之线程控制 三</title><link>http://www.cppblog.com/ivenher/articles/15355.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Sat, 18 Nov 2006 06:21:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/15355.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/15355.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/15355.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/15355.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/15355.html</trackback:ping><description><![CDATA[5.设置线程优先级<br /><br />　　当一个线程被首次创建时，它的优先级等同于它所属进程的优先级。在单个进程内可以通过调用SetThreadPriority函数改变线程的相对优先级。一个线程的优先级是相对于其所属进程的优先级而言的。<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>BOOL SetThreadPriority(HANDLE hThread, int nPriority); </td></tr></tbody></table><br />　　其中参数hThread是指向待修改优先级线程的句柄，线程与包含它的进程的优先级关系如下：<br /><br />　　　线程优先级 = 进程类基本优先级 + 线程相对优先级<br /><br />　　进程类的基本优先级包括：<br /><br />　　（1）实时：REALTIME_PRIORITY_CLASS；<br /><br />　　（2）高：HIGH _PRIORITY_CLASS；<br /><br />　　（3）高于正常：ABOVE_NORMAL_PRIORITY_CLASS；<br /><br />　　（4）正常：NORMAL _PRIORITY_CLASS；<br /><br />　　（5）低于正常：BELOW_ NORMAL _PRIORITY_CLASS；<br /><br />　　（6）空闲：IDLE_PRIORITY_CLASS。<br /><br />　　我们从Win32任务管理器中可以直观的看到这六个进程类优先级，如下图：<br /><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img src="http://dev.yesky.com/imagelist/05/12/7hdedy7210ey.jpg" border="0" /></div></td></tr></tbody></table><br />　　线程的相对优先级包括：<br /><br />　　（1）空闲：THREAD_PRIORITY_IDLE；<br /><br />　　（2）最低线程：THREAD_PRIORITY_LOWEST；<br /><br />　　（3）低于正常线程：THREAD_PRIORITY_BELOW_NORMAL；<br /><br />　　（4）正常线程：THREAD_PRIORITY_ NORMAL (缺省)；<br /><br />　　（5）高于正常线程：THREAD_PRIORITY_ABOVE_NORMAL；<br /><br />　　（6）最高线程：THREAD_PRIORITY_HIGHEST；<br /><br />　　（7）关键时间：THREAD_PRIOTITY_CRITICAL。<br /><br />　　下图给出了进程优先级和线程相对优先级的映射关系：<br /><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img src="http://dev.yesky.com/imagelist/05/12/62v88f956u3z.jpg" border="0" /></div></td></tr></tbody></table><br />　　例如：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>HANDLE hCurrentThread = GetCurrentThread();<br />//获得该线程句柄<br />SetThreadPriority(hCurrentThread, THREAD_PRIORITY_LOWEST); </td></tr></tbody></table><br />　　6.睡眠<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>VOID Sleep(DWORD dwMilliseconds);</td></tr></tbody></table><br />　　该函数可使线程暂停自己的运行，直到dwMilliseconds毫秒过去为止。它告诉系统，自身不想在某个时间段内被调度。<br /><br />　　7.其它重要API<br /><br />　　获得线程优先级<br /><br />　　一个线程被创建时，就会有一个默认的优先级，但是有时要动态地改变一个线程的优先级，有时需获得一个线程的优先级。<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>Int GetThreadPriority (HANDLE hThread);</td></tr></tbody></table><br />　　如果函数执行发生错误，会返回THREAD_PRIORITY_ERROR_RETURN标志。如果函数成功地执行，会返回优先级标志。<br /><br />　　获得线程退出码<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>BOOL WINAPI GetExitCodeThread(<br />　HANDLE hThread,<br />　LPDWORD lpExitCode<br />);</td></tr></tbody></table><br />　　如果执行成功，GetExitCodeThread返回TRUE，退出码被lpExitCode指向内存记录；否则返回FALSE，我们可通过GetLastError()获知错误原因。如果线程尚未结束，lpExitCode带回来的将是STILL_ALIVE。<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>获得/设置线程上下文<br />BOOL WINAPI GetThreadContext(<br />　HANDLE hThread,<br />　LPCONTEXT lpContext<br />);<br />BOOL WINAPI SetThreadContext(<br />　HANDLE hThread,<br />　CONST CONTEXT *lpContext<br />);</td></tr></tbody></table><br />　　由于GetThreadContext和SetThreadContext可以操作CPU内部的寄存器，因此在一些高级技巧的编程中有一定应用。譬如，调试器可利用GetThreadContext挂起被调试线程获取其上下文，并设置上下文中的标志寄存器中的陷阱标志位，最后通过SetThreadContext使设置生效来进行单步调试。<br /><br />　　8.实例<br /><br />　　以下程序使用CreateThread创建两个线程，在这两个线程中Sleep一段时间，主线程通过GetExitCodeThread来判断两个线程是否结束运行：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>#define WIN32_LEAN_AND_MEAN<br />#include &lt;stdio.h&gt;<br />#include &lt;stdlib.h&gt;<br />#include &lt;windows.h&gt;<br />#include &lt;conio.h&gt;<br /><br />DWORD WINAPI ThreadFunc(LPVOID);<br /><br />int main()<br />{<br />　HANDLE hThrd1;<br />　HANDLE hThrd2;<br />　DWORD exitCode1 = 0;<br />　DWORD exitCode2 = 0;<br />　DWORD threadId;<br /><br />　hThrd1 = CreateThread(NULL, 0, ThreadFunc, (LPVOID)1, 0, &amp;threadId );<br />　if (hThrd1)<br />　　printf("Thread 1 launched\n");<br /><br />　hThrd2 = CreateThread(NULL, 0, ThreadFunc, (LPVOID)2, 0, &amp;threadId );<br />　if (hThrd2)<br />　　printf("Thread 2 launched\n");<br /><br />　// Keep waiting until both calls to GetExitCodeThread succeed AND<br />　// neither of them returns STILL_ACTIVE.<br />　for (;;)<br />　{<br />　　printf("Press any key to exit..\n");<br />　　getch();<br /><br />　　GetExitCodeThread(hThrd1, &amp;exitCode1);<br />　　GetExitCodeThread(hThrd2, &amp;exitCode2);<br />　　if ( exitCode1 == STILL_ACTIVE )<br />　　　puts("Thread 1 is still running!");<br />　　if ( exitCode2 == STILL_ACTIVE )<br />　　　puts("Thread 2 is still running!");<br />　　if ( exitCode1 != STILL_ACTIVE &amp;&amp; exitCode2 != STILL_ACTIVE )<br />　　　break;<br />　}<br /><br />　CloseHandle(hThrd1);<br />　CloseHandle(hThrd2);<br /><br />　printf("Thread 1 returned %d\n", exitCode1);<br />　printf("Thread 2 returned %d\n", exitCode2);<br /><br />　return EXIT_SUCCESS;<br />}<br /><br />/*<br />* Take the startup value, do some simple math on it,<br />* and return the calculated value.<br />*/<br />DWORD WINAPI ThreadFunc(LPVOID n)<br />{<br />　Sleep((DWORD)n*1000*2);<br />　return (DWORD)n * 10;<br />}</td></tr></tbody></table><br />　　通过下面的程序我们可以看出多线程程序运行顺序的难以预料以及WINAPI的CreateThread函数与C运行时库的_beginthread的差别：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>#define WIN32_LEAN_AND_MEAN<br />#include &lt;stdio.h&gt;<br />#include &lt;stdlib.h&gt;<br />#include &lt;windows.h&gt;<br /><br />DWORD WINAPI ThreadFunc(LPVOID);<br /><br />int main()<br />{<br />　HANDLE hThrd;<br />　DWORD threadId;<br />　int i;<br /><br />　for (i = 0; i &lt; 5; i++)<br />　{<br />　　hThrd = CreateThread(NULL, 0, ThreadFunc, (LPVOID)i, 0, &amp;threadId);<br />　　if (hThrd)<br />　　{<br />　　　printf("Thread launched %d\n", i);<br />　　　CloseHandle(hThrd);<br />　　}<br />　}<br />　// Wait for the threads to complete.<br />　Sleep(2000);<br /><br />　return EXIT_SUCCESS;<br />}<br /><br />DWORD WINAPI ThreadFunc(LPVOID n)<br />{<br />　int i;<br />　for (i = 0; i &lt; 10; i++)<br />　　printf("%d%d%d%d%d%d%d%d\n", n, n, n, n, n, n, n, n);<br />　return 0;<br />}</td></tr></tbody></table><br />　　运行的输出具有很大的随机性，这里摘取了几次结果的一部分（几乎每一次都不同）：<br /><br /><table width="90%" align="center" border="0"><tbody><tr><td><div align="center"><img src="http://dev.yesky.com/imagelist/05/12/2269bdvd5va1.jpg" border="0" /><img src="http://dev.yesky.com/imagelist/05/12/94hfp586n83a.jpg" border="0" /><img src="http://dev.yesky.com/imagelist/05/12/phqe9k2g8p2f.jpg" border="0" /></div></td></tr></tbody></table><br />　　如果我们使用标准C库函数而不是多线程版的运行时库，则程序可能输出"3333444444"这样的结果，而使用多线程运行时库后，则可避免这一问题。<br /><br />　　下列程序在主线程中创建一个SecondThread，在SecondThread线程中通过自增对Counter计数到1000000，主线程一直等待其结束：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>#include &lt;Win32.h&gt;<br />#include &lt;stdio.h&gt;<br />#include &lt;process.h&gt;<br /><br />unsigned Counter;<br />unsigned __stdcall SecondThreadFunc(void *pArguments)<br />{<br />　printf("In second thread...\n");<br /><br />　while (Counter &lt; 1000000)<br />　　Counter++;<br /><br />　_endthreadex(0);<br />　return 0;<br />}<br /><br />int main()<br />{<br />　HANDLE hThread;<br />　unsigned threadID;<br /><br />　printf("Creating second thread...\n");<br /><br />　// Create the second thread.<br />　hThread = (HANDLE)_beginthreadex(NULL, 0, &amp;SecondThreadFunc, NULL, 0, &amp;threadID);<br /><br />　// Wait until second thread terminates <br />　WaitForSingleObject(hThread, INFINITE);<br />　printf("Counter should be 1000000; it is-&gt; %d\n", Counter);<br />　// Destroy the thread object.<br />　CloseHandle(hThread);<br />}<br /></td></tr></tbody></table><div class="right"> </div><img src ="http://www.cppblog.com/ivenher/aggbug/15355.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2006-11-18 14:21 <a href="http://www.cppblog.com/ivenher/articles/15355.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入浅出Win32多线程程序设计之线程控制 一</title><link>http://www.cppblog.com/ivenher/articles/15353.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Sat, 18 Nov 2006 06:20:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/15353.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/15353.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/15353.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/15353.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/15353.html</trackback:ping><description><![CDATA[WIN32线程控制主要实现线程的创建、终止、挂起和恢复等操作，这些操作都依赖于WIN32提供的一组API和具体编译器的C运行时库函数。<br /><div class="guanggao"><span id="ad3"></span></div><div class="guanggao"><span id="contentAdv"><object codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=5,0,0,0" height="200" width="240" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"><param name="_cx" value="6350" /><param name="_cy" value="5292" /><param name="FlashVars" value="" /><param name="Movie" value="http://media.yesky.com/images/dell-pip-dt-061013.swf" /><param name="Src" value="http://media.yesky.com/images/dell-pip-dt-061013.swf" /><param name="WMode" value="Window" /><param name="Play" value="-1" /><param name="Loop" value="-1" /><param name="Quality" value="High" /><param name="SAlign" value="" /><param name="Menu" value="-1" /><param name="Base" value="" /><param name="AllowScriptAccess" value="" /><param name="Scale" value="ShowAll" /><param name="DeviceFont" value="0" /><param name="EmbedMovie" value="0" /><param name="BGColor" value="" /><param name="SWRemote" value="" /><param name="MovieData" value="" /><param name="SeamlessTabbing" value="1" /><param name="Profile" value="0" /><param name="ProfileAddress" value="" /><param name="ProfilePort" value="0" /><param name="AllowNetworking" value="all" /><embed src="http://media.yesky.com/images/dell-pip-dt-061013.swf" quality="high" pluginspage="http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash" type="application/x-shockwave-flash" width="240" height="200"></embed></object></span></div>　　WIN32线程控制主要实现线程的创建、终止、挂起和恢复等操作，这些操作都依赖于WIN32提供的一组API和具体编译器的C运行时库函数。<br /><br />　　1.线程函数<br /><br />　　在启动一个线程之前，必须为线程编写一个全局的线程函数，这个线程函数接受一个32位的LPVOID作为参数，返回一个UINT，线程函数的结构为：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>UINT ThreadFunction(LPVOID pParam)<br />{<br />　//线程处理代码<br />　return0;<br />}</td></tr></tbody></table><br />　　在线程处理代码部分通常包括一个死循环，该循环中先等待某事情的发生，再处理相关的工作：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>while(1)<br />{<br />　WaitForSingleObject(…,…);//或WaitForMultipleObjects(…)<br />　//Do something<br />}</td></tr></tbody></table><br />　　一般来说，C++的类成员函数不能作为线程函数。这是因为在类中定义的成员函数，编译器会给其加上this指针。请看下列程序：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>#include "windows.h"<br />#include &lt;process.h&gt;<br />class ExampleTask <br />{ <br />　public: <br />　　void taskmain(LPVOID param); <br />　　void StartTask(); <br />}; <br />void ExampleTask::taskmain(LPVOID param) <br />{} <br /><br />void ExampleTask::StartTask() <br />{ <br />　_beginthread(taskmain,0,NULL);<br />} <br /><br />int main(int argc, char* argv[])<br />{<br />　ExampleTask realTimeTask;<br />　realTimeTask.StartTask();<br />　return 0;<br />}</td></tr></tbody></table><br />　　程序编译时出现如下错误：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>error C2664: '_beginthread' : cannot convert parameter 1 from 'void (void *)' to 'void (__cdecl *)(void *)'<br />None of the functions with this name in scope match the target type</td></tr></tbody></table><br />　　再看下列程序：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>#include "windows.h"<br />#include &lt;process.h&gt;<br />class ExampleTask <br />{ <br />　public: <br />　　void taskmain(LPVOID param); <br />}; <br /><br />void ExampleTask::taskmain(LPVOID param) <br />{} <br /><br />int main(int argc, char* argv[])<br />{<br />　ExampleTask realTimeTask;<br />　_beginthread(ExampleTask::taskmain,0,NULL);<br />　return 0;<br />}</td></tr></tbody></table><br />　　程序编译时会出错：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>error C2664: '_beginthread' : cannot convert parameter 1 from 'void (void *)' to 'void (__cdecl *)(void *)'<br />None of the functions with this name in scope match the target type</td></tr></tbody></table><br />　　如果一定要以类成员函数作为线程函数，通常有如下解决方案：<br /><br />　　（1）将该成员函数声明为static类型，去掉this指针；<br /><br />　　我们将上述二个程序改变为：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>#include "windows.h"<br />#include &lt;process.h&gt;<br />class ExampleTask <br />{ <br />　public: <br />　　void static taskmain(LPVOID param); <br />　　void StartTask(); <br />}; <br /><br />void ExampleTask::taskmain(LPVOID param) <br />{} <br /><br />void ExampleTask::StartTask() <br />{ <br />　_beginthread(taskmain,0,NULL);<br />} <br /><br />int main(int argc, char* argv[])<br />{<br />　ExampleTask realTimeTask;<br />　realTimeTask.StartTask();<br />　return 0;<br />}<br />和<br />#include "windows.h"<br />#include &lt;process.h&gt;<br />class ExampleTask <br />{ <br />　public: <br />　　void static taskmain(LPVOID param); <br />}; <br /><br />void ExampleTask::taskmain(LPVOID param) <br />{} <br /><br />int main(int argc, char* argv[])<br />{<br />　_beginthread(ExampleTask::taskmain,0,NULL);<br />　return 0;<br />}</td></tr></tbody></table><br />　　均编译通过。<br /><br />　　将成员函数声明为静态虽然可以解决作为线程函数的问题，但是它带来了新的问题，那就是static成员函数只能访问static成员。解决此问题的一种途径是可以在调用类静态成员函数（线程函数）时将this指针作为参数传入，并在改线程函数中用强制类型转换将this转换成指向该类的指针，通过该指针访问非静态成员。<br /><br />　　（2）不定义类成员函数为线程函数，而将线程函数定义为类的友元函数。这样，线程函数也可以有类成员函数同等的权限； <br /><br />　　我们将程序修改为：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>#include "windows.h"<br />#include &lt;process.h&gt;<br />class ExampleTask <br />{ <br />　public: <br />　　friend void taskmain(LPVOID param); <br />　　void StartTask(); <br />}; <br /><br />void taskmain(LPVOID param) <br />{ <br />　ExampleTask * pTaskMain = (ExampleTask *) param; <br />　//通过pTaskMain指针引用 <br />} <br /><br />void ExampleTask::StartTask() <br />{ <br />　_beginthread(taskmain,0,this);<br />}<br />int main(int argc, char* argv[])<br />{<br />　ExampleTask realTimeTask;<br />　realTimeTask.StartTask();<br />　return 0;<br />}</td></tr></tbody></table><br />　　（3）可以对非静态成员函数实现回调，并访问非静态成员，此法涉及到一些高级技巧，在此不再详述。<br /><img src ="http://www.cppblog.com/ivenher/aggbug/15353.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2006-11-18 14:20 <a href="http://www.cppblog.com/ivenher/articles/15353.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入浅出Win32多线程程序设计之线程控制 二</title><link>http://www.cppblog.com/ivenher/articles/15354.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Sat, 18 Nov 2006 06:20:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/15354.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/15354.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/15354.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/15354.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/15354.html</trackback:ping><description><![CDATA[2.创建线程<br /><br />　　进程的主线程由操作系统自动生成，Win32提供了CreateThread API来完成用户线程的创建，该API的原型为：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>HANDLE CreateThread(<br />　LPSECURITY_ATTRIBUTES lpThreadAttributes,//Pointer to a SECURITY_ATTRIBUTES structure<br />　SIZE_T dwStackSize, //Initial size of the stack, in bytes.<br />　LPTHREAD_START_ROUTINE lpStartAddress,<br />　LPVOID lpParameter, //Pointer to a variable to be passed to the thread<br />　DWORD dwCreationFlags, //Flags that control the creation of the thread<br />　LPDWORD lpThreadId //Pointer to a variable that receives the thread identifier<br />);</td></tr></tbody></table><br />　　如果使用C/C++语言编写多线程应用程序，一定不能使用操作系统提供的CreateThread API，而应该使用C/C++运行时库中的_beginthread（或_beginthreadex），其函数原型为：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>uintptr_t _beginthread( <br />　void( __cdecl *start_address )( void * ), //Start address of routine that begins execution of new thread<br />　unsigned stack_size, //Stack size for new thread or 0.<br />　void *arglist //Argument list to be passed to new thread or NULL<br />);<br />uintptr_t _beginthreadex( <br />　void *security,//Pointer to a SECURITY_ATTRIBUTES structure<br />　unsigned stack_size,<br />　unsigned ( __stdcall *start_address )( void * ),<br />　void *arglist,<br />　unsigned initflag,//Initial state of new thread (0 for running or CREATE_SUSPENDED for suspended); <br />　unsigned *thrdaddr <br />);</td></tr></tbody></table><br />　　_beginthread函数与Win32 API 中的CreateThread函数类似，但有如下差异： <br /><br />　　（1）通过_beginthread函数我们可以利用其参数列表arglist将多个参数传递到线程； <br /><br />　　（2）_beginthread 函数初始化某些 C 运行时库变量，在线程中若需要使用 C 运行时库。 <br /><br />　　3.终止线程<br /><br />　　线程的终止有如下四种方式：<br /><br />　　（1）线程函数返回；<br /><br />　　（2）线程自身调用ExitThread 函数即终止自己，其原型为：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>VOID ExitThread(UINT fuExitCode ); </td></tr></tbody></table><br />　　它将参数fuExitCode设置为线程的退出码。<br /><br />　　注意：如果使用C/C++编写代码，我们应该使用C/C++运行时库函数_endthread (_endthreadex)终止线程，决不能使用ExitThread！<br />_endthread 函数对于线程内的条件终止很有用。例如，专门用于通信处理的线程若无法获取对通信端口的控制，则会退出。<br /><br />　　（3）同一进程或其他进程的线程调用TerminateThread函数，其原型为：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode); </td></tr></tbody></table><br />　　该函数用来结束由hThread参数指定的线程，并把dwExitCode设成该线程的退出码。当某个线程不再响应时，我们可以用其他线程调用该函数来终止这个不响应的线程。<br /><br />　　（4）包含线程的进程终止。<br /><br />　　最好使用第1种方式终止线程，第2~4种方式都不宜采用。<br /><br />　　4.挂起与恢复线程<br /><br />　　当我们创建线程的时候，如果给其传入CREATE_SUSPENDED标志，则该线程创建后被挂起，我们应使用ResumeThread恢复它：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>DWORD ResumeThread(HANDLE hThread); </td></tr></tbody></table><br />　　如果ResumeThread函数运行成功，它将返回线程的前一个暂停计数，否则返回0x FFFFFFFF。<br /><br />　　对于没有被挂起的线程，程序员可以调用SuspendThread函数强行挂起之：<br /><br /><table bordercolor="#cccccc" width="90%" align="center" bgcolor="#e3e3e3" border="1"><tbody><tr><td>DWORD SuspendThread(HANDLE hThread);</td></tr></tbody></table><br />　　一个线程可以被挂起多次。线程可以自行暂停运行，但是不能自行恢复运行。如果一个线程被挂起n次，则该线程也必须被恢复n次才可能得以执行。<br /><img src ="http://www.cppblog.com/ivenher/aggbug/15354.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2006-11-18 14:20 <a href="http://www.cppblog.com/ivenher/articles/15354.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Win32多线程程序设计之线程通信 （三）</title><link>http://www.cppblog.com/ivenher/articles/2297.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Fri, 30 Dec 2005 10:51:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/2297.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/2297.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/2297.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/2297.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/2297.html</trackback:ping><description><![CDATA[<STRONG>信号量<BR><BR></STRONG>　　信号量是维护0到指定最大值之间的同步对象。信号量状态在其计数大于0时是有信号的，而其计数是0时是无信号的。信号量对象在控制上可以支持有限数量共享资源的访问。<BR><BR>　　信号量的特点和用途可用下列几句话定义：<BR><BR>　　（1）如果当前资源的数量大于0，则信号量有效；<BR><BR>　　（2）如果当前资源数量是0，则信号量无效；<BR><BR>　　（3）系统决不允许当前资源的数量为负值；<BR><BR>　　（4）当前资源数量决不能大于最大资源数量。<BR><BR>　　创建信号量<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>HANDLE CreateSemaphore (<BR>　PSECURITY_ATTRIBUTE psa,<BR>　LONG lInitialCount, //开始时可供使用的资源数<BR>　LONG lMaximumCount, //最大资源数<BR>PCTSTR pszName);</TD></TR></TBODY></TABLE><BR>　　释放信号量<BR><BR>　　通过调用ReleaseSemaphore函数，线程就能够对信标的当前资源数量进行递增，该函数原型为：<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>BOOL WINAPI ReleaseSemaphore(<BR>　HANDLE hSemaphore,<BR>　LONG lReleaseCount, //信号量的当前资源数增加lReleaseCount<BR>　LPLONG lpPreviousCount<BR>);</TD></TR></TBODY></TABLE><BR>　　打开信号量<BR><BR>　　和其他核心对象一样，信号量也可以通过名字跨进程访问，打开信号量的API为：<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>HANDLE OpenSemaphore (<BR>　DWORD fdw<A href="http://dev.21tx.com/database/access/" target=_blank><FONT color=#3366cc>Access</FONT></A>,<BR>　BOOL bInherithandle,<BR>　PCTSTR pszName<BR>);</TD></TR></TBODY></TABLE><BR>　　<B>互锁访问</B><BR><BR>　　当必须以原子操作方式来修改单个值时，互锁访问函数是相当有用的。所谓原子访问，是指线程在访问资源时能够确保所有其他线程都不在同一时间内访问相同的资源。<BR><BR>　　请看下列代码：<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>int globalVar = 0;<BR><BR>DWORD WINAPI ThreadFunc1(LPVOID n)<BR>{<BR>　globalVar++;<BR>　return 0;<BR>}<BR>DWORD WINAPI ThreadFunc2(LPVOID n)<BR>{<BR>　globalVar++;<BR>　return 0;<BR>}</TD></TR></TBODY></TABLE><BR>　　运行ThreadFunc1和ThreadFunc2线程，结果是不可预料的，因为globalVar++并不对应着一条机器指令，我们看看globalVar++的反<A href="http://dev.21tx.com/language/masm/" target=_blank><FONT color=#3366cc>汇编</FONT></A>代码：<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>00401038 mov eax,[globalVar (0042d3f0)]<BR>0040103D add eax,1<BR>00401040 mov [globalVar (0042d3f0)],eax</TD></TR></TBODY></TABLE><BR>　　在"mov eax,[globalVar (0042d3f0)]" 指令与"add eax,1" 指令以及"add eax,1" 指令与"mov [globalVar (0042d3f0)],eax"指令之间都可能发生线程切换，使得程序的执行后globalVar的结果不能确定。我们可以使用Interlocked<A href="http://dev.21tx.com/corp/exchange/" target=_blank><FONT color=#3366cc>Exchange</FONT></A>Add函数解决这个问题：<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>int globalVar = 0;<BR><BR>DWORD WINAPI ThreadFunc1(LPVOID n)<BR>{<BR>　InterlockedExchangeAdd(&amp;globalVar,1);<BR>　return 0;<BR>}<BR>DWORD WINAPI ThreadFunc2(LPVOID n)<BR>{<BR>　InterlockedExchangeAdd(&amp;globalVar,1);<BR>　return 0;<BR>}</TD></TR></TBODY></TABLE><BR>　　InterlockedExchangeAdd保证对变量globalVar的访问具有"原子性"。互锁访问的控制速度非常快，调用一个互锁函数的CPU周期通常小于50，不需要进行用户方式与内核方式的切换（该切换通常需要运行1000个CPU周期）。<BR><BR>　　互锁访问函数的缺点在于其只能对单一变量进行原子访问，如果要访问的资源比较复杂，仍要使用临界区或互斥。<BR><BR>　　可等待定时器<BR><BR>　　可等待定时器是在某个时间或按规定的间隔时间发出自己的信号通知的内核对象。它们通常用来在某个时间执行某个操作。<BR><BR>　　创建可等待定时器<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>HANDLE CreateWaitableTimer(<BR>　PSECURITY_ATTRISUTES psa,<BR>　BOOL fManualReset,//人工重置或自动重置定时器<BR>PCTSTR pszName);</TD></TR></TBODY></TABLE><BR>　　设置可等待定时器<BR><BR>　　可等待定时器对象在非激活状态下被创建，程序员应调用 SetWaitableTimer函数来界定定时器在何时被激活：<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>BOOL SetWaitableTimer(<BR>　HANDLE hTimer, //要设置的定时器<BR>　const LARGE_INTEGER *pDueTime, //指明定时器第一次激活的时间<BR>　LONG lPeriod, //指明此后定时器应该间隔多长时间激活一次<BR>　PTIMERAPCROUTINE pfnCompletionRoutine,<BR>　PVOID PvArgToCompletionRoutine,<BR>BOOL fResume);</TD></TR></TBODY></TABLE><BR>　　取消可等待定时器<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>BOOl Cancel WaitableTimer(<BR>　HANDLE hTimer //要取消的定时器<BR>);</TD></TR></TBODY></TABLE><BR>　　打开可等待定时器<BR><BR>　　作为一种内核对象，WaitableTimer也可以被其他进程以名字打开：<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>HANDLE OpenWaitableTimer (<BR>　DWORD fdwAccess,<BR>　BOOL bInherithandle,<BR>　PCTSTR pszName<BR>);</TD></TR></TBODY></TABLE><BR>　　<B>实例</B><BR><BR>　　下面给出的一个程序可能发生死锁现象：<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>#include &lt;<A href="http://dev.21tx.com/os/windows/" target=_blank><FONT color=#3366cc>Windows</FONT></A>.h&gt;<BR>#include &lt;stdio.h&gt;<BR>CRITICAL_SECTION cs1, cs2;<BR>long WINAPI ThreadFn(long);<BR>main()<BR>{<BR>　long iThreadID;<BR>　InitializeCriticalSection(&amp;cs1);<BR>　InitializeCriticalSection(&amp;cs2);<BR>　CloseHandle(CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFn, NULL, 0,&amp;iThreadID));<BR>　while (TRUE)<BR>　{<BR>　　EnterCriticalSection(&amp;cs1);<BR>　　printf("\n线程1占用临界区1");<BR>　　EnterCriticalSection(&amp;cs2);<BR>　　printf("\n线程1占用临界区2");<BR><BR>　　printf("\n线程1占用两个临界区");<BR><BR>　　LeaveCriticalSection(&amp;cs2);<BR>　　LeaveCriticalSection(&amp;cs1);<BR><BR>　　printf("\n线程1释放两个临界区");<BR>　　Sleep(20);<BR>　};<BR>　return (0);<BR>}<BR><BR>long WINAPI ThreadFn(long lParam)<BR>{<BR>　while (TRUE)<BR>　{<BR>　　EnterCriticalSection(&amp;cs2);<BR>　　printf("\n线程2占用临界区2");<BR>　　EnterCriticalSection(&amp;cs1);<BR>　　printf("\n线程2占用临界区1");<BR><BR>　　printf("\n线程2占用两个临界区");<BR><BR>　　LeaveCriticalSection(&amp;cs1);<BR>　　LeaveCriticalSection(&amp;cs2);<BR><BR>　　printf("\n线程2释放两个临界区");<BR>　　Sleep(20);<BR>　};<BR>}</TD></TR></TBODY></TABLE><BR>　　运行这个程序，在中途一旦发生这样的输出：<BR><BR>　　线程1占用临界区1<BR><BR>　　线程2占用临界区2<BR><BR>　　或<BR><BR>　　线程2占用临界区2<BR><BR>　　线程1占用临界区1<BR><BR>　　或<BR><BR>　　线程1占用临界区2<BR><BR>　　线程2占用临界区1<BR><BR>　　或<BR><BR>　　线程2占用临界区1<BR><BR>　　线程1占用临界区2<BR><BR>　　程序就"死"掉了，再也运行不下去。因为这样的输出，意味着两个线程相互等待对方释放临界区，也即出现了死锁。<BR><BR>　　如果我们将线程2的控制函数改为：<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>long WINAPI ThreadFn(long lParam)<BR>{<BR>　while (TRUE)<BR>　{<BR>　　EnterCriticalSection(&amp;cs1);<BR>　　printf("\n线程2占用临界区1");<BR>　　EnterCriticalSection(&amp;cs2);<BR>　　printf("\n线程2占用临界区2");<BR><BR>　　printf("\n线程2占用两个临界区");<BR><BR>　　LeaveCriticalSection(&amp;cs1);<BR>　　LeaveCriticalSection(&amp;cs2);<BR><BR>　　printf("\n线程2释放两个临界区");<BR>　　Sleep(20);<BR>　};<BR>}</TD></TR></TBODY></TABLE><BR>　　再次运行程序，死锁被消除，程序不再挡掉。这是因为我们改变了线程2中获得临界区1、2的顺序，消除了线程1、2相互等待资源的可能性。<BR><BR>　　由此我们得出结论，在使用线程间的同步机制时，要特别留心死锁的发生。<BR><img src ="http://www.cppblog.com/ivenher/aggbug/2297.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-12-30 18:51 <a href="http://www.cppblog.com/ivenher/articles/2297.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Win32多线程程序设计之线程通信 （二）</title><link>http://www.cppblog.com/ivenher/articles/2296.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Fri, 30 Dec 2005 10:50:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/2296.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/2296.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/2296.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/2296.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/2296.html</trackback:ping><description><![CDATA[<STRONG>临界区<BR><BR></STRONG>　　定义临界区变量<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>CRITICAL_SECTION gCriticalSection;</TD></TR></TBODY></TABLE><BR>　　通常情况下，CRITICAL_SECTION结构体应该被定义为全局变量，以便于进程中的所有线程方便地按照变量名来引用该结构体。<BR><BR>　　初始化临界区<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>VOID WINAPI InitializeCriticalSection(<BR>　LPCRITICAL_SECTION lpCriticalSection<BR>　//指向程序员定义的CRITICAL_SECTION变量<BR>);</TD></TR></TBODY></TABLE><BR>　　该函数用于对pcs所指的CRITICAL_SECTION结构体进行初始化。该函数只是设置了一些成员变量，它的运行一般不会失败，因此它采用了VOID类型的返回值。该函数必须在任何线程调用EnterCriticalSection函数之前被调用，如果一个线程试图进入一个未初始化的CRTICAL_SECTION，那么结果将是很难预计的。<BR><BR>　　删除临界区<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>VOID WINAPI DeleteCriticalSection(<BR>　LPCRITICAL_SECTION lpCriticalSection<BR>　//指向一个不再需要的CRITICAL_SECTION变量<BR>);</TD></TR></TBODY></TABLE><BR>　　进入临界区<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>VOID WINAPI EnterCriticalSection(<BR>　LPCRITICAL_SECTION lpCriticalSection<BR>　//指向一个你即将锁定的CRITICAL_SECTION变量<BR>);</TD></TR></TBODY></TABLE><BR>　　离开临界区<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>VOID WINAPI LeaveCriticalSection(<BR>　LPCRITICAL_SECTION lpCriticalSection<BR>　//指向一个你即将离开的CRITICAL_SECTION变量<BR>);</TD></TR></TBODY></TABLE><BR>　　使用临界区编程的一般方法是：<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>void UpdateData()<BR>{<BR>　EnterCriticalSection(&amp;gCriticalSection);<BR>　...//do something<BR>　LeaveCriticalSection(&amp;gCriticalSection);<BR>}</TD></TR></TBODY></TABLE><BR>　　关于临界区的使用，有下列注意点：<BR><BR>　　（1）每个共享资源使用一个CRITICAL_SECTION变量；<BR><BR>　　（2）不要长时间运行关键代码段，当一个关键代码段长时间运行时，其他线程就会进入等待状态，这会降低应用程序的运行性能；<BR><BR>　　（3）如果需要同时访问多个资源，则可能连续调用EnterCriticalSection；<BR><BR>　　（4）Critical Section不是OS核心对象，如果进入临界区的线程"挂"了，将无法释放临界资源。这个缺点在Mutex中得到了弥补。<BR><BR>　　<B>互斥</B><BR><BR>　　互斥量的作用是保证每次只能有一个线程获得互斥量而得以继续执行，使用CreateMutex函数创建： <BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>HANDLE CreateMutex(<BR>　LPSECURITY_ATTRIBUTES lpMutexAttributes,<BR>　// 安全属性结构指针，可为NULL<BR>　BOOL bInitialOwner, <BR>　//是否占有该互斥量，TRUE：占有，FALSE：不占有<BR>　LPCTSTR lpName <BR>　//信号量的名称<BR>);<BR></TD></TR></TBODY></TABLE><BR>　　Mutex是核心对象，可以跨进程访问，下面的代码给出了从另一进程访问命名Mutex的例子：<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>HANDLE hMutex;<BR>hMutex = OpenMutex(MUTEX_ALL_<A href="http://dev.21tx.com/database/access/" target=_blank><FONT color=#3366cc>Access</FONT></A>, FALSE, L"mutexName"); <BR>if (hMutex){<BR>　… <BR>｝<BR>else{<BR>　…<BR>}</TD></TR></TBODY></TABLE><BR>　　相关API：<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>BOOL WINAPI ReleaseMutex(<BR>　HANDLE hMutex<BR>);</TD></TR></TBODY></TABLE><BR>　　使用互斥编程的一般方法是：<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>void UpdateResource()<BR>{<BR>　WaitForSingleObject(hMutex,…);<BR>　...//do something<BR>　ReleaseMutex(hMutex);<BR>}</TD></TR></TBODY></TABLE><BR>　　互斥(mutex)内核对象能够确保线程拥有对单个资源的互斥访问权。互斥对象的行为特性与临界区相同，但是互斥对象属于内核对象，而临界区则属于用户方式对象，因此这导致mutex与Critical Section的如下不同：<BR><BR>　　（1） 互斥对象的运行速度比关键代码段要慢；<BR><BR>　　（2） 不同进程中的多个线程能够访问单个互斥对象；<BR><BR>　　（3） 线程在等待访问资源时可以设定一个超时值。<BR><BR>　　下图更详细地列出了互斥与临界区的不同：<BR><BR>
<TABLE width="90%" align=center border=0>
<TBODY>
<TR>
<TD>
<DIV align=center><IMG src="http://image.21tx.com/image/20051216/17850.jpg" border=0></DIV></TD></TR></TBODY></TABLE><BR><img src ="http://www.cppblog.com/ivenher/aggbug/2296.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-12-30 18:50 <a href="http://www.cppblog.com/ivenher/articles/2296.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Win32多线程程序设计之线程通信 （一）</title><link>http://www.cppblog.com/ivenher/articles/2295.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Fri, 30 Dec 2005 10:49:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/2295.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/2295.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/2295.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/2295.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/2295.html</trackback:ping><description><![CDATA[<STRONG>简介<BR><BR></STRONG>　　线程之间<A href="http://news.21tx.com/telcom/" target=_blank><FONT color=#3366cc>通信</FONT></A>的两个基本问题是互斥和同步。<BR><BR>　　线程同步是指线程之间所具有的一种制约关系，一个线程的执行依赖另一个线程的消息，当它没有得到另一个线程的消息时应等待，直到消息到达时才被唤醒。<BR><BR>　　线程互斥是指对于共享的<A href="http://dev.21tx.com/os/" target=_blank><FONT color=#3366cc>操作系统</FONT></A>资源（指的是广义的"资源"，而不是<A href="http://dev.21tx.com/os/windows/" target=_blank><FONT color=#3366cc>Windows</FONT></A>的.res文件，譬如全局变量就是一种共享资源），在各线程访问时的排它性。当有若干个线程都要使用某一共享资源时，任何时刻最多只允许一个线程去使用，其它要使用该资源的线程必须等待，直到占用资源者释放该资源。<BR><BR>　　线程互斥是一种特殊的线程同步。<BR><BR>　　实际上，互斥和同步对应着线程间通信发生的两种情况：<BR><BR>　　（1）当有多个线程访问共享资源而不使资源被破坏时；<BR><BR>　　（2）当一个线程需要将某个任务已经完成的情况通知另外一个或多个线程时。<BR><BR>　　在WIN32中，同步机制主要有以下几种：<BR><BR>　　（1）事件(Event);<BR><BR>　　（2）信号量(semaphore);<BR><BR>　　（3）互斥量(mutex);<BR><BR>　　（4）临界区(Critical section)。<BR><BR>　　<B>全局变量</B><BR><BR>　　因为进程中的所有线程均可以访问所有的全局变量，因而全局变量成为Win32多线程通信的最简单方式。例如：<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>int var; //全局变量<BR>UINT ThreadFunction(LPVOIDpParam)<BR>{<BR>　var = 0;<BR>　while (var &lt; MaxValue)<BR>　{<BR>　　//线程处理<BR>　　::InterlockedIncrement(long*) &amp;var);<BR>　}<BR>　return 0;<BR>}<BR>请看下列程序：<BR>int globalFlag = false; <BR>DWORD WINAPI ThreadFunc(LPVOID n)<BR>{<BR>　Sleep(2000);<BR>　globalFlag = true;<BR><BR>　return 0;<BR>}<BR><BR>int main()<BR>{<BR>　HANDLE hThrd;<BR>　DWORD threadId;<BR><BR>　hThrd = CreateThread(NULL, 0, ThreadFunc, NULL, 0, &amp;threadId);<BR>　if (hThrd)<BR>　{<BR>　　printf("Thread launched\n");<BR>　　CloseHandle(hThrd);<BR>　}<BR><BR>　while (!globalFlag)<BR>　;<BR>　printf("exit\n");<BR>}</TD></TR></TBODY></TABLE><BR>　　上述程序中使用全局变量和while循环查询进行线程间同步，实际上，这是一种应该避免的方法，因为： <BR><BR>　　（1）当主线程必须使自己与ThreadFunc函数的完成运行实现同步时，它并没有使自己进入睡眠状态。由于主线程没有进入睡眠状态，因此操作系统继续为它调度C P U时间，这就要占用其他线程的宝贵时间周期；<BR><BR>　　（2）当主线程的优先级高于执行ThreadFunc函数的线程时，就会发生globalFlag永远不能被赋值为true的情况。因为在这种情况下，系统决不会将任何时间片分配给ThreadFunc线程。<BR><BR>　　<B>事件</B><BR><BR>　　事件(Event)是WIN32提供的最灵活的线程间同步方式，事件可以处于激发状态(signaled or true)或未激发状态(unsignal or false)。根据状态变迁方式的不同，事件可分为两类：<BR><BR>　　（1）手动设置：这种对象只可能用程序手动设置，在需要该事件或者事件发生时，采用SetEvent及ResetEvent来进行设置。<BR><BR>　　（2）自动恢复：一旦事件发生并被处理后，自动恢复到没有事件状态，不需要再次设置。<BR><BR>　　创建事件的函数原型为：<BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>HANDLE CreateEvent(<BR>　LPSECURITY_ATTRIBUTES lpEventAttributes,<BR>　// SECURITY_ATTRIBUTES结构指针，可为NULL<BR>　BOOL bManualReset, <BR>　// 手动/自动<BR>　// TRUE：在WaitForSingleObject后必须手动调用ResetEvent清除信号<BR>　// FALSE：在WaitForSingleObject后，系统自动清除事件信号<BR>　BOOL bInitialState, //初始状态<BR>　LPCTSTR lpName //事件的名称<BR>);</TD></TR></TBODY></TABLE><BR>　　使用"事件"机制应注意以下事项：<BR><BR>　　（1）如果跨进程访问事件，必须对事件命名，在对事件命名的时候，要注意不要与系统命名空间中的其它全局命名对象冲突；<BR><BR>　　（2）事件是否要自动恢复；<BR><BR>　　（3）事件的初始状态设置。<BR><BR>　　由于event对象属于内核对象，故进程B可以调用OpenEvent函数通过对象的名字获得进程A中event对象的句柄，然后将这个句柄用于ResetEvent、SetEvent和WaitForMultipleObjects等函数中。此法可以实现一个进程的线程控制另一进程中线程的运行，例如： <BR><BR>
<TABLE class=txcode cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD>HANDLE hEvent=OpenEvent(EVENT_ALL_<A href="http://dev.21tx.com/database/access/" target=_blank><FONT color=#3366cc>Access</FONT></A>,true,"MyEvent"); <BR>ResetEvent(hEvent);</TD></TR></TBODY></TABLE><BR><img src ="http://www.cppblog.com/ivenher/aggbug/2295.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-12-30 18:49 <a href="http://www.cppblog.com/ivenher/articles/2295.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Windows多线程多任务设计初步</title><link>http://www.cppblog.com/ivenher/articles/983.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Tue, 08 Nov 2005 11:13:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/983.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/983.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/983.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/983.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/983.html</trackback:ping><description><![CDATA[<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD height=40></TD></TR>
<TR>
<TD>
<TABLE class=pagefont cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD style="PADDING-TOP: 5px">　　 ［前言：］当前流行的Windows操作系统，它能同时运行几个程序(独立运行的程序又称之为进程)，对于同一个程序，它又可以分成若干个独立的执行流，我们称之为线程，线程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法，进程和线程的概念的出现，对提高软件的并行性有着重要的意义。现在的应用软件无一不是多线程多任务处理，单线城的软件是不可想象的。因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。本文针对多线程技术在应用中经常遇到的问题，如线程间的通信、同步等，对它们分别进行探讨。 <BR><BR>　　 一、 理解线程 <BR><BR>　　 要讲解线程，不得不说一下进程，进程是应用程序的执行实例，每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。线程的基本思想很简单，它是一个独立的执行流，是进程内部的一个独立的执行单元，相当于一个子程序，它对应Visual C++中的CwinThread类的对象。单独一个执行程序运行时，缺省的运行包含的一个主线程，主线程以函数地址的形式，如main或WinMain函数，提供程序的启动点，当主线程终止时，进程也随之终止，但根据需要，应用程序又可以分解成许多独立执行的线程，每个线程并行的运行在同一进程中。 <BR><BR>　　 一个进程中的所有线程都在该进程的虚拟地址空间中，使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片，在某一个时刻，CPU只执行一个时间片内的线程，多个时间片中的相应线程在CPU内轮流执行，由于每个时间片时间很短，所以对用户来说，仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU的时间，优先级高的线程优先运行，优先级低的线程则继续等待。 <BR><BR>　　 线程被分为两种：用户界面线程和工作线程（又称为后台线程）。用户界面线程通常用来处理用户的输入并响应各种事件和消息，其实，应用程序的主执行线程CWinAPP对象就是一个用户界面线程，当应用程序启动时自动创建和启动，同样它的终止也意味着该程序的结束，进城终止。工作者线程用来执行程序的后台处理任务，比如计算、调度、对串口的读写操作等，它和用户界面线程的区别是它不用从CwinThread类派生来创建，对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版本；最后需要读者明白的是，一个进程中的所有线程共享它们父进程的变量，但同时每个线程可以拥有自己的变量。 <BR>　　 二、 线程的管理和操作 <BR><BR>　　 1． 线程的启动 <BR><BR>　　 创建一个用户界面线程，首先要从类CwinThread产生一个派生类，同时必须使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE来声明和实现这个CwinThread派生类。 <BR><BR>　　 第二步是根据需要重载该派生类的一些成员函数如：ExitInstance()；InitInstance()；OnIdle();PreTranslateMessage()等函数，最后启动该用户界面线程，调用AfxBeginThread()函数的一个版本：CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );其中第一个参数为指向定义的用户界面线程类指针变量，第二个参数为线程的优先级，第三个参数为线程所对应的堆栈大小，第四个参数为线程创建时的附加标志，缺省为正常状态，如为CREATE_SUSPENDED则线程启动后为挂起状态。 <BR><BR>　　 对于工作线程来说，启动一个线程，首先需要编写一个希望与应用程序的其余部分并行运行的函数如Fun1()，接着定义一个指向CwinThread对象的指针变量*pThread,调用AfxBeginThread(Fun1,param,priority)函数，返回值付给pThread变量的同时一并启动该线程来执行上面的Fun1()函数，其中Fun1是线程要运行的函数的名字，也既是上面所说的控制函数的名字，param是准备传送给线程函数Fun1的任意32位值，priority则是定义该线程的优先级别，它是预定义的常数，读者可参考MSDN。 <BR><BR>　　 2．线程的优先级 <BR><BR>　　 以下的CwinThread类的成员函数用于线程优先级的操作： <BR><BR>int GetThreadPriority(); <BR>BOOL SetThradPriority()(int nPriority); <BR><BR>上述的二个函数分别用来获取和设置线程的优先级，这里的优先级，是相对于该线程所处的优先权层次而言的，处于同一优先权层次的线程，优先级高的线程先运行；处于不同优先权层次上的线程，谁的优先权层次高，谁先运行。至于优先级设置所需的常数，自己参考MSDN就可以了，要注意的是要想设置线程的优先级，这个线程在创建时必须具有THREAD_SET_INFORMATION访问权限。对于线程的优先权层次的设置，CwinThread类没有提供相应的函数，但是可以通过Win32 SDK函数GetPriorityClass()和SetPriorityClass()来实现。 <BR><BR>　　 3．线程的悬挂、恢复 <BR><BR>　　 CwinThread类中包含了应用程序悬挂和恢复它所创建的线程的函数，其中SuspendThread()用来悬挂线程，暂停线程的执行；ResumeThread()用来恢复线程的执行。如果你对一个线程连续若干次执行SuspendThread()，则需要连续执行相应次的ResumeThread()来恢复线程的运行。 <BR><BR>　　 4．结束线程 <BR><BR>　　 终止线程有三种途径，线程可以在自身内部调用AfxEndThread()来终止自身的运行；可以在线程的外部调用BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode )来强行终止一个线程的运行，然后调用CloseHandle（）函数释放线程所占用的堆栈；第三种方法是改变全局变量，使线程的执行函数返回，则该线程终止。下面以第三种方法为例，给出部分代码： <BR><BR>//////////////////////////////////////////////////////////////// <BR>//////CtestView message handlers <BR>/////Set to True to end thread <BR>Bool bend=FALSE;//定义的全局变量，用于控制线程的运行 <BR>//The Thread Function <BR>UINT ThreadFunction(LPVOID pParam)//线程函数 <BR>{ <BR>while(!bend) <BR>{Beep(100,100); <BR>Sleep(1000); <BR>} <BR>return 0; <BR>} <BR>CwinThread *pThread; <BR>HWND hWnd; <BR>///////////////////////////////////////////////////////////// <BR>Void CtestView::OninitialUpdate() <BR>{ <BR>hWnd=GetSafeHwnd(); <BR>pThread=AfxBeginThread(ThradFunction,hWnd);//启动线程 <BR>pThread-&gt;m_bAutoDelete=FALSE;//线程为手动删除 <BR>Cview::OnInitialUpdate(); <BR>} <BR>//////////////////////////////////////////////////////////////// <BR>Void CtestView::OnDestroy() <BR>{ bend=TRUE;//改变变量，线程结束 <BR>WaitForSingleObject(pThread-&gt;m_hThread,INFINITE);//等待线程结束 <BR>delete pThread;//删除线程 <BR>Cview::OnDestroy(); <BR>} <BR>　　 三、 线程之间的通信 <BR><BR>　　 通常情况下，一个次级线程要为主线程完成某种特定类型的任务，这就隐含着表示在主线程和次级线程之间需要建立一个通信的通道。一般情况下，有下面的几种方法实现这种通信任务：使用全局变量（上一节的例子其实使用的就是这种方法）、使用事件对象、使用消息。这里我们主要介绍后两种方法。 <BR><BR>　　 1． 利用用户定义的消息通信 <BR><BR>　　 在Windows程序设计中，应用程序的每一个线程都拥有自己的消息队列，甚至工作线程也不例外，这样一来，就使得线程之间利用消息来传递信息就变的非常简单。首先用户要定义一个用户消息，如下所示：#define WM_USERMSG WMUSER+100；在需要的时候，在一个线程中调用 <BR><BR>：：PostMessage((HWND)param,WM_USERMSG,0,0) <BR>或 <BR>CwinThread::PostThradMessage() <BR><BR>来向另外一个线程发送这个消息，上述函数的四个参数分别是消息将要发送到的目的窗口的句柄、要发送的消息标志符、消息的参数WPARAM和LPARAM。下面的代码是对上节代码的修改，修改后的结果是在线程结束时显示一个对话框，提示线程结束： <BR><BR>UINT ThreadFunction(LPVOID pParam) <BR>{ <BR>while(!bend) <BR>{ <BR>Beep(100,100); <BR>Sleep(1000); <BR>} <BR>：：PostMessage(hWnd,WM_USERMSG,0,0)； <BR>return 0; <BR>} <BR>////////WM_USERMSG消息的响应函数为OnThreadended(WPARAM wParam,LPARAM lParam) <BR>LONG CTestView::OnThreadended(WPARAM wParam,LPARAM lParam) <BR>{ <BR>AfxMessageBox("Thread ended."); <BR>Retrun 0; <BR>} <BR><BR>上面的例子是工作者线程向用户界面线程发送消息，对于工作者线程，如果它的设计模式也是消息驱动的，那么调用者可以向它发送初始化、退出、执行某种特定的处理等消息，让它在后台完成。在控制函数中可以直接使用：：GetMessage()这个SDK函数进行消息分检和处理，自己实现一个消息循环。GetMessage()函数在判断该线程的消息队列为空时，线程将系统分配给它的时间片让给其它线程，不无效的占用CPU的时间，如果消息队列不为空，就获取这个消息，判断这个消息的内容并进行相应的处理。 <BR><BR>　　 2．用事件对象实现通信 <BR><BR>　　 在线程之间传递信号进行通信比较复杂的方法是使用事件对象，用MFC的Cevent类的对象来表示。事件对象处于两种状态之一：有信号和无信号，线程可以监视处于有信号状态的事件，以便在适当的时候执行对事件的操作。上述例子代码修改如下： <BR><BR>//////////////////////////////////////////////////////////////////// <BR>Cevent threadStart,threadEnd; <BR>//////////////////////////////////////////////////////////////////// <BR>UINT ThreadFunction(LPVOID pParam) <BR>{ <BR>：：WaitForSingleObject(threadStart.m_hObject,INFINITE); <BR>AfxMessageBox("Thread start."); <BR>while(!bend) <BR>{ <BR>Beep(100,100); <BR>Sleep(1000); <BR>Int result=::WaitforSingleObject(threadEnd.m_hObject,0); <BR>//等待threadEnd事件有信号，无信号时线程在这里悬停 <BR>If(result==Wait_OBJECT_0) <BR>Bend=TRUE; <BR>} <BR>：：PostMessage(hWnd,WM_USERMSG,0,0)； <BR>return 0; <BR>} <BR>///////////////////////////////////////////////////////////// <BR>Void CtestView::OninitialUpdate() <BR>{ <BR>hWnd=GetSafeHwnd(); <BR>threadStart.SetEvent();//threadStart事件有信号 <BR>pThread=AfxBeginThread(ThreadFunction,hWnd);//启动线程 <BR>pThread-&gt;m_bAutoDelete=FALSE; <BR>Cview::OnInitialUpdate(); <BR>} <BR>//////////////////////////////////////////////////////////////// <BR>Void CtestView::OnDestroy() <BR>{ threadEnd.SetEvent(); <BR>WaitForSingleObject(pThread-&gt;m_hThread,INFINITE); <BR>delete pThread; <BR>Cview::OnDestroy(); <BR>} <BR><BR>运行这个程序，当关闭程序时，才显示提示框，显示"Thread ended" <BR>　　 四、 线程之间的同步 <BR><BR>　　 前面我们讲过，各个线程可以访问进程中的公共变量，所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据，以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍的事件对象实际上就是一种同步形式。Visual C++中使用同步类来解决操作系统的并行性而引起的数据不安全的问题，MFC支持的七个多线程的同步类可以分成两大类：同步对象（CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent）和同步访问对象（CmultiLock和CsingleLock）。本节主要介绍临界区（critical section）、互斥（mutexe）、信号量（semaphore），这些同步对象使各个线程协调工作，程序运行起来更安全。 <BR><BR>　　 1． 临界区 <BR><BR>　　 临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中，需要给各个线程提供一个共享的临界区对象，无论哪个线程占有临界区对象，都可以访问受到保护的数据，这时候其它的线程需要等待，直到该线程释放临界区对象为止，临界区被释放后，另外的线程可以强占这个临界区，以便访问共享的数据。临界区对应着一个CcriticalSection对象，当线程需要访问保护数据时，调用临界区对象的Lock()成员函数；当对保护数据的操作完成之后，调用临界区对象的Unlock()成员函数释放对临界区对象的拥有权，以使另一个线程可以夺取临界区对象并访问受保护的数据。同时启动两个线程，它们对应的函数分别为WriteThread()和ReadThread()，用以对公共数组组array[]操作，下面的代码说明了如何使用临界区对象： <BR><BR>#include "afxmt.h" <BR>int array[10],destarray[10]; <BR>CCriticalSection Section; <BR>//////////////////////////////////////////////////////////////////////// <BR>UINT WriteThread(LPVOID param) <BR>{Section.Lock(); <BR>for(int x=0;x&lt;10;x++) <BR>array[x]=x; <BR>Section.Unlock(); <BR>} <BR>UINT ReadThread(LPVOID param) <BR>{ <BR>Section.Lock(); <BR>For(int x=0;x&lt;10;x++) <BR>Destarray[x]=array[x]; <BR>Section.Unlock(); <BR>} <BR><BR>上述代码运行的结果应该是Destarray数组中的元素分别为1-9，而不是杂乱无章的数，如果不使用同步，则不是这个结果，有兴趣的读者可以实验一下。 <BR>　　 2． 互斥 <BR><BR>　　 互斥与临界区很相似，但是使用时相对复杂一些，它不仅可以在同一应用程序的线程间实现同步，还可以在不同的进程间实现同步，从而实现资源的安全共享。互斥与Cmutex类的对象相对应，使用互斥对象时，必须创建一个CSingleLock或CMultiLock对象，用于实际的访问控制，因为这里的例子只处理单个互斥，所以我们可以使用CSingleLock对象，该对象的Lock()函数用于占有互斥，Unlock()用于释放互斥。实现代码如下： <BR><BR>#include "afxmt.h" <BR>int array[10],destarray[10]; <BR>CMutex Section; <BR><BR>///////////////////////////////////////////////////////////// <BR>UINT WriteThread(LPVOID param) <BR>{ CsingleLock singlelock; <BR>singlelock (&amp;Section); <BR>singlelock.Lock(); <BR>for(int x=0;x&lt;10;x++) <BR>array[x]=x; <BR>singlelock.Unlock(); <BR>} <BR>UINT ReadThread(LPVOID param) <BR>{ CsingleLock singlelock; <BR>singlelock (&amp;Section); <BR>singlelock.Lock(); <BR><BR>For(int x=0;x&lt;10;x++) <BR>Destarray[x]=array[x]; <BR>singlelock.Unlock(); <BR><BR>} <BR><BR>　　 3． 信号量 <BR><BR>　　 信号量的用法和互斥的用法很相似，不同的是它可以同一时刻允许多个线程访问同一个资源，创建一个信号量需要用Csemaphore类声明一个对象，一旦创建了一个信号量对象，就可以用它来对资源的访问技术。要实现计数处理，先创建一个CsingleLock或CmltiLock对象，然后用该对象的Lock()函数减少这个信号量的计数值，Unlock()反之。下面的代码分别启动三个线程，执行时同时显示二个消息框，然后10秒后第三个消息框才得以显示。 <BR><BR>///////////////////////////////////////////////////////////////// <BR>Csemaphore *semaphore; <BR>Semaphore=new Csemaphore(2,2); <BR>HWND hWnd=GetSafeHwnd(); <BR>AfxBeginThread(threadProc1,hWnd); <BR>AfxBeginThread(threadProc2,hWnd); <BR>AfxBeginThread(threadProc3,hWnd); <BR>////////////////////////////////////////////////////////////////////// <BR>UINT ThreadProc1(LPVOID param) <BR>{CsingleLock singelLock(semaphore); <BR>singleLock.Lock(); <BR>Sleep(10000); <BR>::MessageBox((HWND)param,"Thread1 had access","Thread1",MB_OK); <BR>return 0; <BR>} <BR>UINT ThreadProc2(LPVOID param) <BR>{CSingleLock singelLock(semaphore); <BR>singleLock.Lock(); <BR>Sleep(10000); <BR>::MessageBox((HWND)param,"Thread2 had access","Thread2",MB_OK); <BR>return 0; <BR>} <BR>UINT ThreadProc3(LPVOID param) <BR>{CsingleLock singelLock(semaphore); <BR>singleLock.Lock(); <BR>Sleep(10000); <BR>::MessageBox((HWND)param,"Thread3 had access","Thread3",MB_OK); <BR>return 0; <BR>} <BR><BR><BR>　　 对复杂的应用程序来说，线程的应用给应用程序提供了高效、快速、安全的数据处理能力。本文讲述了线程中经常遇到的问题，希望对读者朋友有一定的帮助。</TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><img src ="http://www.cppblog.com/ivenher/aggbug/983.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-11-08 19:13 <a href="http://www.cppblog.com/ivenher/articles/983.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>