当前位置:文档之家› 多任务和多线程windows 程序设计

多任务和多线程windows 程序设计

多任务和多线程windows 程序设计
多任务和多线程windows 程序设计

多任务和多线程

多任务是一个操作系统可以同时执行多个程序的能力。基本上,操作系统使用一个硬件时钟为同时执行的每个程序配置「时间片段」。如果时间片段够小,并且机器也没有由于太多的程序而超出负荷时,那么在使用者看来,所有的这些程序似乎在同时执行着。

多任务并不是什么新的东西。在大型计算机上,多任务是必然的。这些大型主机通常有几十甚至几百个终端机和它连结,而每个终端机使用者都应该感觉到他或者她独占了整个计算机。另外,大型主机的操作系统通常允许使用者「提交工作到背景」,这些背景作业可以在使用者进行其它工作时,由机器执行完成。

个人计算机上的多任务花了更长的时间才普及化。但是现在PC多任务也被认为是很正常的了。我马上就会讨论到,Microsoft Windows的16位版本支持有限度的多任务,Windows的32位版本支持真正的多任务,而且,还多了一种额外的优点,多线程。

多线程是在一个程序内部实作多任务的能力。程序可以把它自己分隔为各自独立的「线程」,这些线程似乎也同时在执行着。这一概念初看起来似乎没有什么用处,但是它可以让程序使用多执行绪在背景执行冗长作业,从而让使用者不必长时间地无法使用其计算机进行其它工作(有时这也许不是人们所希望的,不过这种时候去冲冲凉或者到冰箱去看看总是很不错的)!但是,即使在计算机繁忙的时候,使用者也应该能够使用它。

多任务的各种模式

在PC的早期,有人曾经提倡未来应该朝多任务的方向前进,但是大多数的人还是很迷惑:在一个单使用者的个人计算机上,多任务有什么用呢?好了,最后事实表示即使是不知道这一概念的使用者也都需要多任务的。

DOS下的多任务

在最初PC上的Intel 8088微处理器并不是为多任务而设计的。部分原因(我在上一章中讨论过)是内存管理不够强。当启动和结束多个程序时,多任务的操作系统通常需要移动内存块以收集空闲内存。在8088上是不可能透明于应用系统来做到这一点的。

DOS本身对多任务没有太大的帮助,它的设计目的是尽可能小巧,并且与独立于应用程序之外,因此,除了加载程序以及对程序提供文件系统的存取功能,它几乎没有提供任何支持。不过,有创意的程序写作者仍然在DOS的早期就找到了一种克服这些缺陷的方法,大多数是使用常驻(TSR:terminate-and-stay-resident)程序。有些TSR,比如背景打印队列程序等,透过拦截硬件时钟中断来执行真正的背景处理。其它的TSR,诸如SideKick等弹出式工具,可以执行某种型态的工作切换-暂停目前的应用程序,执行弹出式工具。DOS也逐渐有所增强以便提供对TSR的支持。

一些软件厂商试图在DOS之上架构出工作切换或者多任务的外壳程序(shell)(诸如Quarterdeck的DesqView),但是在这些环境中,仅有其中一个占据了大部分市场,当然,这就是Windows。

非优先权式的多任务

当Microsoft在1985年发表Windows 1.0时,它是最成熟的解决方案,目的是突破DOS的局限。Windows在实际模式下执行。但是即使这样,它已可以在物理内存中移动内存块。这是多任务的前提,虽然移动的方法尚未完全透明于应用程序,但是几乎可以忍受了。

在图形窗口环境中,多任务比在一种命令列单使用者操作系统中显得更有意义。例如,在传统的命令列UNIX中,可以在命令列之外执行程序,让它们在背景执行。然而,程序的所有

显示输出必须被重新转向到一个文件中,否则输出将和使用者正在做的事情混在一起。

窗口环境允许多个程序在相同屏幕上一起执行,前后切换非常容易,并且还可以快速地将数据从一个程序移动到另一个程序中。例如,将绘图程序中建立的图片嵌入由文书处理程序编辑的文本文件中。在Windows中,以多种方式支持数据转移,首先是使用剪贴簿,后来又使用动态数据交换(DDE),而现在则是透过对象连结和嵌入(OLE)。

不过,早期Windows的多任务实作还不是多使用者操作系统中传统的优先权式的分时多任务。这些操作系统使用系统时钟周期性地中断一个工作并开始另一个工作。Windows的这些16位版本支持一种被称为「非优先权式的多任务」,由于Windows消息驱动的架构而使这种型态的多任务成为可能。通常情况下,一个Windows程序将在内存中睡眠,直到它收到一个消息为止。这些消息通常是使用者的键盘或鼠标输入的直接或间接结果。当处理完消息之后,程序将控制权返回给Windows。

Windows的16位版本不会绝对地依据一个timer tick将控制权从一个Windows程序切换到另一个,任何的工作切换都发生在当程序完成对消息的处理后将控制权返回给Windows时。这种非优先权式的多任务也被称为「合作式的多任务」,因为它要求来自应用程序方面的一些合作。一个Windows程序可以占用整个系统,如果它要花很长一段时间来处理消息的话。虽然非优先权式的多任务是16位Windows的一般规则,但仍然出现了某些形式的优先权式多任务。Windows使用优先权式多任务来执行DOS程序,而且,为了实作多媒体,还允许动态链接库接收硬件时钟中断。

16位Windows包括几个功能特性来帮助程序写作者解决(或者,至少可以说是对付)非优先权式多任务中的局限,最显著的当然是时钟式鼠标光标。当然,这并非一种解决方案,而仅仅是让使用者知道一个程序正在忙于处理一件冗长作业,因而让使用者在一段时间内无法使用系统。另一种解决方案是Windows定时器,它允许程序周期性地接收消息并完成一些工作。定时器通常用于时钟应用和动画。

针对非优先权式多任务的另一种解决方案是PeekMessage函数呼叫,我们曾在第五章中的RANDRECT程序里看到过。一个程序通常使用GetMessage呼叫从它的消息队列中找寻下一个消息,不过,如果在消息队列中没有消息,那么GetMessage不会传回,一直到出现一个消息为止。而另一方面,PeekMessage将控制权传回程序,即使没有等待的消息。这样,一个程序可以执行一个冗长作业,并在程序代码中混入PeekMessage呼叫。只要没有这个程序或其它任何程序的消息要处理,那么这个冗长作业将继续执行。

Presentation Manager和序列化的消息队列

Microsoft在一种半DOS/半Windows的环境下实作多任务的第一个尝试(和IBM合作)是OS/2和Presentation Manager(缩写成PM )。虽然OS/2明确地支持优先权式多任务,但是这种多任务方式似乎并未在Presentation Manager中得以落实。问题在于PM序列化来自键盘和鼠标的使用者输入消息。这意味着,在前一个使用者输入消息被完全处理以前,PM不会将一个键盘或者鼠标消息传送给程序。

尽管键盘和鼠标消息只是一个PM(或者Windows)程序可以接收的许多消息中的几个,大多数的其它消息都是键盘或者鼠标事件的结果。例如,菜单命令消息是使用者使用键盘或者鼠标进行菜单选择的结果。在处理菜单命令消息时,键盘或者鼠标消息并未完全被处理。序列化消息队列的主要原因是允许使用者的预先「键入」键盘按键和预先「按入」鼠标按钮。如果一个键盘或者鼠标消息导致输入焦点从一个窗口切换到另一个窗口,那么接下来的键盘消息应该进入拥有新的输入焦点的窗口中去。因此,系统不知道将下一个使用者输入消息发送到何处,直到前一个消息被处理完为止。

目前的共识是不应该让一个应用系统有可能占用整个系统,而这需要非序列化的消息队列,

32位版本的Windows支持这种消息队列。如果一个程序正在忙着处理一项冗长作业,那么您可以将输入焦点切换到另一个程序中。

多线程解决方案

我讨论OS/2的Presentation Manager,只是因为它是第一个为早期的Windows程序写作者(比如我自己)介绍多线程的环境。有趣的是,PM实作多线程的局限为程序写作者提供了应该如何架构多线程程序的必要线索。即使这些限制在32位的Windows中已经大幅减少,但是从更有限的环境中学到的经验仍然是非常有效的。因此,让我们继续讨论下去。

在一个多线程环境中,程序可以将它们自己分隔为同时执行的片段(叫做执行绪)。对执行绪的支持是解决PM中存在的序列化消息队列的最好方法,并且在Windows中线程有更实际的意义。

就程序代码来说,一个线程简单地被表示为可能呼叫程序中其它函数的函数。程序从其主线程开始执行,这个主执行绪是在传统的C程序中叫做main的函数,而在Windows中是WinMain。一旦执行起来,程序可以通过在系统呼叫CreateThread中指定初始线程函数的名称来建立新的线程的执行。操作系统在执行绪之间优先权式地切换控件,和它在程序之间切换控制权的方法非常类似。

在OS/2的Presentation Manager中,每个线程可以建立一个消息队列,也可以不建立。如果希望从线程建立窗口,那么一个PM线程必须建立消息队列。否则,如果只是进行许多的数据处理或者图形输出,那么线程不需要建立消息队列。因为无消息队列的程序不处理消息,所以它们将不会当住系统。唯一的限制是一个无消息队列线程无法向一个消息队列线程中的窗口发送消息,或者呼叫任何发送消息的函数(不过,它们可以将消息递送给消息队列线程)。这样,PM程序写作者学会了如何将它们的程序分隔为一个消息队列线程(在其中建立所有的窗口并处理传送给窗口的消息)和一个或者多个无消息队列线程,在其中执行冗长的背景工作。PM程序写作者还了解到「1/10秒规则」,大体上,程序写作者被告知,一个消息队列线程处理任何消息都不应该超过1/10秒,任何花费更长时间的事情都应该在另一个线程中完成。如果所有的程序写作者都遵循这一规则,那么将没有PM程序会将系统当住超过1/10秒。多线程架构

我已经说过PM的限制让程序写作者理解如何在图形环境中执行的程序里头使用多个执行绪提供了必要的线索。因此在这里我将为您的程序建议一种架构:您的主执行绪建立您程序所需要的所有窗口,并在其中包含所有的窗口消息处理程序,以便处理这些窗口的所有消息;所有其它执行绪只进行一些背景处理,除了和主执行绪通讯,它们不和使用者进行交流。可以把这种架构想象成:主线程处理使用者输入(和其它消息),并建立程序中的其它线程,这些附加的线程完成与使用者无关的工作。

换句话说,您程序的主线程是一个老板,而您的其它线程是老板的职员。老板将大的工作丢给职员处理,而他自己保持和外界的联系。因为那些线程仅仅是职员,所以其它线程不会举行它们自己的记者招待会。它们会认真地完成自己的工作,将结果报告给老板,并等待他们的下一个任务。

一个程序中的线程是同一程序的不同部分,因此他们共享程序的资源,如内存和打开的文件。因为线程共享程序的内存,所以他们还共享静态变量。然而,每个线程都有他们自己的堆栈,因此动态变量对每个线程是唯一的。每个线程还有各自的处理器状态(和数学协处理器状态),这个状态在进行线程切换期间被储存和恢复。

线程间的「争吵」

正确地设计、写作和测试一个复杂的多线程应用程序显然是Windows程序写作者可能遇到的最困难的工作之一。因为优先权式多任务系统可以在任何时刻中断一个线程,并将控制权切换到另一个线程中,在两个线程之间可能有无法预料的随机交互作用的情况。

多线程程序中的一个常见的错误被称为「竞争状态(race condition)」,这发生在程序写作者假设一个线程在另一个线程需要某资料之前已经完成了某些处理(如准备数据)的时候。为了帮助协调线程的活动,操作系统要求各种形式的同步。一种是同步信号(semaphore),它允许程序写作者在程序代码中的某一点阻止一个线程的执行,直到另一个执行绪发信号让它继续为止。类似于同步信号的是「临界区域(critical section)」,它是程序代码中不可中断的部分。

但是同步信号还可能产生称为「死锁(deadlock)」的常见线程错误,这发生在两个线程互相阻止了另一个的执行,而继续执行的唯一办法又是它们继续向前执行。

幸运的是,32位程序比16位程序更能抵抗线程所涉及的某些问题。例如,假定一个线程执行下面的简单叙述:

lCount++ ;

其中lCount是由其它线程使用的一个32位的long型态变量,C中的这个叙述被编译为两条机械码指令,第一条将变量的低16位加1,而第二条指令将任何可能的进位加到高16位上。假定操作系统在这两个机械码指令之间中断了线程。如果lCount在第一条机械码指令之前是0x0000FFFF,那么lCount在线程被中断时为0,而这正是另一个线程将看到的值。只有当线程继续执行时,lCount才会增加到正确的值0x00010000。

这是那些偶尔会导致操作问题的错误之一。在16位程序中,解决此问题正确的方法是将叙述包含在一个临界区域中,在这期间线程不会被中断。然而,在一个32位程序中,该叙述是正确的,因为它被编译为一条机械码指令。

Windows的好处

32位Windows版本(包括Windows NT和Windows 98)有一个非序列化的消息队列。这种实作似乎非常好:如果一个程序正在花费一段长时间处理一个消息,那么鼠标位于该程序的窗口上时,鼠标光标将呈现为一个时钟,但是当将鼠标移到另一个程序的窗口上时,鼠标光标将变为正常的箭头形状。只需按一下就可以将另一个窗口提到前面来。

然而,使用者仍然不能使用正在处理大量工作的那个程序,因为那些工作会阻止程序接收其它消息,这不是我们所希望的。一个程序应该总是能随时处理消息的,所以这时就需要使用从属线程了。

在Windows NT和Windows 98中,没有消息队列线程和无消息队列线程的区别,每个线程在建立时都会有它自己的消息队列,从而减少了PM程序中关于线程的一些不便规定(然而,在大多数情况下,您仍然想通过一条专门处理消息的线程中的消息程序处理输入,而将冗长作业交给那些不包含窗口的线程处理,这种结构几乎总是最容易理解的,我们将看到这一点)。

还有更好的事情:Windows NT和Windows 98中有个函数允许线程杀死同一程序中的另一个线程。当您开始编写多线程程序代码时,您将会发现这种功能在有时是很方便的。OS/2的早期版本没有「杀死线程」的函数。

最后的好消息(至少对这里的话题是好消息)是Windows NT和Windows 98实作了一些被称为「线程区域储存空间(TLS:thread local storage)」的功能。为了了解这一点,回顾一下我在前面提到过的,静态变量(对一个函数来说,既是整体又是区域变量)在线程之间是被共享的,因为它们位于程序的数据储存空间中。动态变量(对一个函数来说总是区域变量)

对每一个线程则是唯一的,因为它们占据堆栈上的空间,而每个线程都有它自己的堆栈。有时让两个或多个线程使用相同的函数,而让这些线程使用唯一于线程的静态变量,那会带来很大便利。这就是线程区域储存空间,其中涉及一些Windows函数呼叫,但是Microsoft 还为C编译器进行扩展,使线程区域储存空间的使用更透明于程序写作者。

新改良过的!支持多线程了!

既然已经介绍了线程的现状,让我们来展望一下线程的未来。有时,有人会出现一种使用操作系统所提供的每一种功能特性的冲动。最坏的情况是,当您的老板走到您的桌前并说:「我听说这种新功能非常炫,让我们在自己的程序中用一些这种新功能吧。」然后您将花费一个星期的时间,试图去了解您的应用程序如何从这种新功能获益。

应该注意的是,在并不需要多线程的应用系统中加入多线程是没有任何意义的。如果您的程序显示沙漏光标的时间太长,或者如果它使用PeekMessage呼叫来避免沙漏光标的出现,那么请重新规划您的程序架构,使用多线程可能会是一个好主意。其它情形,您是在为难您自己,并可能会在程序代码中产生新的错误。

在某些情况下,沙漏光标的出现可能是完全适当的。我在前面提到过「1/10秒规则」,而将一个大文件加载内存可能会花费多于1/10秒的时间,这是否意味着文件加载例程应该在分离的线程中实作呢?没有必要。当使用者命令一个程序打开文件时,他或者她通常想立即完成该操作。将文件加载例程放在分离的线程中只会增加额外的负担。即使您想向您的朋友夸耀您在编写多线程程序,也完全不值得这样做!

Windows 的多线程处理

建立新的线程的API函数是CreateThread,它的语法如下:

hThread = CreateThread (&security_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThread) ;

第一个参数是指向SECURITY_A TTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,它被设为NULL。第二个参数是用于新线程的初始堆栈大小,默认值为0。在任何情况下,Windows根据需要动态延长堆栈的大小。

CreateThread的第三个参数是指向线程函数的指标。函数名称没有限制,但是必须以下列形式声明:

DWORD WINAPI ThreadProc (PVOID pParam) ;

CreateThread的第四个参数为传递给ThreadProc的参数。这样主线程和从属线程就可以共享数据。

CreateThread的第五个参数通常为0,但当建立的线程不马上执行时为旗标CREATE_SUSPENDED。线程将暂停直到呼叫ResumeThread来恢复线程的执行为止。第六个参数是一个指标,指向接受执行绪ID值的变量。

大多数Windows程序写作者喜欢用在PROCESS.H表头文件中声明的C执行时期链接库函数_beginthread。它的语法如下:

hThread = _beginthread (ThreadProc, uiStackSize, pParam) ;

它更简单,对于大多数应用程序很完美,这个线程函数的语法为:

void __cdecl ThreadProc (void * pParam) ;

再论随机矩形

程序20-1 RNDRCTMT是第五章里的RANDRECT程序的多线程版本,您将回忆起RANDRECT使用的是PeekMessage循环来显示一系列的随机矩形。

程序20-1 RNDRCTMT

RNDRCTMT.C

/*---------------------------------------------------------------------------

RNDRCTMT.C -- Displays Random Rectangles (c) Charles Petzold, 1998

-------------------------------------------------------------------------*/

#include

#include

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

HWND hwnd ;

int cxClient, cyClient ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static TCHAR szAppName[] = TEXT ("RndRctMT") ;

MSG msg ;

WNDCLASS wndclass ;

wndclass.style = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc = WndProc ;

wndclass.cbClsExtra = 0 ;

wndclass.cbWndExtra = 0 ;

wndclass.hInstance = hInstance ;

wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName = NULL ;

wndclass.lpszClassName = szAppName ;

if (!RegisterClass (&wndclass))

{

MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ;

return 0 ;

} hwnd = CreateWindow ( szAppName, TEXT ("Random Rectangles"),

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT,

NULL, NULL, hInstance, NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

VOID Thread (PVOID pvoid)

{

HBRUSH hBrush ;

HDC hdc ;

int xLeft, xRight, yTop, yBottom, iRed, iGreen, iBlue ;

while (TRUE)

{

if (cxClient != 0 || cyClient != 0)

{

xLeft = rand () % cxClient ;

xRight = rand () % cxClient ;

yTop = rand () % cyClient ;

yBottom = rand () % cyClient ;

iRed = rand () & 255 ;

iGreen = rand () & 255 ;

iBlue = rand () & 255 ;

hdc = GetDC (hwnd) ;

hBrush = CreateSolidBrush (RGB (iRed, iGreen, iBlue)) ;

SelectObject (hdc, hBrush) ;

Rectangle (hdc,min (xLeft, xRight), min (yTop, yBottom),

max (xLeft, xRight), max (yTop, yBottom)) ;

ReleaseDC (hwnd, hdc) ;

DeleteObject (hBrush) ;

}

}

}

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

{

switch (message)

{

case WM_CREATE:

_beginthread (Thread, 0, NULL) ;

return 0 ;

case WM_SIZE:

cxClient = LOWORD (lParam) ;

cyClient = HIWORD (lParam) ;

return 0 ;

case WM_DESTROY:

PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, message, wParam, lParam) ;

}

在建立多线程的Windows程序时,需要在「Project Settings」对话框中做一些修改。选择「C/C++」页面标签,然后在「Category」下拉式清单方块中选择「Code Generation」。在「Use Run-Time Library」下拉式清单方块中,可以看到用于「Release」设定的「Single-Threaded」和用于Debug设定的「Debug Single-Threaded」。将这些分别改为「Multithreaded」和「Debug Multithreaded」。这将把编译器旗标改为/MT,它是编译器在编译多线程的应用程序所需要的。具体地说,编译器将在.OBJ文件中插入LIBCMT.LIB文件名,而不是LIBC.LIB。连结程序使用这个名称与执行期链接库函数连结。

LIBC.LIB和LIBCMT.LIB文件包含C语言链接库函数,有些C语言链接库函数包含静态数据。例如,由于strtok函数可能被连续地多次呼叫,所以它在静态内存中储存了一个指标。在多线程程序中,每个线程必须在strtok函数中有它自己的静态指针。因此,这个函数的多线程版本稍微不同于单线程的strtok函数。

同时请注意,我在RNDRCTMT.C中包含了表头文件PROCESS.H,这个文件定义一个名为_beginthread的函数,它启动一个新的线程。只有定义了_MT标识符,才会声明这个函数,这是/MT旗标的另一个结果。

在RNDRCTMT.C的WinMain函数中,由CreateWindow传回的hwnd值被储存在一个整体变量中,因此cxClient和cyClient值也可以由窗口消息处理程序的WM_SIZE消息获得。

窗口消息处理程序以最容易的方法呼叫_beginthread-简单地以线程函数的地址(称为Thread)作为第一个参数,其它参数使用0,线程函数传回VOID并有一个参数,该参数是一个指向VOID的指标。在RNDRCTMT中的Thread函数不使用这个参数。

在呼叫了_beginthread函数之后,线程函数(以及该线程函数可能呼叫的其它任何函数)中的程序代码和程序中的其它程序代码同时执行。两个或者多个执行绪使用一个程序中的同一函数,在这种情况下,动态区域变量(储存在堆栈上)对每个执行绪是唯一的。对程序中的所有执行绪来说,所有的静态变量都是一样的。这就是窗口消息处理程序设定整体的cxClient 和cyClient变量并由Thread函数使用的方式。

有时您需要唯一于各个线程的持续储存性数据。通常,这种数据是静态变量,但在Windows 98中,您可以使用「线程区域储存空间」,我将在本章后面进行讨论。

程序设计竞赛的问题

1986年10月3日,Microsoft举行了为期一天,针对计算机杂志出版社的技术编辑和作者的简短的记者招待会,来讨论他们当时的一组语言产品,包括他们的第一个交谈式开发环境,QuickBASIC 2.0。当时,Windows 1.0出现还不到一年,但是没有人知道我们什么时候能得到与该环境类似的东西(这花了好几年)。这一事件与众不同的部分原因是由于Microsoft 的公关人员所举办的「Storm the Gates」程序设计竞赛。Bill Gates使用QuickBASIC 2.0,而计算机出版社的人员可以使用他们选择的任何语言产品。

竞赛的问题是从公众提出的题目中挑选出来的(挑选那些需要写大约半小时程序来解决的问题),问题如下:

建立一个包含四个窗口的多任务仿真程序。第一个窗口必须显示一系列的递增数,第二个必须显示一系列的递增质数,而第三个必须显示Fibonacci数列(Fibonacci数列以数字0和1开始,后头每一个数都是其前两个数的和-即0、1、1、2、3、5、8等等)。这三个窗口应该在数字达到窗口底部时或者进行滚动,或者自行清除窗口内容。第四个窗口必须显示任意半径

的圆,而程序必须在按下一个Escape键时终止。

当然,在1986年10月,在DOS下执行的这样一个程序最多只能是模拟多任务而已,而且没有一个竞赛者具有足够的勇气-并且其中大多数也没有足够的知识-来为Windows编写这个程序。再者,如果真要这么做,当然不会只花半小时了!

参加这次竞赛的大多数人编写了一个程序来将屏幕分为四个区域,程序中包含一个循环,依次更新每个窗口,然后检查是否按下了Escape键。如同DOS环境下的传统习惯,程序占用了百分之百的CPU处理时间。

如果在Windows 1.0中写程序,那么结果将是类似程序20-2 MULTI1的结果。我说「类似」,是因为我编写的程序是32位的,但程序结构和相当多的程序代码-除了变量和函数参数定义以及Unicode支持-都是相同的。

程序20-2 MULTI1

MULTI1.C

/*--------------------------------------------------------------------------

MULTI1.C -- Multitasking Demo

(c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/

#include

#include

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int cyChar ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

static TCHAR szAppName[] = TEXT ("Multi1") ;

HWND hwnd ;

MSG msg ;

WNDCLASS wndclass ;

wndclass.style = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc = WndProc ;

wndclass.cbClsExtra = 0 ;

wndclass.cbWndExtra = 0 ;

wndclass.hInstance = hInstance ;

wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName = NULL ;

wndclass.lpszClassName = szAppName ;

if (!RegisterClass (&wndclass))

{

MessageBox (NULL, TEXT ("This program requires Windows NT!"),

szAppName, MB_ICONERROR) ;

return 0 ;

}

hwnd = CreateWindow ( szAppName, TEXT ("Multitasking Demo"),

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT, CW_USEDEFAULT,

CW_USEDEFAULT, CW_USEDEFAULT,

NULL, NULL, hInstance, NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&msg, NULL, 0, 0))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

return msg.wParam ;

}

int CheckBottom (HWND hwnd, int cyClient, int iLine)

{

if (iLine * cyChar + cyChar > cyClient)

{

InvalidateRect (hwnd, NULL, TRUE) ;

UpdateWindow (hwnd) ;

iLine = 0 ;

}

return iLine ;

}

// -------------------------------------------------------------------------

// Window 1: Display increasing sequence of numbers

// -------------------------------------------------------------------------

LRESULT APIENTRY WndProc1 (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

{

static int iNum, iLine, cyClient ;

HDC hdc ;

TCHAR szBuffer[16] ;

switch (message)

{

case WM_SIZE:

cyClient = HIWORD (lParam) ;

return 0 ;

case WM_TIMER:

if (iNum < 0)

iNum = 0 ;

iLine = CheckBottom (hwnd, cyClient, iLine) ;

hdc = GetDC (hwnd) ;

TextOut (hdc, 0, iLine * cyChar, szBuffer,

wsprintf (szBuffer, TEXT ("%d"), iNum++)) ;

ReleaseDC (hwnd, hdc) ;

iLine++ ;

return 0 ;

}

return DefWindowProc (hwnd, message, wParam, lParam) ;

}

// --------------------------------------------------------------------------

// Window 2: Display increasing sequence of prime numbers

// --------------------------------------------------------------------------

LRESULT APIENTRY WndProc2 (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

{

static int iNum = 1, iLine, cyClient ;

HDC hdc ;

int i, iSqrt ;

TCHAR szBuffer[16] ;

switch (message)

{

case WM_SIZE:

cyClient = HIWORD (lParam) ;

return 0 ;

case WM_TIMER:

do {

if (++iNum < 0)

iNum = 0 ;

iSqrt = (int) sqrt (iNum) ;

for (i = 2 ; i <= iSqrt ; i++)

if (iNum % i == 0) break ;

}

while (i <= iSqrt) ;

iLine = CheckBottom (hwnd, cyClient, iLine) ;

hdc = GetDC (hwnd) ;

TextOut ( hdc, 0, iLine * cyChar, szBuffer,

wsprintf (szBuffer, TEXT ("%d"), iNum)) ;

ReleaseDC (hwnd, hdc) ;

iLine++ ;

return 0 ;

}

return DefWindowProc (hwnd, message, wParam, lParam) ;

}

// --------------------------------------------------------------------------

// Window 3: Display increasing sequence of Fibonacci numbers

// --------------------------------------------------------------------------

LRESULT APIENTRY WndProc3 (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)

{

static int iNum = 0, iNext = 1, iLine, cyClient ;

HDC hdc ;

int iTemp ;

TCHAR szBuffer[16] ;

switch (message)

{

case WM_SIZE:

cyClient = HIWORD (lParam) ;

return 0 ;

case WM_TIMER:

if (iNum < 0)

{

iNum = 0 ;

iNext = 1 ;

}

iLine = CheckBottom (hwnd, cyClient, iLine) ;

hdc = GetDC (hwnd) ;

TextOut ( hdc, 0, iLine * cyChar, szBuffer,

wsprintf (szBuffer, "%d", iNum)) ;

ReleaseDC (hwnd, hdc) ;

iTemp = iNum ;

iNum = iNext ;

iNex += iTemp ;

iLine++ ;

return 0 ;

}

return DefWindowProc (hwnd, message, wParam, lParam) ;

}

// --------------------------------------------------------------------------

// Window 4: Display circles of random radii

// ---------------------------------------------------------------------------

LRESULT APIENTRY WndProc4 (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

{

static int cxClient, cyClient ;

HDC hdc ;

int iDiameter ;

switch (message)

{

case WM_SIZE:

cxClient = LOWORD (lParam) ;

cyClient = HIWORD (lParam) ;

return 0 ;

case WM_TIMER:

InvalidateRect (hwnd, NULL, TRUE) ;

UpdateWindow (hwnd) ;

iDiameter = rand() % (max (1, min (cxClient, cyClient))) ;

hdc = GetDC (hwnd) ;

Ellipse (hdc, (cxClient - iDiameter) / 2,

(cyClient - iDiameter) / 2,

(cxClient + iDiameter) / 2,

(cyClient + iDiameter) / 2) ;

ReleaseDC (hwnd, hdc) ;

return 0 ;

}

return DefWindowProc (hwnd, message, wParam, lParam) ;

}

// --------------------------------------------------------------------------

// Main window to create child windows

// --------------------------------------------------------------------------

LRESULT APIENTRY WndProc ( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

{

static HWND hwndChild[4] ;

static TCHAR * szChildClass[] = { TEXT ("Child1"), TEXT ("Child2"),

TEXT ("Child3"), TEXT ("Child4") } ;

static WNDPROC ChildProc[] = { WndProc1, WndProc2, WndProc3, WndProc4 } ;

HINSTANCE hInstance ;

int i, cxClient, cyClient ;

WNDCLASS wndclass ;

switch (message)

{

case WM_CREATE:

hInstance = (HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE) ;

wndclass.style = CS_HREDRAW | CS_VREDRAW ;

wndclass.cbClsExtra = 0 ;

wndclass.cbWndExtra = 0 ;

wndclass.hInstance = hInstance ;

wndclass.hIcon = NULL ;

wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

实验2-2windows2000 线程同步

实验2 并发与调度 2.2 Windows 2000线程同步 (实验估计时间:120分钟) 背景知识 实验目的 工具/准备工作 实验内容与步骤 背景知识 Windows 2000提供的常用对象可分成三类:核心应用服务、线程同步和线程间通讯。其中,开发人员可以使用线程同步对象来协调线程和进程的工作,以使其共享信息并执行任务。此类对象包括互锁数据、临界段、事件、互斥体和信号等。 多线程编程中关键的一步是保护所有的共享资源,工具主要有互锁函数、临界段和互斥体等;另一个实质性部分是协调线程使其完成应用程序的任务,为此,可利用内核中的事件对象和信号。 在进程内或进程间实现线程同步的最方便的方法是使用事件对象,这一组内核对象允许一个线程对其受信状态进行直接控制 (见表4-1) 。 而互斥体则是另一个可命名且安全的内核对象,其主要目的是引导对共享资源的访问。拥有单一访问资源的线程创建互斥体,所有想要访问该资源的线程应该在实际执行操作之前获得互斥体,而在访问结束时立即释放互斥体,以允许下一个等待线程获得互斥体,然后接着进行下去。 与事件对象类似,互斥体容易创建、打开、使用并清除。利用CreateMutex() API 可创建互斥体,创建时还可以指定一个初始的拥有权标志,通过使用这个标志,只有当线程完成了资源的所有的初始化工作时,才允许创建线程释放互斥体。

为了获得互斥体,首先,想要访问调用的线程可使用OpenMutex() API来获得指向对象的句柄;然后,线程将这个句柄提供给一个等待函数。当内核将互斥体对象发送给等待线程时,就表明该线程获得了互斥体的拥有权。当线程获得拥有权时,线程控制了对共享资源的访问——必须设法尽快地放弃互斥体。放弃共享资源时需要在该对象上调用ReleaseMute() API。然后系统负责将互斥体拥有权传递给下一个等待着的线程(由到达时间决定顺序) 。 实验目的 在本实验中,通过对事件和互斥体对象的了解,来加深对Windows 2000线程同步的理解。 1) 回顾系统进程、线程的有关概念,加深对Windows 2000线程的理解。 2) 了解事件和互斥体对象。 3) 通过分析实验程序,了解管理事件对象的API。 4) 了解在进程中如何使用事件对象。 5) 了解在进程中如何使用互斥体对象。 6) 了解父进程创建子进程的程序设计方法。 工具/准备工作 在开始本实验之前,请回顾教科书的相关内容。 您需要做以下准备: 1) 一台运行Windows 2000 Professional操作系统的计算机。 2) 计算机中需安装Visual C++ 6.0专业版或企业版。 实验内容与步骤 1. 事件对象 2. 互斥体对象 1. 事件对象 清单2-1程序展示了如何在进程间使用事件。父进程启动时,利用CreateEvent() API创建一个命名的、可共享的事件和子进程,然后等待子进程向事件发出信号并终止父进程。在创建时,子进程通过OpenEvent() API打开事件对象,调用SetEvent() API使其转化为已接受信号状态。两个进程在发出信号之后几乎立即终止。 步骤1:登录进入Windows 2000 Professional。 步骤2:在“开始”菜单中单击“程序”-“Microsoft Visual Studio 6.0”–“Microsoft Visual C++ 6.0”命令,进入Visual C++窗口。

Windows多线程程序设计

Windows多线程程序设计- - 1、产生一个线程,只是个框架,没有具体实现。理解::CreateThread函数用法。 #include DWORD WINAPI ThreadFunc(LPVOID); int main() { HANDLE hThread; DWORD dwThreadID; hThread = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(ThreadFunc), NULL, 0, &dwThreadID); ...; return 0; } DWORD WINAPI ThreadFunc(LPVOID lParam) { ...; return 0; } 2、一个真正运转的多线程程序,当你运行它的时候,你会发现(也可能会害怕),自己试试吧。说明了多线程程序是无法预测其行为的,每次运行都会有不同的结果。 #include #include using namespace std; DWORD WINAPI ThreadFunc(LPVOID); int main() { HANDLE hThread; DWORD dwThreadID; // 产生5个线程 for(int i=0; i<5; i++)

{ hThread = ::CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(ThreadFunc), (LPVOID)&i, 0, &dwThreadID); if(dwThreadID) cout << "Thread launched: " << i << endl; } // 必须等待线程结束,以后我们用更好的处理方法 Sleep(5000); return 0; } DWORD WINAPI ThreadFunc(LPVOID lParam) { int n = (int)lParam; for(int i=0; i<3; i++) { cout << n <<","<< n <<","<< n << ","< } return 0; } 3、使用CloseHandle函数来结束线程,应该是“来结束核心对象的”,详细要参见windows 多线程程序设计一书。 修改上面的程序,我们只简单的修改if语句。 if(dwThreadID) { cout << "Thread launched: " << i << endl; CloseHandle(dwThreadID); } 4、GetExitCodeThread函数的用法和用途,它传回的是线程函数的返回值,所以不能用GetExitCodeThread的返回值来判断线程是否结束。 #include #include using namespace std;

一个多线程的windows控制台应用程序

一个多线程的windows控制台应用程序 一、要求: 编写一个单进程、多线程的windows控制台应用程序。 二、平台: Window XP C# 三、内容: 每个进程都有分配给它的一个或多个线程。线程是一个程序的执行部分。 操作系统把极短的一段时间轮流分配给多个线程。时间段的长度依赖于操作系统和处理器。 每个进程都开始一个默认的线程,但是能从它的线程池中创建一个新的线程。 线程是允许进行并行计算的一个抽象概念:在一个线程完成计算任务的同时,另一个线程可以对图像进行更新,两个线程可同时处理同一个进程发出的两个网络请求。 如图所示,选择操作: 1、创建和启动一个线程。在一个进程中同时教和运行两个线程,并且可以不需要停止或者释放一个线程。 相关代码及其解释: public class Threading1:Object { public static void startup() { //创建一个线程数组 Thread[] threads=new Thread[2]; for(int count=0;count

public static void Count() { for(int count=1;count<=9;count++) Console.Write(count+" "); } } 输出结果: 这里通过new方法创建了两个线程,然后使用start()方法来启动线程,两个线程的作用是:两个线程同时从1数到9,并将结果打印出来。 运行上面的程序代码时,可能会在控制台上输出多种不同的结果。从123456789123456789到112233445566778899或121233445566778989在内的各种情况都是可能出现的,输出结果可能与操作系统的调度方式有关。 2、停止线程。当创建一个线程后,可以通过多种属性方法判断该线程是否处于活动状态,启动和停止一个线程等。相关代码及其解释: public class MyAlpha { //下面创建的方法是在线程启动的时候的时候调用 public void Beta() { while(true) { Console.WriteLine("MyAlpha.Beta is running in its own thread."); } } } public class Simple { public static int Stop() { Console.WriteLine("Thread Start/Stop/Join"); MyAlpha TestAlpha=new MyAlpha(); //创建一个线程对象 Thread MyThread=new Thread(new ThreadStart(TestAlpha.Beta)); //开起一个线程 MyThread.Start(); while(!MyThread.IsAlive);

实验五 多线程程序设计(汽院含答案)

实验五多线程程序设计 实验目的 1.掌握Java语言中多线程编程的基本方法 2.掌握Runnable接口实现多线程的方法 3.掌握Thread类实现多线程的用法 实验导读 1.进程和线程的概念 进程是程序一次动态执行的过程,对应从代码加载、执行到执行结束这样一个完整的过程,也是进程自身从产生、发展到消亡的过程。 线程是比进程更小的执行单元,一个进程在执行过程中,可以产生多个线程。每个线程都有自身的产生、执行和消亡的过程。 2.线程的状态与生命周期 ●新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态。此时它 已经有了相应的内存空间和其他资源。 ●运行:线程创建之后就具备了运行的条件,一旦轮到它来享用CPU资源时,即JVM将CPU使用权 切换给该线程时,此线程的就可以脱离创建它的主线程独立开始自己的生命周期了(即run方法执行的过程)。 ●中断:有4种原因的中断,CPU资源从当前线程切换给其他线程、执行了sleep(int millsecond)方法、 执行了wait()方法、进入阻塞状态。 ●死亡:run方法结束。 3.线程的创建 在Java语言中,与线程支持密切相关的是https://www.doczj.com/doc/0c15050837.html,ng.Thread类和https://www.doczj.com/doc/0c15050837.html,ng.Runnable接口。Runnable接口定义很简单,只有一个run方法。任何一个类如果希望自己的实例能够以线程的形式执行,都可以来实现Runnable接口。 继承Thread类和实现Runnable接口,都可以用来创建Thread对象,效果上并没有什么不同。继承Thread 类的方法很明显的缺点就是这个类不能再继承其他的类了,而实现Runnable接口不会有这个麻烦。 另外,在继承Thread类的代码中,this其实就是指当前正在运行的线程对象,如果使用实现Runnable 接口的方式,要得到当前正在执行的线程,需要使用Thread.currentThread()方法。 线程创建后仅仅是占有了内存资源,在JVM管理的线程中还没有这个线程,此线程必须调用start()方法(从父类继承的方法)通知JVM,这样JVM就会知道又有一个新一个线程排队等候切换了。 注意:多次启动一个线程,或者启动一个已经运行的线程对象是非法的,会抛出IllegalThreadStateException异常对象。 4.线程的优先级 同一时刻在等待队列中的线程会有很多个,它们各自任务的重要性有所不同。为了加以区分,使工作安排和资源分配时间更为合理,每个线程可以被赋予不同的优先级,让任务比较急的线程拥有更高的优先级,从而更快地进入执行状态。 Java中提供了10个等级的线程优先级,最低为Thread.MIN_PRIORITY=1,最高为

基于多线程的端口扫描程序课程设计报告

滁州学院 课程设计报告 课程名称: 设计题目:基于多线程的端口扫描程序 院部:计算机与信息工程学院 专业:网络工程 组别:第六组 起止日期: 2012 年12月31日~2013 年1月6日指导教师: 计算机与信息工程学院二○一二年制

课程设计任务书 目录 1 需求分析. 0 1..1 网络安全 0 1.2 课程背景 0 1.3 扫描器 0 1.4 多线程扫描器介绍 (1) 错误! 未定义书签。

错误! 未定义书签。 错误! 未定义书签。 错误! 未定义书签。 1.5 端口扫描 (2) 2 概要设计. (3) 2.1 整体框架设计 (3) 2.2 流程图描述 (3) 3 详细设计. (3) 3.1 端口扫描线程启动 (3) 3.2 GUI 图形界面 (5) 3.3 按钮监听及异常处理 (6) 4 调试与操作说明. (8) 4.1 运行界面 (8) 4.2 扫描结果 (8) 4.3 错误提示 (8) 5 课程设计总结与体会. (8) 6 参考文献. (9) 7 致谢. (9) 8 附录. 0 1 需求分析 1..1 网络安全二十一世纪是信息化、网络化的世纪,信息是社会发展的重要资源。信息安全保障能力是一个国家综合国力、经济竞争实力和生存能力的重要组成部分,是世界各国在奋力攀登的制高点。网络安全是指网络系统的硬件、软件及其系统中的数据受到保护,不因偶然的或者恶意的原因而遭到破坏、更改、泄露,系统连续可靠正常地运行。网络安全包括技术领域和非技术领域两大部分: 非技术领域包括一些制度、政策、管理、安全意识、实体安全

等方面的内容; 技术领域包括隐患扫描、防火墙、入侵检测、访问控制、虚拟专用网、CA 认证、操作系统等方面的内容。这些技术的目标是保证信息的可控性、可用性、保密性、完整性、和不可抵赖性。端口扫描属于安全探测技术范畴,对应于网络攻击技术中的网络信息收集技术。 1.2 课程背景 随着Internet 的不断发展,信息技术已成为促进经济发展、社会进步的巨大推动力。端口扫描技术是网络安全扫描技术一个重要的网络安全技术。与防火墙、入侵检测系统互相配合,能够有效提高网络的安全性。安全扫描是安全技术领域中重要的一类。通过扫描能自动检测远端或本地主机系统信息,包括主机的基本信息(如计算机名、域名、组名、操作系统 型等)、服务信息、用户信息以及漏洞信息,它的重要性在于能够对网络进行安全评估,及时发现安全隐患,防患于未然。 网络的安全状况取决于网络中最薄弱的环节,任何疏忽都有可能引入不安全的因素,最有效的方法是定期对网络系统进行安全分析,及时发现并修正存在的脆弱,保证系统安全。 国外安全扫描技术的历史可以追溯到20 世纪90 年代,当时因特网刚刚起步,但是在过去的十年内,扫描技术飞速发展,迄今为止,其扫描技术已经非常完善,但是在全面性,隐蔽性和智能性上还有待提高。安全扫描从最初专门为UNIX 系统而编写的一些只有简单功能的小程序发展到现在,已经出现了可以运行多个操作系统平台上的,具有复杂功能的系统程序。 国内的扫描技术是在国外的扫描器基础上发展起来的。其中有一些专门从事安全技术的公司。这些公司的扫描器以硬件为主,其特点是执行速度快,不像软件一样受到安装主机系统的限制。 然而对于更多的基于主机的端口扫描而言,简单,实用,可靠才是它们的长处。 1.3 扫描器扫描器是一种自动检测远程或本地主机安全性弱点的程序,通过使用扫描器你可以不留痕迹的发现远程服务器的各种TCP端口的分配。这就能让我们间接的或直观的了解到远程主机所存在的安全问题。为了保证网络中计算机的安全性,必须采取主动策略, 快速、及时、准确、安全的检测出网络中计算机及防火墙开放的和未开放的端口。计算机端口扫描技术就是这种主动防御策略实现的重要技术手段。 扫描器采用模拟攻击的形式对目标可能存在的已知安全漏洞进行逐项检查。目标可以是工作站、服务器、交换机、数据库应用等各种对象。然后根据扫描结果向系统管理员提供周 密可靠的安全性分析报告,为提高网络安全整体水平产生重要依据。在网络安全体系的建设中,安全扫描工具花费低、效果好、见效快、与网络的运行相对对立、安装运行简单,可以大规模减少安全管理员的手工劳动,有利于保持全网安全政策的统一和稳定。 1.4 多线程扫描器介绍 在java 中,组件放置在窗体上的方式是完全基于代码的。组件放置在窗体上的方式通常不是通过绝对坐标控制,而是由“布局管理器”根据组件加入的顺序决定其位置。每个容器都有一个属于的自己布局管理器。使用不同的布局管理器,组件大小,位置和形状将大不相同。表格型布局管理器将容器划分成为一个多行多列的表格,表格的大小全部相同,是由其中最大的组件所决定。通过add 方法可以将组件一一放在每个表格

windows 并发的多线程的应用

(1)苹果香蕉问题 #include using namespace std; #include #include int k; HANDLE Apple_;HANDLE Banana_; CRITICAL_SECTION mmutex; DWORD WINAPI Son(LPVOID n) {//HANDLE Apple_; CRITICAL_SECTION mmutex; int i=1; OpenSemaphore(MUTEX_ALL_ACCESS,false,"Apple_"); while(1) { ::WaitForSingleObject(Apple_,INFINITE);//等苹果 cout<<"Son eats"<

基于ARM的多线程应用程序设计

开放性实验报告 题目: 基于ARM的多线程应用程序设计院系名称:电气工程学院 专业班级:自动1302 学生姓名:张鹏涛 学号:201323020219 指导教师:张晓东

目录 1 系统概述与设计要求 (2) 1.1 系统概述 (2) 1.2 设计要求 (2) 2 方案论证 (2) 2.1 实现方法 (2) 2.2 线程优势 (2) 3 硬件设计 (3) 3.1 树莓派接口驱动LED电路设计 (3) 4 软件设计 (4) 4.1 驱动三色LED灯 (4) 4.1.1 驱动实现方法 (4) 4.1.2 wiringPi库安装和软件编程 (5) 4.2 服务器和客户端 (5) 4.2.1 服务器设计方法 (5) 4.2.2 客户端设计方法 (6) 5 系统调试 (6) 设计心得 (8) 参考文献 (9) 附录1(LED驱动程序) (10) 附录2(服务器程序) (10) 附录3(客户端程序代码) (14)

1 系统概述与设计要求 1.1 系统概述 本系统设计是基于树莓派开发板上实现的,树莓派由注册于英国的慈善组织“Raspberry Pi 基金会”开发,Eben·Upton/埃·厄普顿为项目带头人。2012年3月,英国剑桥大学埃本·阿普顿(Eben Epton)正式发售世界上最小的台式机,又称卡片式电脑,外形只有信用卡大小,却具有电脑的所有基本功能,这就是Raspberry Pi电脑板,中文译名"树莓派"。它是一款基于ARM的微型电脑主板,以SD/MicroSD 卡为内存硬盘,卡片主板周围有1/2/4个USB接口和一个10/100 以太网接口(A型没有网口),可连接键盘、鼠标和网线,同时拥有视频模拟信号的电视输出接口和HDMI高清视频输出接口,以上部件全部整合在一张仅比信用卡稍大的主板上,具备所有PC的基本功能。而树莓派2具有900MHz内核频率,4核ARM Cortex-A7,1GB 内存,带Micro SD 卡插槽(支持通过它启动Linux 操作系统,如Fedora),40PIN接口(可以增加驱动外设)。本系统设计正式在树莓派2环境下开发实现多线程设计,设计的主要功能就是两个客户端通过服务器互相收发信息。 1.2 设计要求 要求多个客户端能够同时连接服务器,而服务器需要创建线程来管理这多个客户端,并且能够把一个客户端发来的数据进行解析,发给另一个客户端,实现两个甚至多个客户端互相收发信息。能够通过驱动三色灯来发现系统运行的状态,红色说明有错误发生,绿色说明正在正常运行,蓝色说明有用户连接,绿色说明有客户端互相收发信息。 2 方案论证 2.1 实现方法 要实现服务器同时管理两个甚至多个客户端,就必须引入进程或线程。 2.2 线程优势 一是和进程相比,它是一种非常"节俭"的多任务操作方式。

Windows下多线程同步机制

多线程同步机制 Critical section(临界区)用来实现“排他性占有”。适用范围是单一进程的各线程之间。它是: ·一个局部性对象,不是一个核心对象。 ·快速而有效率。 ·不能够同时有一个以上的critical section被等待。 ·无法侦测是否已被某个线程放弃。 Mutex Mutex是一个核心对象,可以在不同的线程之间实现“排他性占有”,甚至几十那些现成分属不同进程。它是: ·一个核心对象。 ·如果拥有mutex的那个线程结束,则会产生一个“abandoned”错误信息。 ·可以使用Wait…()等待一个mutex。 ·可以具名,因此可以被其他进程开启。 ·只能被拥有它的那个线程释放(released)。 Semaphore Semaphore被用来追踪有限的资源。它是: ·一个核心对象。 ·没有拥有者。 ·可以具名,因此可以被其他进程开启。 ·可以被任何一个线程释放(released)。 Ev ent Object Ev ent object通常使用于overlapped I/O,或用来设计某些自定义的同步对象。它是: ·一个核心对象。 ·完全在程序掌控之下。 ·适用于设计新的同步对象。 · “要求苏醒”的请求并不会被储存起来,可能会遗失掉。 ·可以具名,因此可以被其他进程开启。 Interlocked Variable 如果Interlocked…()函数被使用于所谓的spin-lock,那么他们只是一种同步机制。所谓spin-lock是一种busy loop,被预期在极短时间内执行,所以有最小的额外负担(overhead)。系统核心偶尔会使用他们。除此之外,interlocked variables主要用于引用技术。他们:·允许对4字节的数值有些基本的同步操作,不需动用到critical section或mutex之类。 ·在SMP(Symmetric Multi-Processors)操作系统中亦可有效运作。 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

多线程编程的原则及要点

2.4多线程编程的原则及要点: 随着多核CPU的出世,多核编程方面的问题将摆上了程序员的日程,有许多老的程序员以为早就有多CPU的机器,业界在多CPU机器上的编程已经积累了很多经验,多核CPU上的编程应该差不多,只要借鉴以前的多任务编程、并行编程和并行算法方面的经验就足够了。 但是,多核机器和以前的多CPU机器有很大的不同,以前的多CPU机器都是用在特定领域,比如服务器,或者一些可以进行大型并行计算的领域,这些领域很容易发挥出多CPU的优势,而现在多核机器则是应用到普通用户的各个层面,特别是客户端机器要使用多核CPU,而很多客户端软件要想发挥出多核的并行优势恐怕没有服务器和可以进行大型并行计算的特定领域简单。 多核CPU中,要很好地发挥出多个CPU的性能的话,必须保证分配到各个CPU上的任务有一个很好的负载平衡。否则一些CPU在运行,另外一些CPU处于空闲,无法发挥出多核CPU 的优势来。 要实现一个好的负载平衡通常有两种方案,一种是静态负载平衡,另外一种是动态负载平衡。 1、静态负载平衡 静态负载平衡中,需要人工将程序分割成多个可并行执行的部分,并且要保证分割成的各个部分能够均衡地分布到各个CPU上运行,也就是说工作量要在多个任务间进行均匀的分配,使得达到高的加速系数。 2、动态负载平衡 动态负载平衡是在程序的运行过程中来进行任务的分配达到负载平衡的目的。实际情况中存在许多不能由静态负载平衡解决的问题,比如一个大的循环中,循环的次数是由外部输入的,事先并不知道循环的次数,此时采用静态负载平衡划分策略就很难实现负载平衡。 动态负载平衡中对任务的调度一般是由系统来实现的,程序员通常只能选择动态平衡的调度策略,不能修改调度策略,由于实际任务中存在很多的不确定因素,调度算法无法做得很优,因此动态负载平衡有时可能达不到既定的负载平衡要求。 3、负载平衡的难题在那里? 负载平衡的难题并不在于负载平衡的程度要达到多少,因为即使在各个CPU上分配的任务执行时间存在一些差距,但是随着CPU核数的增多总能让总的执行时间下降,从而使加速系数随CPU核数的增加而增加。 负载平衡的困难之处在于程序中的可并行执行块很多要靠程序员来划分,当然CPU核数较少时,比如双核或4核,这种划分并不是很困难。但随着核数的增加,划分的粒度将变得越来越细,到了16核以上时,估计程序员要为如何划分任务而抓狂。比如一段顺序执行的代码,放到128核的CPU上运行,要手工划分成128 个任务,其划分的难度可想而知。

在Windows下创建进程和线程的API

利用API在Windows下创建进程和线程 前言: 谈到在Windows创建线程的例子,在网上的很多的参考都是基于MFC的。其实,就操作系统实验这个前提而言,大可不必去碰那个大型的MFC的框架。在Windows命令控制台下可创建进程及线程,做些简单的进程及线程的测试程序。 1、实验准备: 要实验的Windows下的多线程实验,应做如下准备: a) 在新建中选”Win32 Console Application”的An empty project b) 选”工程”的”设置”选项,在”设置”中选择“C/C++”标签,在”Project Option”中,将”MLd”参数改成“MTd”(如图1)。 图1 选项 以上两步对实验成功至关重要,否则,即是代码无误,在连接时同样会出现问题。 2、Windows下进程的创建: Windows的进程和线程模型被描述成”多进程,基于单进程的多线程”。 在创建一个线程时,Windows会做大量的工作---创建一个新的地址空间,为进程分配资源以及创建一个基线程。

CreateProcess函数的原型如下: 虽然有很多参数,不过在现阶段的实验级别,大多数参数只要用默认值即可。 下面要做的关于Windows使用进程的实验,在Linux系统下,可以使用类似: execve(char* cmdName ,char* cmdArgu)的语句从一个程序中去执行其它的程序。 而如果在Windows下,当使用CreateProcess去执行相应的功能时,只要去改变cmdLine中的容即可,其它的参数使用默认值,具体见代码1: 代码1执行的功能是从命令行中启动这个名叫的launch的测试程序,在launch后面应加上保存有需要打开程序路径的文件名: 如在命令行中键入: >launch set.txt 而set.txt中的容为: C:\\WINDOWS\\SYSTEM32\\CALC.EXE C:\\WINDOWS\\SYSTEM32\\NOTEPAD.EXE NEW.TXT C:\\WINDOWS\\SYSTEM32\\CHARMAP.EXE 路径的前半部分为”C:\\WINDOWS\\”,这当然要视你的Windows系统的类型以及系统盘的存放位置而定。如果是NT或2000的机器,则应使用WINNT. /*测试程序1: 示例如使用进程的launch程序(启动程序),通过在命令行中加载相应的命令文件,去按照命令文件中指定的程序路径打开相应的程序去执行*/

Windows多线程及消息队列

1.所谓的worker线程,是指完全不牵扯到图形用户界面(GUI),纯粹做运算的线程。 2.微软的多线程模型: Win32说明文件一再强调线程分为GUI线程和worker线程两种。GUI线程负责建造窗口以及处理主消息循环。Worker负责执行纯粹的运算工作,如重新计算或重新编页等,这些运算工作会导致主线程的消息队列失去反应。一般而言,GUI线程绝不会去做那些不能够马上完成的工作。 GUI线程的定义是:拥有消息队列的线程。任何一个特定窗口的消息总是被产生这一窗口的线程抓到并处理。所有对此窗口的改变也都应该由该线程完成。 如果worker线程也产生了一个窗口,那么就会有一个消息队列随之被产生出来并且附着到此线程身上,于是worker线程摇身一变成了GUI线程。这里的意思是:worker线程不能够产生窗口、对话框、消息框,或任何其他与UI有关的东西。 如果一个worker线程需要输入或输出错误信息,它应该授权给UI线程来做,并且将结果通知给worker线程。 消息队列是一个链表,只有在必要的时候,才有元素产生出来。具体的关于消息队列的数据结构,可以参考相关的windows文档。 3.在Win32中,每一个线程有它自己专属的消息队列。这并不意味着每一个窗口有它自己的消息队列,因为一个线程可以产生许多窗口。如果一个线程停止回应,或是它忙于一段耗时的计算工作,那么由它产生的窗口统统都会停止回应,但系统中的其他窗口还会继续正常工作。 以下是一个非常基本的规则,用来管理Win32中的线程、消息、窗口的互动: 所有传送给某一窗口之消息,将由产生该窗口之线程负责处理。 比方说,使用SetWindowText来更新一个Edit框的内容,其实就是发出了一个WM_SETTEXT 消息给edit窗口函数。推而广之,每一个控件都是一个窗口,都拥有自己的窗口函数。 对窗口所作的一切事情基本上都会被该窗口的窗口函数处理,并因此被产生该窗口的线程处理。当需要发送一个消息时,Windows会自动计算出哪一个线程应该接收到消息(以便确定该消息实体应该挂在在哪一个线程的消息队列中)。同时,windows还会确定线程应该如何被告知有这么一个消息进来。一共有四种可能: (1)如果属于同一线程,使用SendMessage传递消息,则直接调用窗口函数。 (2)如果属于同一线程,使用PostMessage传递消息,则把消息放在消息队列中然后立即返回。(3)如果不属于同一线程,使用SendMessage传递消息,则切换到新线程中并调用窗口函数。在该窗口函数结束之前,SendMessage不会返回。 (4)PostMessage立刻返回,消息则被放到另一线程的消息队列中。 当我send一个消息给另一线程掌握的窗口时,系统必须做一次context switch,切换到另一线程去,调用该窗口函数,然后再做一次contex t switch切换回来,相对一般的函数调用而言,期间的额外负担较大。如果在MDI中,为每个子窗口分配一个线程,那么该子窗口的所有资源——包括画刷,DC,调色板等等都属于线程的资源。此时为线程做context switch时会代价很大。

当前流行的Windows操作系统能同时运行几个程序独立运行

当前流行的Windows操作系统能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义。现在的大型应用软件无一不是多线程多任务处理,单线程的软件是不可想象的。因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。本实例针对多线程技术在应用中经常遇到的问题,如线程间的通信、同步等,分别进行探讨,并利用多线程技术进行线程之间的通信,实现了数字的简单排序。 一、实现方法 1、理解线程 要讲解线程,不得不说一下进程,进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。线程的基本思想很简单,它是一个独立的执行流,是进程内部的一个独立的执行单元,相当于一个子程序,它对应于Visual C++中的CwinThread类对象。单独一个执行程序运行时,缺省地包含的一个主线程,主线程以函数地址的形式出现,提供程序的启动点,如main ()或WinMain()函数等。当主线程终止时,进程也随之终止。根据实际需要,应用程序可以分解成许多独立执行的线程,每个线程并行的运行在同一进程中。 一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,所以对用户来说,仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU 的时间,优先级高的线程优先运行,优先级低的线程则继续等待。 线程被分为两种:用户界面线程和工作线程(又称为后台线程)。用户界面线程通常用来处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程CWinAPP对象就是一个用户界面线程,当应用程序启动时自动创建和启动,同样它的终止也意味着该程序的结束,进程终止。工作线程用来执行程序的后台处理任务,比如计算、调度、对串口的读写操作等,它和用户界面线程的区别是它不用从CWinThread类派生来创建,对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版本;最后需要读者明白的是,一个进程中的所有线程共享它们父进程的变量,但同时每个线程可以拥有自己的变量。

2嵌入式系统设计实验二(多线程)

注意实验二是在实验一的基础上完成其内容,具体环境配置见实验一 目录 实验二多线程应用程序设计 (2) 2.1实验目的 (2) 2.2、实验内容 (2) 2.3、预备知识 (2) 2.4、实验设备及工具 (2) 2.5、实验原理及代码分析 (3) 2.6、实验步骤 (11) 2.7、思考题 (13)

实验二多线程应用程序设计 2.1实验目的 ?了解多线程程序设计的基本原理。 ?学习pthread库函数的使用。 2.2、实验内容 ?读懂pthread.c的源代码,熟悉几个重要的pthread库函数的使用。掌握共享 锁和信号量的使用方法。 ?进入/root/share/exp/basic/02_pthread目录,运行make 产生pthread程序, 使用NFS方式连接开发主机进行运行实验。 2.3、预备知识 ?有C语言基础 ?掌握在Linux下常用编辑器的使用 ?掌握Makefile 的编写和使用 ?掌握Linux下的程序编译与交叉编译过程 2.4、实验设备及工具 ?硬件:UP-TECH S2410/P270 DVP嵌入式实验平台,PC机Pentium 500以上, 硬 盘40G以上,内存大于128M。 ?软件:PC机操作系统REDHAT LINUX 9.0 +MINICOM + ARM-LINUX开发环境

2.5、实验原理及代码分析 1.多线程程序的优缺点 ?多线程程序作为一种多任务、并发的工作方式,有以下的优点: ?1)提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很 长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操 作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线 程,可以避免这种尴尬的情况。 ?2)使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不 同的线程运行于不同的CPU上。 ?3)改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个 独立或半独立的运行部分,这样的程序会利于理解和修改。 ?Libc中的pthread库提供了大量的API函数,为用户编写应用程序提供支持。2.实验源代码与结构流程图 ?本实验为著名的生产者-消费者问题模型的实现,主程序中分别启动生产者线 程和消费者线程。生产者线程不断顺序地将0到1000的数字写入共享的循环 缓冲区,同时消费者线程不断地从共享的循环缓冲区读取数据。流程图如图 2.2.1所示: 图2.1生产者-消费者实验源代码结构流程图

基于Windows多线程环境下的串口通信

第46卷 第3期 武汉大学学报(自然科学版) V ol.46 N o.3 2000年6月 J.W uhan U niv.(N at.Sci.Ed.) June,2000,373~375 文章编号:0253-9888(2000)03-0373-03 基于Windows多线程环境下的串口通信 陈淑珍,石 波 (武汉大学电子信息学学院,武汉430072) 摘 要:根据串口通信的基本原理,结合W indow s环境下的多任务并发机制,采用Window s的多线程技术来实现串口动态实时通信.有效地解决了在串口通信中的实时响应问题,降低了数据的丢失率,提高了系统的可靠性.同时提出了在Window s环境下实现串口通信的一般方法和步骤.实践证明,这种结合多线程技术的串口通信方法具有很强的实用性. 关 键 词:多线程;串行通信;实时查询 中图分类号:T P311.11 文献标识码:A 在实际的工程应用中,应用程序经常需要具备与外围设备进行通信的能力.在与串口,调制解调器,或是通过电话线进行通信的应用程序中,异步串行通信是一种重要的通信手段.在单任务的操作系统中,应用程序不能处理通信过程中的突发和并发事件,这种缺陷会引起数据丢失和不可靠性.而Window s基于线程的多任务并发机制使得应用程序能同时执行不同的任务,达到了降低数据丢失率,提高系统可靠性的目的. 1 串口通信的原理和机制 不论何种通信,背后都需要一个通信协议的支持.串口通信大多采用了美国电子工业协会(EIA)于1969年制定的RS-232标准[1].RS-232标准规定了数据终端设备和数据通信设备之间的连接和通信规则.该协议运用RTS(Request to Send)和(Clear to Send)信号来实现串口和外围设备的硬件“握手”,从而建立通信双方的连接和应答.在通信的连接和应答完成以后,双方就可以在误差允许范围内进行串行通信. 由于Window s是一个基于消息驱动的操作系统,它的很多消息是从硬件反馈过来的.Window s 不允许程序开发人员直接和硬件打交道,在串口通信方面提供了一组API系统函数来管理串口,这对减低编程工作量,提高系统的稳定性和安全性都是很有好处的. 在Win9x操作系统中,对串行通讯设备的操作如同文件的操作一样:串行通讯设备的打开、读写和关闭等操作均与文件操作相同,这和以前Win3x中的通信方式不同.由于w in9x系统中取消了串行通讯中的特定消息WM_COMM NOTIFY(外围通讯设备一有相应事件发生,该消息就会被传送),使得应用程序工作于“事件驱动”方式时,应创建专用的线程来监视有关的串行通讯设备. ●打开和关闭串口.通信会话以调用CreateFile函数开始,为读、写操作打开串口.为实现串口的排他性访问,共享标志应设置成false,创建标志应为o pen_exiting,模板句柄应为null,同时返回串口句柄. 通信会话通过调用CloseHandle函数来关闭串口占用的内存句柄,释放相应的串口资源. ●初始化和配置串口.一旦串口处于打开状态,Window s就可以给串口分配接受和发送缓冲区.缓冲区的大小既可以缺省,也可以指定(调用SetupComm函数). 配置串口需要设置串口通讯中特定事件的掩码(调用SetCom mM ask),只要串口中出现特定的消 收稿日期:2000-02-22  基金项目:九五国家重点科技攻关项目(204980340) 作者简介:陈淑珍(1946-),女,教授,现从事计算机网络与多媒体研究.

POSIX线程程序设计(中文版)

POSIX 多线程程序设计 Blaise Barney, Lawrence Livermore National Laboratory 目录表 1.摘要 2.Pthreads 概述 1.什么是线程? 2.什么是Pthreads? 3.为什么使用Pthreads? 4.使用线程设计程序 3.Pthreads API编译多线程程序 4.线程管理 1.创建和终止线程 2.向线程传递参数 3.连接(Joining)和分离(Detaching)线程 4.栈管理 5.其它函数 5.互斥量(Mutex Variables) 1.互斥量概述 2.创建和销毁互斥量 3.锁定(Locking)和解锁(Unlocking)互斥量 6.条件变量(Condition Variable) 1.条件变量概述 2.创建和销毁条件变量 3.等待(Waiting)和发送信号(Signaling) 7.没有覆盖的主题 8.Pthread 库API参考 9.参考资料 在多处理器共享内存的架构中(如:对称多处理系统SMP),线程可以用于实现程序的并行性。历史上硬件销售商实现了各种私有版本的多线程库,使得软件开发者不得不关心它的移植性。对于UNIX系统,IEEE POSIX 1003.1标准定义了一个C语言多线程编程接口。依附于该标准的实现被称为POSIX theads 或Pthreads。 该教程介绍了Pthreads的概念、动机和设计思想。内容包含了Pthreads API主要的三大类函数:线程管理(Thread Managment)、互斥量(Mutex Variables)和

条件变量(Condition Variables)。向刚开始学习Pthreads的程序员提供了演示例程。 适于:刚开始学习使用线程实现并行程序设计;对于C并行程序设计有基本了解。不熟悉并行程序设计的可以参考EC3500: Introduction To Parallel Computing。 什么是线程? ?技术上,线程可以定义为:可以被操作系统调度的独立的指令流。但是这是什么意思呢? ?对于软件开发者,在主程序中运行的“函数过程”可以很好的描述线程的概念。 ?进一步,想象下主程序(a.out)包含了许多函数,操作系统可以调度这些函数,使之同时或者(和)独立的执行。这就描述了“多线程”程序。 ?怎样完成的呢? ?在理解线程之前,应先对UNIX进程(process)有所了解。进程被操作系统创建,需要相当多的“额外开销”。进程包含了程序的资源和执行状态信息。如下: o进程ID,进程group ID,用户ID和group ID o环境 o工作目录 o程序指令 o寄存器 o栈 o堆 o文件描述符 o信号动作(Signal actions) o共享库 o进程间通信工具(如:消息队列,管道,信号量或共享内存)

相关主题
文本预览
相关文档 最新文档