多线程的基本概念和Delphi线程对象Tthread介绍

作者:小菜 更新时间:2025-02-27 点击数:
简介:WIN 98/NT/2000/XP是个多任务操作系统,也就是:一个进程可以划分为多个线程,每个线程轮流占用CPU运行时间和资源,或者说,把CPU时间划成片,每个

【菜科解读】

WIN 98/NT/2000/XP是个多任务操作系统,也就是:一个进程可以划分为多个线程,每个线程轮流占用CPU运行时间和资源,或者说,把CPU时间划成片,每个片分给不同的线程,这样,每个线程轮流的“挂起”和“唤醒”,由于时间片很小,给人的感觉是同时运行的。

多线程带来如下好处:1)避免瓶颈;2)并行操作;3)提高效率;

多线程的两个概念:1)进程:也称任务,程序载入内存,并分配资源,称为“一个进程”。

注意:进程本身并不一定要正在执行。

进程由以下几部分组成:a>一个私有的地址空间,它是进程可以使用的一组虚拟内存地址空间;b>程序的相关代码、数据源;c>系统资源,比如操作系统同步对象等;d>至少包含一个线程(主线程);2)线程:是进程的执行单位(线程本身并不包括程序代码,真正拥有代码的是进程),是操作系统分配CPU时间的基本实体,每个进程至少包括一个线程,称为主线程。

一个进程如果有多个线程,就可以共享同一进程的资源,并可以并发执行。

通俗点说就是进程中一段并发运行的代码(一个函数或过程)。

线程主要由如下两部分组成:a>数据结构;b>CPU寄存器和堆栈;

线程函数运行,启动函数就返回了,主线程继续向下执行,而线程函数在一个独立的线程中执行,它要执行多久,什么时候返回,主线程是不管也不知道的。

一、Delphi线程对象--- Tthread

虽然Windows提供了较多的多线程设计的API函数,但是直接使用API函数极其不方便,而且使用不当还容易出错。

为解决这个问题,Borland公司率先推出了一种Tthread对象,来解决多线程设计上的困难,简化了多线程问题的处理。

一、Tthread对象的主要方法

构造线程:

constructor Create(CreateSuspended:boolean)

CreateSuspended=true构造但不唤醒;false构造的同时即唤醒。

挂起线程:suspend:(把线程挂起的次数加一)

唤醒线程:resume:(注意:注意这个属性是把线程挂起的次数减一,当次数为0时,即唤醒。

也就是说,线程挂起多少次,唤醒也需要多少次。

同时挂起的时候将保持线程的地址指针不变,所以线程挂起后再唤醒,将从挂起的地方开始运行)析构(清除线程所占用的内存):destroy

终止线程Terminate

使用这个类也很简单,基本用法是:先从TThread派生一个自己的线程类(因为TThread是一个抽象类,不能生成实例),然后是覆盖(Override)抽象方法:Execute(这就是线程函数,也就是在线程中执行的代码部分),如果需要用到可视VCL对象,还需要通过Synchronize过程进行。

线程的终止和退出:

1)自动退出:

一个线程从Execute()过程中退出,即意味着线程的终止,此时将调用Windows的ExitThread()函数来清除线程所占用的堆栈。

如果线程对象的FreeOnTerminate属性设为True,则线程对象将自动删除,并释放线程所占用的资源。

这是消除线程对象最简单的办法。

2)受控退出:

利用线程对象的Terminate属性,可以由进程或者由其他线程控制线程的退出。

只需要简单的调用该线程的Terminate方法,并设置线程对象的Terminate属性为True。

一般来说,在线程中,应该不断监视Terminate的值,一旦发现为True,则退出,一般来说,例如在Execute()过程中可以这样写:While not Terminate dobegin........end;

3)退出的API函数:

关于线程退出的API函数声明如下:

Function TerminateThread(hThread:Thandle;dwExitCode:DWORD);

不过,这个函数会使代码立刻终止,而不管程序中有没有

try....finally

机制,可能会导致错误,不到万不得已,最好不要使用。

4)利用挂起线程的方法(suspend)

利用挂起线程的suspend方法,后面跟个Free,也可以释放线程,例如:thread1.suspend; //挂起

thread2.free; //释放

二、多线程的同步机制

同步机制,研究多线程的同步机制的必要性在于,多线程同步工作时,如果同时调用相同的资源,就可能会出现问题,如对全局变量、数据库操作发生冲突,甚至产生死锁和竞争问题。

举个发生冲突的实例看一下:

一般来说,对内存数据加一的操作分解以后有三个步骤:1、从内存中读出数据2、数据加一3、存入内存现在假设在一个两个线程的应用中用Inc进行加一操作可能出现的一种情况:1、线程A从内存中读出数据(假设为3)2、线程B从内存中读出数据(也是3)3、线程A对数据加一(现在是4)4、线程B对数据加一(现在也是4)5、线程A将数据存入内存(现在内存中的数据是4)6、线程B也将数据存入内存(现在内存中的数据还是4,但两个线程都对它加了一,应该是5才对,所以这里出现了错误的结果)

1.临界区(Critical Sections)

临界区(CriticalSection)是一项共享数据访问保护的技术。

对它只有两个操作:Enter和Leave,这两个操作也是原子操作。

它的保护原理是这样的:当一个线程A调用某一个Enter后,开始访问某个数据D,如果此时另一个线程B也要访问数据D,则它会在调用这个Enter时,发现已经有线程进入临界区,然后线程B就会被挂起,等待线程A调用Leave。

当线程A完成操作,调用Leave离开后,线程B就会被唤醒,并设置临界区标志,开始操作数据,这样就防止了访问冲突

varCS:TRTLCriticalSection;//被声明在程序最上方,作为线程都可以使用的全局变量。

initializeCriticalSection(cs); //初始化

Procedure InterlockedIncrement( var aValue : Integer );Begin EnterCriticalSection(CS);//独占 Inc( aValue ); LeaveCriticalSection(CS);//解除独占End;

现在再来看前面那个例子:1.线程A进入临界区(假设数据为3)2.线程B进入临界区,因为A已经在临界区中,所以B被挂起3.线程A对数据加一(现在是4)4.线程A离开临界区,唤醒线程B(现在内存中的数据是4)5.线程B被唤醒,对数据加一(现在就是5了)6.线程B离开临界区,现在的数据就是正确的了。

临界区就是这样保护共享数据的访问

请注意,临界区只能在一个进程内使用,可以在多处设置调用enter。

不要长时间锁住一份资源,如果你一直让资源被锁定,你就会阻止其它线程的执行,并把整个程序带到一个完全停止的状态,所以千万不要在一个ciritical section中调用sleep()或任何Wait…()函数。

ciritical section的一个缺点是,它不是核心对象,如果进入ciritical section的那个线程结束了或者当掉了,而没有调用LeaveCriticalSection的话,系统没有办法将该ciritical section清除,如果你需要这样的机能,你应该使用mutex。

VOID InitializeCriticalSection(

LPCRITICAL_SECTION lpCriticalSection//一个指针,指向欲被初始化的CRITICAL_SECTION变量

);

函数功能:初始化一个临界对象,当你用毕临界对象时,必须调用DeleteCriticalSection()清除它。

VOID DeleteCriticalSection (LPCRITICAL_SECTION lpCriticalSection//临界对象指针);

函数功能:申请删除临界对象

VOID EnterCriticalSection(

LPCRITICAL_SECTION lpCriticalSection//临界对象指针

);

函数功能:申请进入临界对象

VOID LeaveCriticalSection(

LPCRITICAL_SECTION lpCriticalSection//临界对象指针

);

函数功能

申请进入临界对象

2.互斥器(Mutexes)

一个时间内只能够有一个线程拥有mutex,就好像同一个时间只能够有一个线程进入同一临界区。

Mutex和critical section还是有差别的:

1.锁住一个未被使用的Mutexes,比锁住一个未被使用的critical section,需要花费几乎100倍的时间

2. Mutexes可以跨进程使用,critical section则只能够在同一个进程中使用

3.等待一个Mutexes时,你可以指定结束等待的时间长度,但对于critical section则不行。

两种对象的相关函数比较:

CRITICAL_SECTIONMutex核心对象InitializeCriticalSection()CreateMutex()OpenMutex()EnterCriticalSection()WaitForSingleObject()WaitForMultipleObject()MsgWaitForMutipleObjects()LeaveCriticalSection()ReleaseMutex()DeleteCriticalSection()CloseHandle()

Mutex的使用机制:

1.有一个mutex,此时没有任何线程拥有它,此时它处于非激发状态。

2.某个线程调用WaitforSingleObject()或任何其它的wait…函数,并指定该mutex的handle为参数

3.win32于是将该mutex的拥有权给予这个线程,然后将此mutex设为激发状态,于是wait..函数返回

4.wait..函数返回后,win32立刻又将mutex设为非激发状态,是任何处于等待状态下的其它线程没有办法获得其拥有权

5.获得该mutex的线程调用Release,将mutex释放掉。

于是循环到第一步。

如果线程拥有一个mutex,而在结束前没有调用releaseMutex,mutex不会被摧毁,该mutex会被win32视为“未被拥有”以及“未被激发”,下一个等待中的线程会被以WAIT_ABANDONED_0通知。

如果是WaitForMultipleObjects()等待辞mutex,函数返回值介于WAIT_ABANDONED_0和WAIT_ABANDONED_0+n之间,n是指handle数组的元素个数。

HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, BOOL bInitialOwner, LPCTSTR lpName);

参数

lpMutexAttributes:安全属性。

Null表示使用默认的属性。

bInitialOwner:如果你希望调用这个函数的线程拥有mutex,就将此值设为true

lpName:互斥对象的名称

返回值

如果成功,则返回一个handle,否则返回null。

函数说明:

如果指定的mutex名称已经存在,win32会给你一个mutex handle,而不会为你产生一个新的mutex。

调用GetLastError会传回ERROR_ALREADY_EXISTS。

当你不需要mutex时,你可以调用closehandle()将它关闭。

BOOL ReleaseMutex(

HANDLE hMutex//欲释放mutex的handle

);

返回值

如果成功,则返回true,否则返回false。

3.信号量(Semaphores)

Mutex是semaphore的一种退化,如果你产生一个semaphore并令最大值为1,那它就是个mutex。

因此,mutex又常被称为binary semaphore。

在许多系统中,semaphore常被使用,因为mutex可能并不存在,在win32中semaphore被使用的情况就少得多,因为mutex存在的原因。

一旦semaphore的现值降到0,就表示资源已经耗尽。

此时任何线程如果调用Wait…函数,必然要等待,直到某个锁定被解除。

HANDLE CreateSemaphore(

LPSECURITY_ATTRIBUTESlpSemaphoreAttributes,

LONGlInitialCount,

LONGlMaximumCount,

LPCTSTRlpName

)

参数:

lpSemaphoreAttributes:安全属性,null表示使用默认属性。

lInitialCount:初始值,必须>=0,并且三、事件(Events)

事件(Event)是一种核心对象,它的唯一目的就是成为激发状态或未激发状态。

这两种状态完全在你的掌握之下,不会因为Wait…函数的调用而变化。

HANDLECreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL bManualReset,BOOL bInitialState,

LPCTSTR lpName

);

参数:

lpEventAttributes:安全属性,null表示使用默认属性。

bManualReset:

此值为false,表示event变成激发状态后,自动重置为非激发状态;

此值为true,表示不会自动重置,必须靠程序(调用ResetEvent)操作才能将激发状态的event重置为非激发状态。

bInitialState:初始状态,true一开始处于激发状态,false一开始处于非激发状态

lpName:Event对象名

返回值:

如果成功就返回一个handle,否则返回null。

如果event名称已经存在,函数还是会成功,GetLastError会返回ERROR_ALREADY_EXISTS

BOOL SetEvent(HANDLEhEvent);

//把event对象设为激发状态

BOOL ResetEvent(HANDLEhEvent);

//把event对象设为非激发状态

BOOL PulseEvent(HANDLEhEvent );

//如果event的bManualReset为true:把event对象设为激发状态,唤醒所有等待中的线程,然后event恢复为非激发状态

//如果event的bManualReset为false:把event对象设为激发状态,唤醒一个等待中的线程,然后event恢复为非激发状态

5.使用Synchronize方法

这个方法用于访问VCL主线程所管理的资源,其方法的应用是:第一步:把访问主窗口(或主窗口控件资源)的代码放到线程的一个方法中;第二步:是在线程对象的Execute方法中,通过Synchronize方法使用该方法。

实例:procedure Theater.Execute;beginSynchronize(update);end;procedure Theater.update;begin.........end;这里通过Synchronize使线程方法update同步。

6、使用VCL类的Look方法

在Delphi的IDE提供的构件中,有一些对象内部提供了线程的同步机制,工作线程可以直接使用这些控件,比如:Tfont,Tpen,TBitmap,TMetafile,Ticon等。

另外,一个很重要的控件对象叫TCanvas,提供了一个Lock方法用于线程的同步,当一个线程使用此控件对象的时候,首先调用这个对象的Lock方法,然后对这个控件进行操作,完毕后再调用Unlock方法,释放对控间的控制权。

例如:CanversObject.look;try画图finallyCanversObject.unlock;end;{使用这个保护机制,保证不论有没有异常,unlock都会被执行否则很可能会发生死锁。

在多线程设计的时候,应该很注意发生死锁的问题}

四、线程的优先级:

在多线程的情况下,一般要根据线程执行任务的重要性,给线程适当的优先级,一般如果量的线程同时申请CPU时间,优先级高的线程优先。

优先权类别(Priority Class)

Win32提供四种优先权类别,每一个类别对应一个基本的优先权层次。

表格5-1优先权类别

优先权类别基础优先权值HIGH_PRIORITY_CLASS13IDLE_PRIORITY_CLASS4NORMAL_PRIORITY_CLASS7or8REALTIME_PRIORITY_CLASS24

大部分程序使用NORMAL_PRIORITY_CLASS。

优先权类别是进程的属性之一,利用SetPriorityClass和GetPriorityClass函数可以调整和获取该值。

优先权层次(priority Level)

线程的优先权层次使你能够调整同一个进程内的各线程的相对重要性。

一共七种优先权层次:

表格5-2

优先权层次调整值THREAD_PRIORITY_LOWEST-2THREAD_PRIORITY_BELOW_NORMAL-1THREAD_PRIORITY_NORMAL0THREAD_PRIORITY_ABOVE_NORMAL+1THREAD_PEIOEITY_HIGHEST+2THREAD_PRIORITY_IDLE1THREAD_PRIORITY_TIME_CRITICAL15

利用SetThreadPriority和GetThreadPriority函数可以调整和获取该值

在Windows下,给线程的优先级分为30级,而Delphi中Tthread对象相对简单的把优先级分为七级。

也就是在Tthread中声明了一个枚举类型TTthreadPriority:typeTTthreadPriority(tpidle,tpLowest,tpLower,tpNormal,tpHight,tpHighest,tpTimecrital)分别对应的是最低(系统空闲时有效,-15),较低(-2),低(-1),正常(普通0),高(1),较高(2),最高(15)。

设置优先级可使用thread对象的priority属性:threadObject.priority:=Tthreadpriority(级别);

BOOL SetThreadPriority(

HANDLEhThread,//欲调整优先权的那个线程的句柄

int nPriority//表格5-2所显示的值

);

IntGetThreadPriority(

HANDLE hThread//线程的句柄

);

返回值是线程的优先级。

多,线程,的,基本概念,和,Delphi,对象,Tthrea

晋文公流亡期间没有礼遇他的国家后来怎么样了?

众所周知,春秋时期在外流亡19年,最终于61岁时回到晋国即位为晋国国君,通过仅仅8年的励精图治,使得晋国一跃成为中原最强国家,晋文公也成为当之无愧的春秋霸主之一。

晋文公流亡期间,先后经过了狄国、卫国、、曹国、宋国、郑国、、秦国这八个国家。

对于这位皇族远亲、晋国公子,大多数国家对重耳一行还是很客气的,最次都是好吃好喝地招待,好一点的还给他送礼品、送老婆什么的。

但是也有不待见这位一看就不是潜力股的国家,比如卫国、曹国和郑国。

俗话说得好:“凡事留一线,日后好相见”。

卫国、曹国、郑国这三个国家大概是没有想到大龄青年重耳也会有翻身的一天,不然肯定不是这样做吧。

那么重耳称霸以后,是如何对待这三个让他受辱的国家的呢? 卫国(领土被瓜分,国君丢了君位): 重耳流亡路过卫国时,卫国国君未加礼遇,连饭都不请重耳一行吃一顿。

重耳一行也够凄惨的,沦落到了向乡野农夫讨饭吃的地步。

等到重耳即位为晋国国君后,励精图治,五年后(前632年),重耳尽起三军,目标直指卫、曹两国,其主要目的是解楚国围宋之困,但我相信还有一个重要原因是终于可以顺便出一口心中的恶气了。

晋国的第一目标是曹国,重耳向卫国借道,卫文公哪里敢借啊,于是晋军只能绕道攻击曹国,晋军一路,攻下曹国五鹿,并于齐国结盟。

卫文公看见晋军势大,抱着侥幸心理向晋文公求和,晋国不允,卫文公又想破罐子破摔全面倒向楚国,不料国内臣民一致不允,卫国只好想出一个折中的办法,卫文公自己将自己流放,不当卫国国君,这才消了晋文公重耳等人心理的一口恶气。

曹国(国君被俘,领土被瓜分): 重耳到了曹国,曹国国君好歹请他们吃了一顿饭,但这国君太无聊了,听说重耳是骈胁,居然想出偷看他洗澡的主义来,并且不听曹国大夫僖负羁的劝告付诸实践了。

僖负羁实在是觉得对不起重耳,于是私下送了很多食物和一块玉璧给重耳,重耳收了食物,把玉璧退回给了僖负羁。

到晋文公回国五年后,晋军攻击曹、卫两国解宋被楚围国之困,收拾曹国时,中军将郤臻病逝,晋国换了中军将后先收拾了卫国,接着重点来收拾曹国。

曹国国君负隅顽抗,被晋军攻破城池,城破之后,晋文公以胜利者的姿态大骂了曹共公一顿,然后下令不得骚扰对他有恩的僖负羁一家,不料魏准、颠颉率军强攻僖负羁家,并纵火将僖负羁家烧为一片白地,晋文公听后很生气,杀死了颠颉以正军纪,让魏准戴罪立功。

郑国(结局比较好,仅大夫自杀): 重耳一行路过郑国的时候,郑国国君也没有礼遇他们,郑国大夫叔瞻劝说郑文公:“重耳与郑国都是姬人,有远亲关系,而且又是一国公子,希望郑文公对重耳一行加以礼遇”,郑文公说到:“流亡在外的公子这么多,基本都是姬姓,我要一个个招待的话,哪里招待得过来?” 叔瞻又劝说郑文公“那你杀了他吧,以绝后患”郑文公也没有采纳。

重耳回国后,经过励精图治,富国强军,先后教训了卫国和曹国出了一口恶气,而且通过一举称霸诸侯。

但每每想起流亡时郑国的态度都觉得不爽,于是召集秦国一起,打算围殴郑国,两路大军围郑,晋文公发话:“让叔瞻来见我”郑国大夫叔瞻自杀,郑文公没办法,只好死马当活马医,派了一个说客连夜入说服了,秦国退兵,晋国没办法,也只好退兵。

这是史上有名的“烛之武退秦师”。

好吧,郑国算是运气比较好的国家了,晋国撤兵两年后,晋文公去世,再没有人以当初的无礼为由讨伐他们,但是基于春秋的形势,沦落为三流诸侯的郑国仍将夹在南北两个大国之间艰难求生,直至灭国。

随机文章世界上最大战斗机图128,长30米是狂风战斗机3倍揭秘尼古拉特斯拉的黑科技,人造地震武器可以劈开地球神奇的镜子效应,相互喜欢的人就会产生镜子效应(死心塌地)蛇的白化种白蛇罕见异常,日本成功繁育出白蛇(最受欢迎的宠物蛇)老子参透了宇宙真相,(道生一,一生二,二生三,三生万物)诠释宇宙奥妙

解析“下宫之难”的罪魁祸首是赵庄姬还是晋景公?

《左传》和《》中的时间线如此混乱,也难怪下宫之难会成为历史上的一大谜题了。

很多历史学家认为,下宫之难根本就不存在,没有被灭过族,因为一般认为,公元前588年的时候,、赵同和赵旃还做过十二卿,就像我前面写得那样。

然而,事实到底如何呢? 历史讲究的是准确、公正,而我特别喜欢主观臆断,可能有人会喷我,但是我觉得的史家之绝唱都不严谨,而且大部分历史记载实际上已经在歪曲事实,加入了很多个人的感情,又何必强求后人严谨呢?我认为,学历史最重要的是我们从中学到了什么,而不是知道了什么。

如果仅仅是知道了什么,把历史故事讲得,也没有什么意义。

好了,不废话了。

简单说说我对下宫之难的理解吧。

我认为下宫之难是确实存在的,至于其发生的时间,《赵世家》是对的,而且,下宫之难的罪魁祸首既不是赵庄姬,也不是屠岸贾,而是——晋景公。

我们知道,晋景公的爸爸——晋成公在世的时候,受够了赵盾的窝囊气。

这一切,并不算年幼的晋景公肯定是看在眼里的。

晋成公忍耐赵氏的七年,也是晋景公对赵氏憎恨到极点的七年。

晋景公这么强势的国君,对曾经的赵家来说,的确是一场噩梦,而的失利,则正好为晋景公提供了最好的灭口机会。

我甚至认为,邲之战的失败,是晋景公一手导演的,因为他有意让老实听话的荀林父坐大,进而压制,并族灭赵氏、先氏。

因为当时赵氏、先氏、郤氏是一丘之貉,他们互相扶持,交替为执政。

很明显,赵盾之后为郤缺,郤缺之后为先毂,先毂之后为赵朔、是他们三大家族商量好了的,甚至这种交替并不需要国君的授意。

然而,晋景公非常果断地扼杀这三大于襁褓之中。

郤缺死了之后,荀林父接替了中军元帅的位置,荀林父致仕之后,士会又继为执政,估计当时先毂和赵朔都懵了。

然后发生的事情都在晋景公的预料之中,先毂首先不服,在邲之战中不服从号令,结果首当其冲地成为了,被晋景公处死。

《左传》记载:晋景公乃命杀先毂,尽灭其家族。

晋人曰:“先毂咎由自取,以致身受刑戮”。

——其实先毂小哥只是个性情中人而已,他非常悲催地被晋景公牵着鼻子走,最后也没人可怜,身败名裂,家族覆灭,还落得个“咎由自取”的评语,真心悲惨。

然后,晋景公又以迅雷不及掩耳之势发动“下宫之难”,族灭赵氏家族,郤氏很快被孤立,所谓“”,也不敢怎么样了。

总之,当时的情况就是这么个情况。

世人都认为作案凶手是屠岸贾,其实真正的凶手是晋景公。

这让我想起来网上有人说杀父子的幕后主谋实际上是宋高宗,只不过是替陛下承担了骂名而已。

想想也是,如果不是皇帝授意,秦桧怎么敢动名震天下的大将军呢? 从另一个角度来说,赵氏和先氏的族灭,说明了一个道理,那就是做人不能太强硬,要以和为贵,赵氏和先氏如果不是因为太嚣张了,又怎么会受到君主的猜忌呢?他老人家说的“至刚则折,情深不寿”也是可以用在这里的。

不过这种生死存亡也跟个人的水平有关,有句话怎么说来,不是,就是西风压倒东风。

晋景公的水平太高了,尤其是掣肘的水平。

在当时赵氏、先氏、郤氏如此强力的局势下,他居然能够重用、士氏,扳回一局,这种能力是相当可怕的。

而且我们可以注意到,他在景公三年才开始这样做,而不是刚即位就进行“大清洗”,说明他的忍耐力也是很强大的,晋景公非常像的大帝。

《逸周书·谥法解》中云:“由义而济曰景;耆意大虑曰景;布义行刚曰景”,晋景公当得起这样的美谥。

写到这里,我终于也能体会到皇帝为什么自称为“孤家寡人”了,因为他们的身世真的很“悲惨”! 随机文章夏朝之前是不是可以随便更改”姓”名?历史上的杨家将到底有多强月球上发现上亿年飞船,预示十多亿年前外星文明造访地球东风31洲际弹道导弹造价,4000万美元一枚(与民兵3相当)撒哈拉之眼有人去过吗,传闻无人能活着到达中心(形成原因未知)

加入收藏
               

多线程的基本概念和Delphi线程对象Tthread介绍

点击下载文档

格式为doc格式

  • 账号登录
社交账号登录