本节书摘来自异步社区出版社《c++多线程编程实战》一书中的第2章,第2.9节,作者: 【黑山共和国】milos ljumovic(米洛斯 留莫维奇),更多章节内容可以访问云栖社区“异步社区”公众号查看。
既可以在用户空间也可以在内核中实现线程包。具体选择在哪里实现还存在一些争议,在一些实现中可能会混合使用内核线程和用户线程。
我们将讨论在不同地方实现线程包的方法及优缺点。第1种方法是,把整个线程包放进用户空间,内核完全不知道。就内核而言,它管理着普通的单线程进程。这种方法的优点和最显著的优势是,可以在不支持线程的操作系统中实现用户级线程包。
过去,传统的操作系统就采用这种方法,甚至沿用至今。用这种方法,线程可以通过库来实现。所有这些实现都具有相同的通用结构。线程运行在运行时系统的顶部,该系统是专门管理线程的过程集合。我们在前面见过一些例子(<code>createthread</code>、<code>terminatethread</code>等),以后还会见到更多。
下面的程序示例演示了在用户空间中的线程用法。我们要复制大型文件,但是不想一开始就读取整个文件的内容,或者更优化地一部分一部分地读取,而且不用在文件中写入数据。这就涉及2.5节中提到的生产者-消费者问题。
准备就绪
确定安装并运行了visual studio。
操作步骤
1. 创建一个新的win32应用程序项目,并命名为<code>concurrentfilecopy</code>。
2. 打开【解决方案资源管理器】,添加一个新的头文件,命名为<code>concurrentfilecopy.h</code>。打开<code>concurrentfilecopy.h</code>,并输入下面的代码:
tchar* sztitle = _t("concurrent file copy");
tchar* szwindowclass = t(" _cfc_wnd_class_ _");
dword dwreadbytes = 0;
dword dwwritebytes = 0;
dword dwblocksize = 0;
dword dwfilesize = 0;
hlocal pmemory = null;
int winapi _twinmain(hinstance hinstance, hinstance hprev, lptstr
szcmdline, int icmdshow)
{
unreferenced_parameter(hprev);
unreferenced_parameter(szcmdline);
registerwndclass(hinstance);
hwnd hwnd = null;
hwnd hwndpb = null;
if (!(hwnd = initializeinstance(hinstance, icmdshow, hwndpb)))
{
return 1;
}
msg msg = { 0 };
tchar szreadfile[max_path];
tchar szwritefile[max_path];
if (filedialog(hwnd, szreadfile, file_open) && filedialog(hwnd,
szwritefile, file_save))
copydetails copydetails = { hinstance, hwndpb, szreadfile,
szwritefile };
handle hmutex = createmutex(null, false, mutex_name);
handle hreadthread = createthread(null, 0,
(lpthread_start_routine)readroutine, &copydetails, 0, null);
while (getmessage(&msg, null, 0, 0))
{
translatemessage(&msg);
dispatchmessage(&msg);
}
closehandle(hreadthread);
closehandle(hmutex);
else
messagebox(hwnd, _t("cannot open file!"),
_t("error!"), mb_ok);
localfree(pmemory);
unregisterclass(szwindowclass, hinstance);
return (int)msg.wparam;
}
atom registerwndclass(hinstance hinstance)
wndclassex wndex;
wndex.cbsize = sizeof(wndclassex);
wndex.style = cs_hredraw | cs_vredraw;
wndex.lpfnwndproc = wndproc;
wndex.cbclsextra = 0;
wndex.cbwndextra = 0;
wndex.hinstance = hinstance;
wndex.hicon = loadicon(hinstance, makeintresource(idi_application));
wndex.hcursor = loadcursor(null, idc_arrow);
wndex.hbrbackground = (hbrush)(color_window + 1);
wndex.lpszmenuname = null;
wndex.lpszclassname = szwindowclass;
wndex.hiconsm = loadicon(wndex.hinstance, makeintresource(idi_application));
return registerclassex(&wndex);
hwnd initializeinstance(hinstance hinstance, int icmdshow, hwnd& hwndpb)
hwnd hwnd = createwindow(szwindowclass, sztitle, ws_overlapped
| ws_caption | ws_sysmenu | ws_minimizebox, 200, 200, 440, 290,
null, null, hinstance, null);
rect rcclient = { 0 };
int cyvscroll = 0;
if (!hwnd)
return null;
hfont hfont = createfont(14, 0, 0, 0, fw_normal, false, false,
false, baltic_charset, out_default_precis, clip_default_precis,
default_quality, default_pitch | ff_modern,
_t("microsoft sans serif"));
hwnd hbutton = createwindow(_t("button"), _t("close"), ws_child
| ws_visible | bs_pushbutton | ws_tabstop, 310, 200, 100, 25,
hwnd, (hmenu)button_close, hinstance, null);
sendmessage(hbutton, wm_setfont, (wparam)hfont, true);
getclientrect(hwnd, &rcclient);
cyvscroll = getsystemmetrics(sm_cyvscroll);
hwndpb = createwindow(progress_class, (lptstr)null, ws_child |
ws_visible, rcclient.left, rcclient.bottom - cyvscroll,
rcclient.right, cyvscroll, hwnd, (hmenu)0, hinstance, null);
sendmessage(hwndpb, pbm_setstep, (wparam)1, 0);
showwindow(hwnd, icmdshow);
updatewindow(hwnd);
return hwnd;
lresult callback wndproc(hwnd hwnd, uint umsg, wparam wparam, lparam lparam)
switch (umsg)
case wm_command:
switch (loword(wparam))
{
case button_close:
{
destroywindow(hwnd);
break;
}
}
break;
case wm_destroy:
postquitmessage(0);
default:
return defwindowproc(hwnd, umsg, wparam, lparam);
return 0;
dword winapi readroutine(lpvoid lpparameter)
pcopydetails pcopydetails = (pcopydetails)lpparameter;
handle hfile = createfile(pcopydetails->szreadfilename,
generic_read, file_share_read, null, open_existing,
file_attribute_normal, null);
if (hfile == (handle)invalid_handle_value)
return false;
dwfilesize = getfilesize(hfile, null);
dwblocksize = getblocksize(dwfilesize);
handle hwritethread = createthread(null, 0,
(lpthread_start_routine)writeroutine, pcopydetails, 0, null);
size_t ubufferlength = (size_t)ceil((double) dwfilesize / (double)dwblocksize);
sendmessage(pcopydetails->hwndpb, pbm_setrange, 0,
makelparam(0, ubufferlength));
pmemory = localalloc(lptr, dwfilesize);
void* pbuffer = localalloc(lptr, dwblocksize);
int ioffset = 0;
dword dwbytesred = 0;
do
readfile(hfile, pbuffer, dwblocksize, &dwbytesred, null);
if (!dwbytesred)
handle hmutex = openmutex(mutex_all_access, false,
mutex_name);
waitforsingleobject(hmutex, infinite);
memcpy((char*)pmemory + ioffset, pbuffer, dwbytesred);
dwreadbytes += dwbytesred;
releasemutex(hmutex);
ioffset += (int)dwblocksize;
} while (true);
localfree(pbuffer);
closehandle(hfile);
closehandle(hwritethread);
dword winapi writeroutine(lpvoid lpparameter)
handle hfile = createfile(pcopydetails->szwritefilename,
generic_write, 0, null, create_always, file_attribute_normal, null);
dword dwbyteswritten = 0;
int iremainingbytes = (int)dwfilesize - ioffset;
if (iremainingbytes <= 0)
sleep(10);
if (dwwritebytes < dwreadbytes)
dword dwbytestowrite = dwblocksize;
if (!(dwfilesize / dwblocksize))
dwbytestowrite = (dword)iremainingbytes;
handle hmutex = openmutex(mutex_all_access, false, mutex_name);
waitforsingleobject(hmutex, infinite);
writefile(hfile, (char*)pmemory + ioffset, dwbytestowrite,
&dwbyteswritten, null);
dwwritebytes += dwbyteswritten;
releasemutex(hmutex);
sendmessage(pcopydetails->hwndpb, pbm_stepit, 0, 0);
ioffset += (int)dwblocksize;
bool filedialog(hwnd hwnd, lptstr szfilename, dword dwfileoperation)
openfilenamew ofn;
openfilenamea ofn;
tchar szfile[max_path];
zeromemory(&ofn, sizeof(ofn));
ofn.lstructsize = sizeof(ofn);
ofn.hwndowner = hwnd;
ofn.lpstrfile = szfile;
ofn.lpstrfile[0] = '0';
ofn.nmaxfile = sizeof(szfile);
ofn.lpstrfilter = _t("all0.0text0*.txt0");
ofn.nfilterindex = 1;
ofn.lpstrfiletitle = null;
ofn.nmaxfiletitle = 0;
ofn.lpstrinitialdir = null;
ofn.flags = dwfileoperation == file_open ? ofn_pathmustexist |
ofn_filemustexist : ofn_showhelp | ofn_overwriteprompt;
if (dwfileoperation == file_open)
if (getopenfilename(&ofn) == true)
_tcscpy_s(szfilename, max_path - 1, szfile);
return true;
if (getsavefilename(&ofn) == true)
return false;
dword getblocksize(dword dwfilesize)
return dwfilesize > 4096 ? 4096 : 512;
}<code>`</code>
示例分析
我们创建了一个和哲学家就餐示例非常像的ui。例程<code>myregisterclass</code>、<code>initin</code>stance和<code>wndproc</code>几乎都一样。我们在程序中添加<code>filedialog</code>来询问用户读写文件的路径。为了读和写,分别启动了两个线程。
操作系统的调度十分复杂。我们根本不知道是调度算法还是硬件中断使得某线程被调度在cup中执行。这意味着写线程可能在读线程之前执行。出现这种情况会导致一个异常,因为写线程没东西可写。
因此,我们在写操作中添加了<code>if</code>条件,如下代码所示: