一、关于多线程冲突问题。
3.6版开始增加了多线程支持库,提供对多线程的支持,并通过实现进入许可证机制以避免多线程冲突。
多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。 线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单。
多个线程的执行是并发的,也就是在逻辑上“同时”,而不管是否是物理上的“同时”。如果系统只有一个CPU,那么真正的“同时”是不可能的,但是由于CPU的速度非常快,用户感觉不到其中的区别,因此我们也不用关心它,只需要设想各个线程是同时执行即可。
多线程和传统的单线程在程序设计上最大的区别在于,由于各个线程的控制流彼此独立,使得各个线程之间的代码是乱序执行的,由此带来的线程调度,同步等问题,将在下面探讨。
由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。
多线程也有它不利的一面。任何时候某个应用程序使用的线程多于一个时,如果多个线程在同一时刻试图使用相同的数据或资源,可能出现麻烦。这种情况一旦出现,程序将变得非常复杂并且难以调试。
更糟的是多线程代码经常在最初开发时运行良好,在形成产品时却往往失败,原因在于有未被发现的多个线程与相同的数据或资源相互作用的情况。这使得多线程编程非常危险。
因此,在编程时需要考虑在多个线程访问同一资源时产生冲突的问题:当一个线程正在访问一个进程对象时,另一个线程要改变该对象,这时可能会产生错误的结果。所以,程序员编程时要解决这种冲突。
最简单的避免线程冲突的的方法是使线程之间永远不与相同的数据或资源交互。但这不一定可行,对任何多线程程序来说,避免或最小化共享数据或资源应作为一个目标。
二、下面介绍一下在Win32 基础上用API函数进行多线程编程的过程。
1、用Win32函数创建和中止线程
Win32函数库中提供了多线程控制的操作函数,包括创建线程、中止线程、建立互斥区等。首先,在应用程序的主线程或者其它活动线程的适当地方创建新的线程。创建线程的函数如下:
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId );
其中,参数lpThreadAttributes 指定了线程的安全属性,在Windows 95中被忽略;dwStackSize 指定了线程的堆栈深度;lpStartAddress 指定了线程的起始地址,一般情况为下面的原型函数:DWORD WINAPI ThreadFunc( LPVOID );lpParameter指定了线程执行时传送给线程的32位参数,即上面函数的参数;dwCreationFlags指定了线程创建的特性; lpThreadId 指向一个DWORD变量,可返回线程ID值。
如果创建成功则返回线程的句柄,否则返回NULL。
创建了新的线程后,则该线程就开始启动执行了。如果在dwCreationFlags中用了CREATE_SUSPENDED特性,那么线程并不马上执行,而是先挂起,等到调用ResumeThread后才开始启动线程,在这个过程中可以调用函数:
BOOL SetThreadPriority( HANDLE hThread, int nPriority);
来设置线程的优先权。
当线程的函数返回后,线程自动中止。如果在线程的执行过程中中止的话,则可调用函数:
VOID ExitThread( DWORD dwExitCode);
如果在线程的外面中止线程的话,则可调用下面的函数:
BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode );
但应注意:该函数可能会引起系统不稳定,而且线程所占用的资源也不释放。因此,一般情况下,建议不要使用该函数。
如果要中止的线程是进程内的最后一个线程,则在线程被中止后相应的进程也应中止。
2、用Win32函数控制线程对共享资源的访问
在线程体内,如果该线程完全独立,与其它的线程没有数据存取等资源操作上的冲突,则可按照通常单线程的方法进行编程。但是,在多线程处理时情况常常不是这样,线程之间经常要同时访问一些资源。例如,一个线程负责公式计算,另一个线程负责结果的显示,两个线程都要访问同一个结果变量。这时如果不进行冲突控制的话,则很可能显示的是不正确的结果。
对共享资源进行访问引起冲突是不可避免的,但我们可用以下办法来进行操作控制:
(1) 通过设置线程的互斥体对象,在可能冲突的地方进行同步控制。
首先,建立互斥体对象,得到句柄:
HANDLE CreateMutex( );
然后,在线程可能冲突区域的开始(即访问共享资源之前),调用WaitForSingleObject将句柄传给函数,请求占用互斥体对象:
dwWaitResult = WaitForSingleObject(hMutex, 5000L);
共享资源访问完后,释放对互斥体对象的占用:
ReleaseMutex(hMutex);
互斥体对象在同一时刻只能被一个线程占用。当互斥体对象被一个线程占用时,若有另一线程想占用它,则必须等到前一线程释放后才能成功。
(2) 设置信号:在操作共享资源前,打开信号;完成操作后,关闭信号。这类似于互斥体对象的处理。
首先,创建信号对象:
HANDLE CreateSemaphore( );
或者打开一个信号对象:
HANDLE OpenSemaphore( );
然后,在线程的访问共享资源之前调用WaitForSingleObject。
共享资源访问完后,释放对信号对象的占用:
ReleaseSemaphore();
信号对象允许同时对多个线程共享资源的访问,在创建对象时指定最大可同时访问的线程数。当一个线程申请访问成功后,信号对象中的计数器减一;调用ReleaseSemaphore函数后,信号对象中的计数器加一。其中,计数器值大于等于0,小于等于创建时指定的最大值。利用信号对象,我们不仅可以控制共享资源的访问,还可以在应用的初始化时候使用。假定一个应用在创建一个信号对象时,将其计数器的初始值设为0,这样就阻塞了其它线程,保护了资源。待初始化完成后,调用ReleaseSemaphore函数将其计数器增加至最大值,进行正常的存取访问
三、下面介绍易语言的多线程编程方法。
通过研究易语言附带的两个多线程例程,总结如下:
(一)、先看易语言对自己的多程机制的解释:
1、创建进入许可证:创建并返回一个进入许可证数值,此许可证值用作进入程序中的指定许可代码区,以避免多线程冲突。
2、删除进入许可证:删除由“创建进入许可证”命令所创建返回的进入许可证,以释放系统资源。
3、启动线程:创建并启动一条线程,可重复使用以创建多条线程。
4、进入许可区:根据已经创建的许可证进入指定许可代码区,在此线程未退出之前,其它线程如要通过同一个进入许可证进入该许可代码区则必须先等待此线程退出许可代码区,从而保证了指定许可代码区在任何时候都只能有一条线程进入并执行。
5、退出许可区:指示当前线程将退出许可代码区,并允许其它使用同一进入许可证的线程进入此许可代码区。
(二)、易语言的多线程编程过程大约如下:
1、先用“创建进入许可证”命令为一个线程进入一个指定的许可代码区建立一个许可证。
2、用“启动线程”命令创建并启动一条线程,以运行一个线程的子程序。
3、在一个线程子程序里用“进入许可区”使该线程占用一个许可代码区,并锁定该代码区不让其他线程进入,并锁定其他线程运行,以避免线程冲突。
4、使用“退出许可区”解锁该许可代码区,以便让其他线程进入。若想使多个线程同时运行,我们可以为每个线程建立一个进入许可证,进入许可区与退出许可区连着进行使多个线程同步运行。
5、当退出程序时,要删除进入许可证以释放系统资源。
(三)、下面的示例代码同时运行两个线程,一个是连续随机画圆,一个是连续随机画矩形:
窗口程序集:窗口程序集1
程序集变量:进入许可证1 数据类型:整数型
程序集变量:画圆判断 数据类型:逻辑型
程序集变量:画矩形判断 数据类型:逻辑型
程序集变量:进入许可证2 数据类型:整数型
══════════════════════════════
子程序:_按钮1_被单击
※ 备注:如果按钮1的标题为“停止画圆”时就开始画圆(画圆判断 = 假),否则如果按钮1的标题为“开始画圆”时就停止画圆(画圆判断 = 真)。
画圆判断 = 取反 (画圆判断)
判断 (画圆判断 = 真)
如果 (进入许可证1 ≠ 0)
如果真 (启动线程 (&画圆线程) = 真)
按钮1.标题 = “停止画圆”
如果真结束
否则
信息框 (“创建进入许可证 失败”, 0, )
如果结束
默认
按钮1.标题 = “开始画圆”
判断结束
══════════════════════════════
子程序:画圆线程
判断循环首 (画圆判断 = 真)
置随机数种子 ()
画板1.刷子颜色 = 取颜色值 (取随机数 (0, 255), 取随机数 (0, 255), 取随机数 (0, 255))
画板1.画笔颜色 = 取颜色值 (取随机数 (0, 255), 取随机数 (0, 255), 取随机数 (0, 255))
画板1.画椭圆 (取随机数 (1, 画板1.宽度), 取随机数 (1, 画板1.高度), 取随机数 (1, 画板1.宽度), 取随机数 (1, 画板1.高度))
进入许可区 (进入许可证1)
退出许可区 (进入许可证1)
延时 (500)
判断循环尾 ()
══════════════════════════════
子程序:__启动窗口_创建完毕
进入许可证1 = 创建进入许可证 ()
进入许可证2 = 创建进入许可证 ()
══════════════════════════════
子程序:__启动窗口_将被销毁
删除进入许可证 (进入许可证1)
删除进入许可证 (进入许可证2)
══════════════════════════════
子程序:_按钮2_被单击
画矩形判断 = 取反 (画矩形判断)
判断 (画矩形判断 = 真)
如果 (进入许可证2 ≠ 0)
如果真 (启动线程 (&画矩形线程) = 真)
按钮2.标题 = “停止画矩形”
如果真结束
否则
信息框 (“创建进入许可证 失败”, 0, )
如果结束
默认
按钮2.标题 = “开始画矩形”
判断结束
══════════════════════════════
子程序:画矩形线程
判断循环首 (画矩形判断 = 真)
置随机数种子 ()
画板2.刷子颜色 = 取颜色值 (取随机数 (0, 255), 取随机数 (0, 255), 取随机数 (0, 255))
画板2.画笔颜色 = 取颜色值 (取随机数 (0, 255), 取随机数 (0, 255), 取随机数 (0, 255))
画板2.画矩形 (取随机数 (1, 画板2.宽度), 取随机数 (1, 画板2.高度), 取随机数 (1, 画板2.宽度), 取随机数 (1, 画板2.高度))
进入许可区 (进入许可证2)
退出许可区 (进入许可证2)
延时 (500)
判断循环尾 ()
Comments