Delphi中有一个线程类TThread是用来实现多线程编程的,这个绝大多数Delphi书藉都有说到,但基本上都是对

TThread类的几个成员作一简单介绍,再说明一下Execute的实现和Synchronize的用法就完了。然而这并不是多线程编
程的全部,我写此文的目的在于对此作一个补充。

线程本质上是进程中一段并发运行的代码。一个进程至少有一个线程,即所谓的主线程。同时还可以有多个子线程。
当一个进程中用到超过一个线程时,就是所谓的“多线程”。
那么这个所谓的“一段代码”是如何定义的呢?其实就是一个函数或过程(对Delphi而言)。
如果用Windows API来创建线程的话,是通过一个叫做CreateThread的API函数来实现的,它的定义为:

HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId 
);

其各参数如它们的名称所说,分别是:线程属性(用于在NT下进行线程的安全属性设置,在9X下无效),堆栈大小,
起始地址,参数,创建标志(用于设置线程创建时的状态),线程ID,最后返回线程Handle。其中的起始地址就是线
程函数的入口,直至线程函数结束,线程也就结束了。

因为CreateThread参数很多,而且是Windows的API,所以在C Runtime Library里提供了一个通用的线程函数(理论上
可以在任何支持线程的OS中使用):

unsigned long _beginthread(void (_USERENTRY *__start)(void *), unsigned __stksize, void *__arg);

Delphi也提供了一个相同功能的类似函数:

function BeginThread(SecurityAttributes: Pointer; StackSize: LongWord; ThreadFunc: TThreadFunc; Parameter: Pointer; CreationFlags: LongWord; var ThreadId: LongWord
): Integer;

这三个函数的功能是基本相同的,它们都是将线程函数中的代码放到一个独立的线程中执行。线程函数与一般函数的
最大不同在于,线程函数一启动,这三个线程启动函数就返回了,主线程继续向下执行,而线程函数在一个独立的线
程中执行,它要执行多久,什么时候返回,主线程是不管也不知道的。
正常情况下,线程函数返回后,线程就终止了。但也有其它方式:

Windows API:
VOID ExitThread( DWORD dwExitCode );C Runtime Library:
void _endthread(void);Delphi Runtime Library:
procedure EndThread(ExitCode: Integer);

为了记录一些必要的线程数据(状态/属性等),OS会为线程创建一个内部Object,如在Windows中那个Handle便是这
个内部Object的Handle,所以在线程结束的时候还应该释放这个Object。

虽然说用API或RTL(Runtime Library)已经可以很方便地进行多线程编程了,但是还是需要进行较多的细节处理,为此
Delphi在Classes单元中对线程作了一个较好的封装,这就是VCL的线程类:TThread。

使用这个类也很简单,大多数的Delphi书籍都有说,基本用法是:先从TThread派生一个自己的线程类(因为TThread
是一个抽象类,不能生成实例),然后是Override抽象方法:Execute(这就是线程函数,也就是在线程中执行的代码
部分),如果需要用到可视VCL对象,还需要通过Synchronize过程进行。关于之方面的具体细节,这里不再赘述,请
参考相关书籍。

本文接下来要讨论的是TThread类是如何对线程进行封装的,也就是深入研究一下TThread类的实现。因为只是真正地
了解了它,才更好地使用它。
下面是DELPHI7中TThread类的声明(本文只讨论在Windows平台下的实现,所以去掉了所有有关Linux平台部分的代码
):

TThread = class
privateFHandle: THandle;FThreadID: THandle;FCreateSuspended: Boolean;FTerminated: Boolean;FSuspended: Boolean;FFreeOnTerminate: Boolean;FFinished: Boolean;FReturnValue: Integer;FOnTerminate: TNotifyEvent;FSynchronize: TSynchronizeRecord;FFatalException: TObject;procedure CallOnTerminate;class procedure Synchronize(ASyncRec: PSynchronizeRecord); overload;function GetPriority: TThreadPriority;procedure SetPriority(Value: TThreadPriority);procedure SetSuspended(Value: Boolean);
protectedprocedure CheckThreadError(ErrCode: Integer); overload;procedure CheckThreadError(Success: Boolean); overload;procedure DoTerminate; virtual;procedure Execute; virtual; abstract;procedure Synchronize(Method: TThreadMethod); overload;property ReturnValue: Integer read FReturnValue write FReturnValue;property Terminated: Boolean read FTerminated;
publicconstructor Create(CreateSuspended: Boolean);destructor Destroy; override;procedure AfterConstruction; override;procedure Resume;procedure Suspend;procedure Terminate;function WaitFor: LongWord;class procedure Synchronize(AThread: TThread; AMethod: TThreadMethod); overload;class procedure StaticSynchronize(AThread: TThread; AMethod: TThreadMethod);property FatalException: TObject read FFatalException;property FreeOnTerminate: Boolean read FFreeOnTerminate write FFreeOnTerminate;property Handle: THandle read FHandle;property Priority: TThreadPriority read GetPriority write SetPriority;property Suspended: Boolean read FSuspended write SetSuspended;property ThreadID: THandle read FThreadID;property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate;
end;

TThread类在Delphi的RTL里算是比较简单的类,类成员也不多,类属性都很简单明白,本文将只对几个比较重要的类
成员方法和唯一的事件:OnTerminate作详细分析。
首先就是构造函数:

constructor TThread.Create(CreateSuspended: Boolean);
begininherited Create;AddThread;FSuspended := CreateSuspended;FCreateSuspended := CreateSuspended;FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);if FHandle = 0 thenraise EThread.CreateResFmt(@SThreadCreateError, [SysErrorMessage(GetLastError)]);
end;

虽然这个构造函数没有多少代码,但却可以算是最重要的一个成员,因为线程就是在这里被创建的。
在通过Inherited调用TObject.Create后,第一句就是调用一个过程:AddThread,其源码如下:

procedure AddThread;
beginInterlockedIncrement(ThreadCount);
end;

同样有一个对应的RemoveThread:

procedure RemoveThread;
beginInterlockedDecrement(ThreadCount);
end;

它们的功能很简单,就是通过增减一个全局变量来统计进程中的线程数。只是这里用于增减变量的并不是常用的
Inc/Dec过程,而是用了InterlockedIncrement/InterlockedDecrement这一对过程,它们实现的功能完全一样,都是
对变量加一或减一。但它们有一个最大的区别,那就是InterlockedIncrement/InterlockedDecrement是线程安全的。
即它们在多线程下能保证执行结果正确,而Inc/Dec不能。或者按操作系统理论中的术语来说,这是一对“原语”操作。

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

而用InterlockIncrement过程则没有这个问题,因为所谓“原语”是一种不可中断的操作,即操作系统能保证在一个
“原语”执行完毕前不会进行线程切换。所以在上面那个例子中,只有当线程A执行完将数据存入内存后,线程B才可
以开始从中取数并进行加一操作,这样就保证了即使是在多线程情况下,结果也一定会是正确的。

前面那个例子也说明一种“线程访问冲突”的情况,这也就是为什么线程之间需要“同步”(Synchronize),关于这
个,在后面说到同步时还会再详细讨论。

说到同步,有一个题外话:加拿大滑铁卢大学的教授李明曾就Synchronize一词在“线程同步”中被译作“同步”提出
过异议,个人认为他说的其实很有道理。在中文中“同步”的意思是“同时发生”,而“线程同步”目的就是避免这
种“同时发生”的事情。而在英文中,Synchronize的意思有两个:一个是传统意义上的同步(To occur at the same 
time),另一个是“协调一致”(To operate in unison)。在“线程同步”中的Synchronize一词应该是指后面一种
意思,即“保证多个线程在访问同一数据时,保持协调一致,避免出错”。不过像这样译得不准的词在IT业还有很多
,既然已经是约定俗成了,本文也将继续沿用,只是在这里说明一下,因为软件开发是一项细致的工作,该弄清楚的
,绝不能含糊。

扯远了,回到TThread的构造函数上,接下来最重要就是这句了:
FHandle := BeginThread(nil, 0, @ThreadProc, Pointer(Self), CREATE_SUSPENDED, FThreadID);
这里就用到了前面说到的Delphi RTL函数BeginThread,它有很多参数,关键的是第三、四两个参数。第三个参数就是
前面说到的线程函数,即在线程中执行的代码部分。第四个参数则是传递给线程函数的参数,在这里就是创建的线程
对象(即Self)。其它的参数中,第五个是用于设置线程在创建后即挂起,不立即执行(启动线程的工作是在
AfterConstruction中根据CreateSuspended标志来决定的),第六个是返回线程ID。

现在来看TThread的核心:线程函数ThreadProc。有意思的是这个线程类的核心却不是线程的成员,而是一个全局函数
(因为BeginThread过程的参数约定只能用全局函数)。下面是它的代码:

function ThreadProc(Thread: TThread): Integer;
varFreeThread: Boolean;
begintryif not Thread.Terminated thentryThread.Execute;exceptThread.FFatalException := AcquireExceptionObject;end;finallyFreeThread := Thread.FFreeOnTerminate;Result := Thread.FReturnValue;Thread.DoTerminate;Thread.FFinished := True;SignalSyncEvent;if FreeThread then Thread.Free;EndThread(Result);end;
end;

虽然也没有多少代码,但却是整个TThread中最重要的部分,因为这段代码是真正在线程中执行的代码。下面对代码作
逐行说明:
首先判断线程类的Terminated标志,如果未被标志为终止,则调用线程类的Execute方法执行线程代码,因为TThread
是抽象类,Execute方法是抽象方法,所以本质上是执行派生类中的Execute代码。

所以说,Execute就是线程类中的线程函数,所有在Execute中的代码都需要当作线程代码来考虑,如防止访问冲突等。
如果Execute发生异常,则通过AcquireExceptionObject取得异常对象,并存入线程类的FFatalException成员中。
最后是线程结束前做的一些收尾工作。局部变量FreeThread记录了线程类的FreeOnTerminated属性的设置,然后将线
程返回值设置为线程类的返回值属性的值。然后执行线程类的DoTerminate方法。

DoTerminate方法的代码如下:

procedure TThread.DoTerminate;
beginif Assigned(FOnTerminate) then Synchronize(CallOnTerminate);
end;

很简单,就是通过Synchronize来调用CallOnTerminate方法,而CallOnTerminate方法的代码如下,就是简单地调用
OnTerminate事件:

procedure TThread.CallOnTerminate;
beginif Assigned(FOnTerminate) then FOnTerminate(Self);
end;

因为OnTerminate事件是在Synchronize中执行的,所以本质上它并不是线程代码,而是主线程代码(具体见后面对
Synchronize的分析)。

执行完OnTerminate后,将线程类的FFinished标志设置为True。接下来执行SignalSyncEvent过程,其代码如下:

procedure SignalSyncEvent;
beginSetEvent(SyncEvent);
end;

也很简单,就是设置一下一个全局Event:SyncEvent,关于Event的使用,本文将在后文详述,而SyncEvent的用途将
在WaitFor过程中说明。

然后根据FreeThread中保存的FreeOnTerminate设置决定是否释放线程类,在线程类释放时,还有一些些操作,详见接
下来的析构函数实现。

最后调用EndThread结束线程,返回线程返回值。至此,线程完全结束。
说完构造函数,再来看析构函数:

destructor TThread.Destroy;
beginif (FThreadID <> 0) and not FFinished then  beginTerminate;if FCreateSuspended thenResume;WaitFor;end;if FHandle <> 0 then CloseHandle(FHandle);inherited Destroy;FFatalException.Free;RemoveThread;
end;

在线程对象被释放前,首先要检查线程是否还在执行中,如果线程还在执行中(线程ID不为0,并且线程结束标志未设
置),则调用Terminate过程结束线程。Terminate过程只是简单地设置线程类的Terminated标志,如下面的代码:

procedure TThread.Terminate;
beginFTerminated := True;
end;

所以线程仍然必须继续执行到正常结束后才行,而不是立即终止线程,这一点要注意。

在这里说一点题外话:很多人都问过我,如何才能“立即”终止线程(当然是指用TThread创建的线程)。结果当然是
不行!终止线程的唯一办法就是让Execute方法执行完毕,所以一般来说,要让你的线程能够尽快终止,必须在
Execute方法中在较短的时间内不断地检查Terminated标志,以便能及时地退出。这是设计线程代码的一个很重要的原
则!

当然如果你一定要能“立即”退出线程,那么TThread类不是一个好的选择,因为如果用API强制终止线程的话,最终
会导致TThread线程对象不能被正确释放,在对象析构时出现Access Violation。这种情况你只能用API或RTL函数来创
建线程。

如果线程处于启动挂起状态,则将线程转入运行状态,然后调用WaitFor进行等待,其功能就是等待到线程结束后才继
续向下执行。关于WaitFor的实现,将放到后面说明。

线程结束后,关闭线程Handle(正常线程创建的情况下Handle都是存在的),释放操作系统创建的线程对象。
然后调用TObject.Destroy释放本对象,并释放已经捕获的异常对象,最后调用RemoveThread减小进程的线程数。

其它关于Suspend/Resume及线程优先级设置等方面,不是本文的重点,不再赘述。下面要讨论的是本文的另两个重点
:Synchronize和WaitFor。

但是在介绍这两个函数之前,需要先介绍另外两个线程同步技术:事件和临界区。

事件(Event)与Delphi中的事件有所不同。从本质上说,Event其实相当于一个全局的布尔变量。它有两个赋值操作
:Set和Reset,相当于把它设置为True或False。而检查它的值是通过WaitFor操作进行。对应在Windows平台上,是三
个API函数:SetEvent、ResetEvent、WaitForSingleObject(实现WaitFor功能的API还有几个,这是最简单的一个)。

这三个都是原语,所以Event可以实现一般布尔变量不能实现的在多线程中的应用。Set和Reset的功能前面已经说过了
,现在来说一下WaitFor的功能:

WaitFor的功能是检查Event的状态是否是Set状态(相当于True),如果是则立即返回,如果不是,则等待它变为Set
状态,在等待期间,调用WaitFor的线程处于挂起状态。另外WaitFor有一个参数用于超时设置,如果此参数为0,则不
等待,立即返回Event的状态,如果是INFINITE则无限等待,直到Set状态发生,若是一个有限的数值,则等待相应的
毫秒数后返回Event的状态。

当Event从Reset状态向Set状态转换时,唤醒其它由于WaitFor这个Event而挂起的线程,这就是它为什么叫Event的原
因。所谓“事件”就是指“状态的转换”。通过Event可以在线程间传递这种“状态转换”信息。

当然用一个受保护(见下面的临界区介绍)的布尔变量也能实现类似的功能,只要用一个循环检查此布尔值的代码来
代替WaitFor即可。从功能上说完全没有问题,但实际使用中就会发现,这样的等待会占用大量的CPU资源,降低系统
性能,影响到别的线程的执行速度,所以是不经济的,有的时候甚至可能会有问题。所以不建议这样用。

临界区(CriticalSection)则是一项共享数据访问保护的技术。它其实也是相当于一个全局的布尔变量。但对它的操
作有所不同,它只有两个操作:Enter和Leave,同样可以把它的两个状态当作True和False,分别表示现在是否处于临
界区中。这两个操作也是原语,所以它可以用于在多线程应用中保护共享数据,防止访问冲突。

用临界区保护共享数据的方法很简单:在每次要访问共享数据之前调用Enter设置进入临界区标志,然后再操作数据,
最后调用Leave离开临界区。它的保护原理是这样的:当一个线程进入临界区后,如果此时另一个线程也要访问这个数
据,则它会在调用Enter时,发现已经有线程进入临界区,然后此线程就会被挂起,等待当前在临界区的线程调用
Leave离开临界区,当另一个线程完成操作,调用Leave离开后,此线程就会被唤醒,并设置临界区标志,开始操作数
据,这样就防止了访问冲突。

以前面那个InterlockedIncrement为例,我们用CriticalSection(Windows API)来实现它:

Var
InterlockedCrit : TRTLCriticalSection;
Procedure InterlockedIncrement( var aValue : Integer );
BeginEnterCriticalSection( InterlockedCrit );Inc( aValue );LeaveCriticalSection( InterlockedCrit );
End;

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

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

关于临界区的使用,有一点要注意:即数据访问时的异常情况处理。因为如果在数据操作时发生异常,将导致Leave操
作没有被执行,结果将使本应被唤醒的线程未被唤醒,可能造成程序的没有响应。所以一般来说,如下面这样使用临
界区才是正确的做法:

EnterCriticalSection
Try
// 操作临界区数据
FinallyLeaveCriticalSection
End;

最后要说明的是,Event和CriticalSection都是操作系统资源,使用前都需要创建,使用完后也同样需要释放。如
TThread类用到的一个全局Event:SyncEvent和全局CriticalSection:TheadLock,都是在
InitThreadSynchronization和DoneThreadSynchronization中进行创建和释放的,而它们则是在Classes单元的
Initialization和Finalization中被调用的。

由于在TThread中都是用API来操作Event和CriticalSection的,所以前面都是以API为例,其实Delphi已经提供了对它
们的封装,在SyncObjs单元中,分别是TEvent类和TCriticalSection类。用法也与前面用API的方法相差无几。因为
TEvent的构造函数参数过多,为了简单起见,Delphi还提供了一个用默认参数初始化的Event类:TSimpleEvent。

顺便再介绍一下另一个用于线程同步的类:TMultiReadExclusiveWriteSynchronizer,它是在SysUtils单元中定义的
。据我所知,这是Delphi RTL中定义的最长的一个类名,还好它有一个短的别名:TMREWSync。至于它的用处,我想光
看名字就可以知道了,我也就不多说了。

有了前面对Event和CriticalSection的准备知识,可以正式开始讨论Synchronize和WaitFor了。
我们知道,Synchronize是通过将部分代码放到主线程中执行来实现线程同步的,因为在一个进程中,只有一个主线程
。先来看看Synchronize的实现:

procedure TThread.Synchronize(Method: TThreadMethod);
beginFSynchronize.FThread := Self;FSynchronize.FSynchronizeException := nil;FSynchronize.FMethod := Method;Synchronize(@FSynchronize);
end;

其中FSynchronize是一个记录类型:

PSynchronizeRecord = ^TSynchronizeRecord;
TSynchronizeRecord = recordFThread: TObject;FMethod: TThreadMethod;FSynchronizeException: TObject;
end;

用于进行线程和主线程之间进行数据交换,包括传入线程类对象,同步方法及发生的异常。
在Synchronize中调用了它的一个重载版本,而且这个重载版本比较特别,它是一个“类方法”。所谓类方法,是一种
特殊的类成员方法,它的调用并不需要创建类实例,而是像构造函数那样,通过类名调用。之所以会用类方法来实现
它,是因为为了可以在线程对象没有创建时也能调用它。不过实际中是用它的另一个重载版本(也是类方法)和另一
个类方法StaticSynchronize。下面是这个Synchronize的代码:

class procedure TThread.Synchronize(ASyncRec: PSynchronizeRecord);
varSyncProc: TSyncProc;
beginif GetCurrentThreadID = MainThreadID thenASyncRec.FMethodelse beginSyncProc.Signal := CreateEvent(nil, True, False, nil);tryEnterCriticalSection(ThreadLock);tryif SyncList = nil thenSyncList := TList.Create;SyncProc.SyncRec := ASyncRec;SyncList.Add(@SyncProc);SignalSyncEvent;if Assigned(WakeMainThread) thenWakeMainThread(SyncProc.SyncRec.FThread);LeaveCriticalSection(ThreadLock);tryWaitForSingleObject(SyncProc.Signal, INFINITE);finallyEnterCriticalSection(ThreadLock);end;finallyLeaveCriticalSection(ThreadLock);end;finallyCloseHandle(SyncProc.Signal);end;if Assigned(ASyncRec.FSynchronizeException) then raise ASyncRec.FSynchronizeException;end;
end;

这段代码略多一些,不过也不算太复杂。
首先是判断当前线程是否是主线程,如果是,则简单地执行同步方法后返回。
如果不是主线程,则准备开始同步过程。
通过局部变量SyncProc记录线程交换数据(参数)和一个Event Handle,其记录结构如下:

TSyncProc = recordSyncRec: PSynchronizeRecord;Signal: THandle;
end;

然后创建一个Event,接着进入临界区(通过全局变量ThreadLock进行,因为同时只能有一个线程进入Synchronize状
态,所以可以用全局变量记录),然后就是把这个记录数据存入SyncList这个列表中(如果这个列表不存在的话,则
创建它)。可见ThreadLock这个临界区就是为了保护对SyncList的访问,这一点在后面介绍CheckSynchronize时会再
次看到。

再接下就是调用SignalSyncEvent,其代码在前面介绍TThread的构造函数时已经介绍过了,它的功能就是简单地将
SyncEvent作一个Set的操作。关于这个SyncEvent的用途,将在后面介绍WaitFor时再详述。

接下来就是最主要的部分了:调用WakeMainThread事件进行同步操作。WakeMainThread是一个TNotifyEvent类型的全
局事件。这里之所以要用事件进行处理,是因为Synchronize方法本质上是通过消息,将需要同步的过程放到主线程中
执行,如果在一些没有消息循环的应用中(如Console或DLL)是无法使用的,所以要使用这个事件进行处理。
而响应这个事件的是Application对象,下面两个方法分别用于设置和清空WakeMainThread事件的响应(来自Forms单元):

procedure TApplication.HookSynchronizeWakeup;
beginClasses.WakeMainThread := WakeMainThread;
end;procedure TApplication.UnhookSynchronizeWakeup;
beginClasses.WakeMainThread := nil;
end;

上面两个方法分别是在TApplication类的构造函数和析构函数中被调用。
这就是在Application对象中WakeMainThread事件响应的代码,消息就是在这里被发出的,它利用了一个空消息来实现:

procedure TApplication.WakeMainThread(Sender: TObject);
beginPostMessage(Handle, WM_NULL, 0, 0);
end;

而这个消息的响应也是在Application对象中,见下面的代码(删除无关的部分):

procedure TApplication.WndProc(var Message: TMessage);
…
begintry…with Message docase Msg of…WM_NULL:CheckSynchronize;…exceptHandleException(Self);end;
end;

其中的CheckSynchronize也是定义在Classes单元中的,由于它比较复杂,暂时不详细说明,只要知道它是具体处理
Synchronize功能的部分就好,现在继续分析Synchronize的代码。
在执行完WakeMainThread事件后,就退出临界区,然后调用WaitForSingleObject开始等待在进入临界区前创建的那个
Event。这个Event的功能是等待这个同步方法的执行结束,关于这点,在后面分析CheckSynchronize时会再说明。
注意在WaitForSingleObject之后又重新进入临界区,但没有做任何事就退出了,似乎没有意义,但这是必须的!
因为临界区的Enter和Leave必须严格的一一对应。那么是否可以改成这样呢:

if Assigned(WakeMainThread) thenWakeMainThread(SyncProc.SyncRec.FThread);WaitForSingleObject(SyncProc.Signal, INFINITE);finallyLeaveCriticalSection(ThreadLock);
end;

上面的代码和原来的代码最大的区别在于把WaitForSingleObject也纳入临界区的限制中了。看上去没什么影响,还使
代码大大简化了,但真的可以吗?
事实上是不行!

因为我们知道,在Enter临界区后,如果别的线程要再进入,则会被挂起。而WaitFor方法则会挂起当前线程,直到等
待别的线程SetEvent后才会被唤醒。如果改成上面那样的代码的话,如果那个SetEvent的线程也需要进入临界区的话
,死锁(Deadlock)就发生了(关于死锁的理论,请自行参考操作系统原理方面的资料)。
死锁是线程同步中最需要注意的方面之一!
最后释放开始时创建的Event,如果被同步的方法返回异常的话,还会在这里再次抛出异常。

回到前面CheckSynchronize,见下面的代码:

function CheckSynchronize(Timeout: Integer = 0): Boolean;
varSyncProc: PSyncProc;LocalSyncList: TList;
beginif GetCurrentThreadID <> MainThreadID thenraise EThread.CreateResFmt(@SCheckSynchronizeError, [GetCurrentThreadID]);if Timeout > 0 thenWaitForSyncEvent(Timeout)elseResetSyncEvent;LocalSyncList := nil;EnterCriticalSection(ThreadLock);tryInteger(LocalSyncList) := InterlockedExchange(Integer(SyncList), Integer(LocalSyncList));tryResult := (LocalSyncList <> nil) and (LocalSyncList.Count > 0);if Result then beginwhile LocalSyncList.Count > 0 do beginSyncProc := LocalSyncList[0];LocalSyncList.Delete(0);LeaveCriticalSection(ThreadLock);trytrySyncProc.SyncRec.FMethod;exceptSyncProc.SyncRec.FSynchronizeException := AcquireExceptionObject;end;finallyEnterCriticalSection(ThreadLock);end;SetEvent(SyncProc.signal);end;end;finallyLocalSyncList.Free;end;finallyLeaveCriticalSection(ThreadLock);end;
end;

首先,这个方法必须在主线程中被调用(如前面通过消息传递到主线程),否则就抛出异常。
接下来调用ResetSyncEvent(它与前面SetSyncEvent对应的,之所以不考虑WaitForSyncEvent的情况,是因为只有在
Linux版下才会调用带参数的CheckSynchronize,Windows版下都是调用默认参数0的CheckSynchronize)。
现在可以看出SyncList的用途了:它是用于记录所有未被执行的同步方法的。因为主线程只有一个,而子线程可能有
很多个,当多个子线程同时调用同步方法时,主线程可能一时无法处理,所以需要一个列表来记录它们。
在这里用一个局部变量LocalSyncList来交换SyncList,这里用的也是一个原语:InterlockedExchange。同样,这里
也是用临界区将对SyncList的访问保护起来。
只要LocalSyncList不为空,则通过一个循环来依次处理累积的所有同步方法调用。最后把处理完的LocalSyncList释
放掉,退出临界区。

再来看对同步方法的处理:首先是从列表中移出(取出并从列表中删除)第一个同步方法调用数据。然后退出临界区
(原因当然也是为了防止死锁)。
接着就是真正的调用同步方法了。
如果同步方法中出现异常,将被捕获后存入同步方法数据记录中。
重新进入临界区后,调用SetEvent通知调用线程,同步方法执行完成了(详见前面Synchronize中的
WaitForSingleObject调用)。
至此,整个Synchronize的实现介绍完成。

最后来说一下WaitFor,它的功能就是等待线程执行结束。其代码如下:

function TThread.WaitFor: LongWord;
varH: array[0..1] of THandle;WaitResult: Cardinal;Msg: TMsg;
beginH[0] := FHandle;if GetCurrentThreadID = MainThreadID then  beginWaitResult := 0;H[1] := SyncEvent;repeat{ This prevents a potential deadlock if the background thread does a SendMessage to the foreground thread }if WaitResult = WAIT_OBJECT_0 + 2 thenPeekMessage(Msg, 0, 0, 0, PM_NOREMOVE);WaitResult := MsgWaitForMultipleObjects(2, H, False, 1000, QS_SENDMESSAGE);CheckThreadError(WaitResult <> WAIT_FAILED);if WaitResult = WAIT_OBJECT_0 + 1 thenCheckSynchronize;until WaitResult = WAIT_OBJECT_0;end else WaitForSingleObject(H[0], INFINITE);CheckThreadError(GetExitCodeThread(H[0], Result));
end;

如果不是在主线程中执行WaitFor的话,很简单,只要调用WaitForSingleObject等待此线程的Handle为Signaled状态
即可。

如果是在主线程中执行WaitFor则比较麻烦。首先要在Handle数组中增加一个SyncEvent,然后循环等待,直到线程结
束(即MsgWaitForMultipleObjects返回WAIT_OBJECT_0,详见MSDN中关于此API的说明)。
在循环等待中作如下处理:如果有消息发生,则通过PeekMessage取出此消息(但并不把它从消息循环中移除),然后
调用MsgWaitForMultipleObjects来等待线程Handle或SyncEvent出现Signaled状态,同时监听消息(QS_SENDMESSAGE
参数,详见MSDN中关于此API的说明)。可以把此API当作一个可以同时等待多个Handle的WaitForSingleObject。如果
是SyncEvent被SetEvent(返回WAIT_OBJECT_0 + 1),则调用CheckSynchronize处理同步方法。
为什么在主线程中调用WaitFor必须用MsgWaitForMultipleObjects,而不能用WaitForSingleObject等待线程结束呢?
因为防止死锁。由于在线程函数Execute中可能调用Synchronize处理同步方法,而同步方法是在主线程中执行的,如
果用WaitForSingleObject等待的话,则主线程在这里被挂起,同步方法无法执行,导致线程也被挂起,于是发生死锁。
而改用WaitForMultipleObjects则没有这个问题。首先,它的第三个参数为False,表示只要线程Handle或SyncEvent
中只要有一个Signaled即可使主线程被唤醒,至于加上QS_SENDMESSAGE是因为Synchronize是通过消息传到主线程来的
,所以还要防止消息被阻塞。这样,当线程中调用Synchronize时,主线程就会被唤醒并处理同步调用,在调用完成后
继续进入挂起等待状态,直到线程结束。
至此,对线程类TThread的分析可以告一个段落了,对前面的分析作一个总结:
1、 线程类的线程必须按正常的方式结束,即Execute执行结束,所以在其中的代码中必须在适当的地方加入足够多
的对Terminated标志的判断,并及时退出。如果必须要“立即”退出,则不能使用线程类,而要改用API或RTL函数。
2、 对可视VCL的访问要放在Synchronize中,通过消息传递到主线程中,由主线程处理。
3、 线程共享数据的访问应该用临界区进行保护(当然用Synchronize也行)。
4、 线程通信可以采用Event进行(当然也可以用Suspend/Resume)。
5、 当在多线程应用中使用多种线程同步方式时,一定要小心防止出现死锁。
6、 等待线程结束要用WaitFor方法。

***************************** 结束***********************************************

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 第九课 常见的二叉树:平衡二叉树之红黑树

    第九章 常见的二叉树:平衡二叉树之红黑树1 背景2 定义3 红黑树与AVL树对比4 树结构调整5 代码实现5.1 红黑树的插入5.2 红黑树的删除5.3 具体代码实现 1 背景 对于二叉排序树,时间复杂度最差时候会是 O(n),比如插入的元素是有序的,生成的二叉排序树就是一个链表,这种情况下…...

    2024/5/6 4:46:40
  2. 【Linux 性能优化系列】Linux 性能优化 -- CPU 性能篇(零) CPU 性能优化综述

    【Linux 性能优化系列】Linux 性能优化 -- CPU 性能优化综述【1】CPU 性能指标CPU 使用率 描述了非空闲时间占总 CPU 时间的百分比; 用户 CPU 使用率,包括用户态 CPU 使用率(user)和低优先级用户态 CPU 使用率(nice),表示 CPU 在用户态运行的时间百分比;用户 CPU 使用率…...

    2024/5/6 2:26:27
  3. Android 实现多种样式 item 列表

    写了几年的代码了,真心体会到了,代码得一天一天的写,饭得一天一天的吃,一口气吃个胖子的天才也不是没有,但是自己吃几碗饭,自己还是清楚的,想一想很久之前,自己写多样式列表还是直接用别人的框架,套进去了,然后出一个盼着别出 bug 的列表,最近有时间,自己撸一个简单…...

    2024/5/4 22:09:24
  4. 一起认识Spring

    Spring的AOP(一) spring中最重要的两个东西就是IOC(DI)和AOP,今天我们主要讲解有关AOP相关的知识,在了解AOP前,希望读者先了解动态代理的相关知识。 AOP我主要讲解通过注解的方式进行AOP的: 1.开启@Aspect注解,我们要让这个注解被扫描到,我们可以添加@EnableAspectJA…...

    2024/4/11 19:31:24
  5. C/C++字符串处理相关总结

    C/C++字符串处理相关总览< cctype >头文件的函数头文件内< cstring >的函数头文件< string >中的函数为什么size_t重要?ASCII字符与转义字符读入一行数据清空字符串区域复制字符串区域搜索字符串相关字符串连接字符串分割字符串比较字符串转整型/浮点型整型…...

    2024/4/23 21:48:27
  6. 玩转数据库技能自测 考试答案(阿里云打卡领T恤活动)

    下面有关PolarDB数据库集群账号说法错误的是? 选B 一个集群可以创建多个高权限账号以下哪个数据库不是关系型数据库? C.Redis7.RDS MySQL与自建数据库对比有哪些优势? 我选的ABCD。但最后是90分,怀疑这里扣分了。其他答案都很肯定。 猜测答案是BCD。A高性价比这个描述我没找…...

    2024/4/18 3:42:41
  7. Javascript基础之https工作原理是什么?

    作为一个前端,虽说每天都是在写JavaScript,但是http相关原理还是要懂的。近些年,互联网发生翻天覆地的变化。尤其是我们一直习以为常的HTTP协议,在逐渐的被HTTPS协议所取代,在浏览器、搜索引擎、CA机构、大型互联网企业的共同促进下,互联网迎来了“HTTPS加密时代”,HTTP…...

    2024/4/11 19:31:21
  8. 7寸高通八核Android9.0坚固三防平板,高亮屏户外阳光可视

    ​T75产品形态7寸安卓三防平板平台Qualcomm外观尺寸216.98*133.96*21.4mm系统Android 9.0CPUMSM8953,八核,频率2.0GhzRAM4GROM64/128GB亮度700 nit分辨率800*1280 IPS重量680g触摸屏5多点电容屏,G+G,硬度7H以上,防刮花,TP厚度:1.1mm,支持手套触摸电池3.7V/7500mAh,约7小…...

    2024/4/11 19:31:20
  9. https单向认证和双向认证demo(SpringBoot+okhttp3+keytool自签名)

    目录一、前言二、软件版本三、单向认证四、双向认证一、前言HTTPS (全称:Hyper Text Transfer Protocol over SecureSocket Layer),是以安全为目标的 HTTP 通道,在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性 [1] 。HTTPS 在HTTP 的基础下加入SSL 层,HTT…...

    2024/4/23 11:12:43
  10. Windows下Git多账号配置

    平时都是配置工作时使用的git账号,有时候需要对自己的项目进行管理,所以需要配置多个ssh_key.一、创建另一个需要管理的密钥在win10系统下打开C:\Users\Administrator文件夹,右击选择git bash here执行命令 ssh-keygen -t rsa -C roc_wl 创建对应的sshkey,命名为id_rsa_roc…...

    2024/4/10 12:05:14
  11. 数据分析——阿里资金流入流出分析(task1-数据探索与分析)

    数据分析——阿里资金流入流出分析(task1-数据探索与分析) 学习目标 熟悉数据分析的流程,了解金融时间序列分析的一般方法。 任务安排 数据集可在阿里天池下载: https://tianchi.aliyun.com/competition/entrance/231573/information 数据实践 库导入 import pandas as pd…...

    2024/4/22 6:13:52
  12. 简单的CAS单点登录

    使用Redis+cookie实现简单的单点登录1:用户第一次登陆A系统,发现未登陆,会携带上returnUrl跳转至CAS系统的登陆方法,在这个方法会验证此用户是否已经登陆(具有全局会话),如果没有登陆,就会跳转至CAS登陆页面,让用户登录@GetMapping("/login")public String …...

    2024/4/10 12:05:12
  13. 微信小程序遇到的各种小坑贴在这里

    1.image底部空白:给image组件css添加vertical-align: middle;vertical-align: middle;...

    2024/4/17 16:08:44
  14. leetcode 529. 扫雷游戏 (python)

    题目描述让我们一起来玩扫雷游戏!给定一个代表游戏板的二维字符矩阵。 M 代表一个未挖出的地雷,E 代表一个未挖出的空方块,B 代表没有相邻(上,下,左,右,和所有4个对角线)地雷的已挖出的空白方块,数字(1 到 8)表示有多少地雷与这块已挖出的方块相邻,X 则表示一个已…...

    2024/4/11 19:31:18
  15. Slf4j和Log4j的区别

    log4j 大家都知道,就不在多说了,重点说说slf4j;简单日记门面(simple logging Facade for java)SLF4J是为各种loging APIs提供一个简单统一的接口,从而使得最终用户能够在部署的时候配置自己希 望的loging APIs实现准确的说,slf4j并不是一种具体的日志系统,而是一个用户日志…...

    2024/5/5 22:10:24
  16. 通过SQL查询获取特定数据库的所有表名?

    本文翻译自:Get all table names of a particular database by SQL query? I am working on application which can deal with multiple database servers like "MySQL" and "MS SQL Server". 我正在研究可以处理多个数据库服务器(如“ MySQL”和“ MS …...

    2024/4/11 19:31:17
  17. Redis BitMap 使用场景及实现

    转载自:https://codingnote.cc/p/157727https://diuut.com/?p=1055叙述前段时间,在网上看到一道面试题:如何用redis存储统计1亿用户一年的登陆情况,并快速检索任意时间窗口内的活跃用户数量。觉得很有意思,就仔细想了下 。并做了一系列实验,自己模拟了下 。还是有点收获…...

    2024/4/11 19:31:15
  18. cef程序输出

    Cef输出输出目录中包括了CEF必选和可选的类库和资源文件(加粗的为必选),它们的主要功能如下:│ CefSharp.dll:CefSharp基础类库,定义了相关接口;│ CefSharp.Core.dll:CefSharp核心类库;│ CefSharp.WinForms.dll:CefSharp组件类库,如果是其他平台,可能是CefSharp.…...

    2024/5/5 17:18:14
  19. 【深度学习】生成对抗网络 GAN

    【深度学习】生成对抗网络 GAN引言算法简介模型介绍工作原理 引言生成式对抗网络(GAN, Generative Adversarial Networks)是一种深度学习模型,自从2014年Goodfellow提出了GAN以来,GAN已经成为近年来无监督学习最具前景的方法之一。原文链接如下: Generative Adversarial Ne…...

    2024/5/6 1:21:20
  20. RFID智能汽车制造管理系统解决方案

    RFID智能汽车制造管理系统解决方案 1.项目背景 1.1 行业背景 在汽车行业快速发展竞争日益激励的背景下,各个汽车制造商都在追求管理系统的优化。已从开始的只追求最终结果的最优,发展到目前要求使制造汽车的每个环节都达到最优的目的,这就给管理者提出了一个严峻的问题,如何…...

    2024/4/11 19:31:12

最新文章

  1. 【hive】transform脚本

    文档地址&#xff1a;https://cwiki.apache.org/confluence/display/Hive/LanguageManualTransform 一、介绍二、实现1.脚本上传到本地2.脚本上传到hdfs 三、几个需要注意的点1.脚本名不要写全路径2.using后面语句中&#xff0c;带不带"python"的问题3.py脚本Shebang…...

    2024/5/6 6:09:26
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 2024.3.21 QT

    QT登录界面设计&#xff1a; //头文件 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QMovie>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nu…...

    2024/5/4 13:57:26
  4. 宝塔面板MySQL错误修复 has the wrong structure or is missing

    MySQL未知原因造成停止服务&#xff0c;查看日志时&#xff0c;发现&#xff1a; [Warning] Optional native table &#xff07;performance_schema&#xff07;.&#xff07;processlist&#xff07; has the wrong structure or is missing.进入远程&#xff0c;输入命令&a…...

    2024/4/29 5:22:24
  5. 416. 分割等和子集问题(动态规划)

    题目 题解 class Solution:def canPartition(self, nums: List[int]) -> bool:# badcaseif not nums:return True# 不能被2整除if sum(nums) % 2 ! 0:return False# 状态定义&#xff1a;dp[i][j]表示当背包容量为j&#xff0c;用前i个物品是否正好可以将背包填满&#xff…...

    2024/5/5 18:19:03
  6. 【Java】ExcelWriter自适应宽度工具类(支持中文)

    工具类 import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet;/*** Excel工具类** author xiaoming* date 2023/11/17 10:40*/ public class ExcelUti…...

    2024/5/5 12:22:20
  7. Spring cloud负载均衡@LoadBalanced LoadBalancerClient

    LoadBalance vs Ribbon 由于Spring cloud2020之后移除了Ribbon&#xff0c;直接使用Spring Cloud LoadBalancer作为客户端负载均衡组件&#xff0c;我们讨论Spring负载均衡以Spring Cloud2020之后版本为主&#xff0c;学习Spring Cloud LoadBalance&#xff0c;暂不讨论Ribbon…...

    2024/5/5 19:59:54
  8. TSINGSEE青犀AI智能分析+视频监控工业园区周界安全防范方案

    一、背景需求分析 在工业产业园、化工园或生产制造园区中&#xff0c;周界防范意义重大&#xff0c;对园区的安全起到重要的作用。常规的安防方式是采用人员巡查&#xff0c;人力投入成本大而且效率低。周界一旦被破坏或入侵&#xff0c;会影响园区人员和资产安全&#xff0c;…...

    2024/5/4 23:54:44
  9. VB.net WebBrowser网页元素抓取分析方法

    在用WebBrowser编程实现网页操作自动化时&#xff0c;常要分析网页Html&#xff0c;例如网页在加载数据时&#xff0c;常会显示“系统处理中&#xff0c;请稍候..”&#xff0c;我们需要在数据加载完成后才能继续下一步操作&#xff0c;如何抓取这个信息的网页html元素变化&…...

    2024/5/5 15:25:47
  10. 【Objective-C】Objective-C汇总

    方法定义 参考&#xff1a;https://www.yiibai.com/objective_c/objective_c_functions.html Objective-C编程语言中方法定义的一般形式如下 - (return_type) method_name:( argumentType1 )argumentName1 joiningArgument2:( argumentType2 )argumentName2 ... joiningArgu…...

    2024/5/6 6:01:13
  11. 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】

    &#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P5713-洛谷团队系统【入门2分支结构】&#x1f30f;题目描述&#x1f30f;输入格…...

    2024/5/4 23:54:44
  12. 【ES6.0】- 扩展运算符(...)

    【ES6.0】- 扩展运算符... 文章目录 【ES6.0】- 扩展运算符...一、概述二、拷贝数组对象三、合并操作四、参数传递五、数组去重六、字符串转字符数组七、NodeList转数组八、解构变量九、打印日志十、总结 一、概述 **扩展运算符(...)**允许一个表达式在期望多个参数&#xff0…...

    2024/5/6 1:08:53
  13. 摩根看好的前智能硬件头部品牌双11交易数据极度异常!——是模式创新还是饮鸩止渴?

    文 | 螳螂观察 作者 | 李燃 双11狂欢已落下帷幕&#xff0c;各大品牌纷纷晒出优异的成绩单&#xff0c;摩根士丹利投资的智能硬件头部品牌凯迪仕也不例外。然而有爆料称&#xff0c;在自媒体平台发布霸榜各大榜单喜讯的凯迪仕智能锁&#xff0c;多个平台数据都表现出极度异常…...

    2024/5/5 18:50:00
  14. Go语言常用命令详解(二)

    文章目录 前言常用命令go bug示例参数说明 go doc示例参数说明 go env示例 go fix示例 go fmt示例 go generate示例 总结写在最后 前言 接着上一篇继续介绍Go语言的常用命令 常用命令 以下是一些常用的Go命令&#xff0c;这些命令可以帮助您在Go开发中进行编译、测试、运行和…...

    2024/5/6 0:27:44
  15. 用欧拉路径判断图同构推出reverse合法性:1116T4

    http://cplusoj.com/d/senior/p/SS231116D 假设我们要把 a a a 变成 b b b&#xff0c;我们在 a i a_i ai​ 和 a i 1 a_{i1} ai1​ 之间连边&#xff0c; b b b 同理&#xff0c;则 a a a 能变成 b b b 的充要条件是两图 A , B A,B A,B 同构。 必要性显然&#xff0…...

    2024/5/5 2:25:33
  16. 【NGINX--1】基础知识

    1、在 Debian/Ubuntu 上安装 NGINX 在 Debian 或 Ubuntu 机器上安装 NGINX 开源版。 更新已配置源的软件包信息&#xff0c;并安装一些有助于配置官方 NGINX 软件包仓库的软件包&#xff1a; apt-get update apt install -y curl gnupg2 ca-certificates lsb-release debian-…...

    2024/5/4 21:24:42
  17. Hive默认分割符、存储格式与数据压缩

    目录 1、Hive默认分割符2、Hive存储格式3、Hive数据压缩 1、Hive默认分割符 Hive创建表时指定的行受限&#xff08;ROW FORMAT&#xff09;配置标准HQL为&#xff1a; ... ROW FORMAT DELIMITED FIELDS TERMINATED BY \u0001 COLLECTION ITEMS TERMINATED BY , MAP KEYS TERMI…...

    2024/5/5 13:14:22
  18. 【论文阅读】MAG:一种用于航天器遥测数据中有效异常检测的新方法

    文章目录 摘要1 引言2 问题描述3 拟议框架4 所提出方法的细节A.数据预处理B.变量相关分析C.MAG模型D.异常分数 5 实验A.数据集和性能指标B.实验设置与平台C.结果和比较 6 结论 摘要 异常检测是保证航天器稳定性的关键。在航天器运行过程中&#xff0c;传感器和控制器产生大量周…...

    2024/5/4 13:16:06
  19. --max-old-space-size=8192报错

    vue项目运行时&#xff0c;如果经常运行慢&#xff0c;崩溃停止服务&#xff0c;报如下错误 FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 因为在 Node 中&#xff0c;通过JavaScript使用内存时只能使用部分内存&#xff08;64位系统&…...

    2024/5/5 17:03:52
  20. 基于深度学习的恶意软件检测

    恶意软件是指恶意软件犯罪者用来感染个人计算机或整个组织的网络的软件。 它利用目标系统漏洞&#xff0c;例如可以被劫持的合法软件&#xff08;例如浏览器或 Web 应用程序插件&#xff09;中的错误。 恶意软件渗透可能会造成灾难性的后果&#xff0c;包括数据被盗、勒索或网…...

    2024/5/5 21:10:50
  21. JS原型对象prototype

    让我简单的为大家介绍一下原型对象prototype吧&#xff01; 使用原型实现方法共享 1.构造函数通过原型分配的函数是所有对象所 共享的。 2.JavaScript 规定&#xff0c;每一个构造函数都有一个 prototype 属性&#xff0c;指向另一个对象&#xff0c;所以我们也称为原型对象…...

    2024/5/5 3:37:58
  22. C++中只能有一个实例的单例类

    C中只能有一个实例的单例类 前面讨论的 President 类很不错&#xff0c;但存在一个缺陷&#xff1a;无法禁止通过实例化多个对象来创建多名总统&#xff1a; President One, Two, Three; 由于复制构造函数是私有的&#xff0c;其中每个对象都是不可复制的&#xff0c;但您的目…...

    2024/5/4 23:54:30
  23. python django 小程序图书借阅源码

    开发工具&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索图书&#xff0c;轮播图&#xff0…...

    2024/5/5 17:03:21
  24. 电子学会C/C++编程等级考试2022年03月(一级)真题解析

    C/C++等级考试(1~8级)全部真题・点这里 第1题:双精度浮点数的输入输出 输入一个双精度浮点数,保留8位小数,输出这个浮点数。 时间限制:1000 内存限制:65536输入 只有一行,一个双精度浮点数。输出 一行,保留8位小数的浮点数。样例输入 3.1415926535798932样例输出 3.1…...

    2024/5/5 15:25:31
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57