mirror of
				https://gitlab.com/freepascal.org/fpc/source.git
				synced 2025-10-31 10:11:27 +01:00 
			
		
		
		
	 11b601d44c
			
		
	
	
		11b601d44c
		
	
	
	
	
		
			
			Notes: - Netware had a ThreadSwitch in there, but that is not really required - some platform were missing the setting of FTerminated to True, thus they'll now do that as well git-svn-id: trunk@46543 -
		
			
				
	
	
		
			301 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			301 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| {
 | |
|     This file is part of the Free Pascal run time library.
 | |
|     Copyright (c) 1999-2000 by Peter Vreman
 | |
|     Copyright (c) 2006 by Jonas Maebe
 | |
|     members of the Free Pascal development team.
 | |
| 
 | |
|     Generic *nix TThread implementation
 | |
| 
 | |
|     See the file COPYING.FPC, included in this distribution,
 | |
|     for details about the copyright.
 | |
| 
 | |
|     This program is distributed in the hope that it will be useful,
 | |
|     but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 | |
| 
 | |
|  **********************************************************************}
 | |
| 
 | |
| 
 | |
| 
 | |
| {
 | |
|   What follows, is a short description on my implementation of TThread.
 | |
|   Most information can also be found by reading the source and accompanying
 | |
|   comments.
 | |
| 
 | |
|   A thread is created using BeginThread, which in turn calls
 | |
|   pthread_create. So the threads here are always posix threads.
 | |
|   Posix doesn't define anything for suspending threads as this is
 | |
|   inherintly unsafe. Just don't suspend threads at points they cannot
 | |
|   control. Therefore, I didn't implement .Suspend() if its called from
 | |
|   outside the threads execution flow (except on Linux _without_ NPTL).
 | |
| 
 | |
|   The implementation for .suspend uses an RTLEvent, which is initialized
 | |
|   at thread creation. If the thread tries to suspend itself, we simply
 | |
|   let it wait on the Event until it is unblocked by someone else
 | |
|   who calls .Resume.
 | |
| 
 | |
| 
 | |
|   Johannes Berg <johannes@sipsolutions.de>, Sunday, November 16 2003
 | |
| }
 | |
| 
 | |
| { ok, so this is a hack, but it works nicely. Just never use
 | |
|   a multiline argument with WRITE_DEBUG! }
 | |
| 
 | |
| {.$DEFINE DEBUG_MT}
 | |
| {$MACRO ON}
 | |
| {$IFDEF DEBUG_MT}
 | |
| {$define WRITE_DEBUG := writeln} // actually write something
 | |
| {$ELSE}
 | |
| {$define WRITE_DEBUG := //}      // just comment out those lines
 | |
| {$ENDIF}
 | |
| 
 | |
| var
 | |
|   ThreadsInited: boolean = false;
 | |
| const
 | |
|   // stupid, considering its not even implemented...
 | |
|   Priorities: array [TThreadPriority] of Integer =
 | |
|    (-20,-19,-10,0,9,18,19);
 | |
| 
 | |
| 
 | |
| procedure DoneThreads;
 | |
| begin
 | |
|   ThreadsInited := false;
 | |
| end;
 | |
| 
 | |
| function ThreadFunc(parameter: Pointer): ptrint;
 | |
| var
 | |
|   LThread: TThread;
 | |
|   LFreeOnTerminate: boolean;
 | |
| {$ifdef DEBUG_MT}
 | |
|   lErrorAddr, lErrorBase: Pointer;
 | |
| {$endif}
 | |
| begin
 | |
|   WRITE_DEBUG('ThreadFunc is here...');
 | |
|   LThread := TThread(parameter);
 | |
|   WRITE_DEBUG('thread initing, parameter = ', ptruint(LThread));
 | |
|   try
 | |
|     // wait until AfterConstruction has been called, so we cannot
 | |
|     // free ourselves before TThread.Create has finished
 | |
|     // (since that one may check our VTM in case of $R+, and
 | |
|     //  will call the AfterConstruction method in all cases)
 | |
| //    LThread.Suspend;
 | |
|     WRITE_DEBUG('AfterConstruction should have been called for ',ptruint(lthread));
 | |
|     if LThread.FInitialSuspended then
 | |
|       begin
 | |
|         WRITE_DEBUG('thread ', ptruint(LThread), ' waiting for RTLEvent ', ptruint(LThread.FSuspendEvent));
 | |
|         RtlEventWaitFor(LThread.FSuspendEvent);
 | |
|         if not(LThread.FTerminated) then
 | |
|           begin
 | |
|             if not LThread.FSuspended then
 | |
|               begin
 | |
|                 LThread.FInitialSuspended := false;
 | |
|                 CurrentThreadVar := LThread;
 | |
|                 WRITE_DEBUG('going into LThread.Execute');
 | |
|                 LThread.Execute;
 | |
|               end
 | |
|             else
 | |
|               WRITE_DEBUG('thread ', ptruint(LThread), ' initially created suspended, resumed, but still suspended?!');
 | |
|           end
 | |
|         else
 | |
|           WRITE_DEBUG('initially created suspended, but already terminated');
 | |
|       end
 | |
|      else
 | |
|        begin
 | |
|          LThread.FSuspendedInternal := true;
 | |
|          WRITE_DEBUG('waiting for SuspendedInternal - ', LThread.ClassName);
 | |
|          RtlEventWaitFor(LThread.FSuspendEvent);
 | |
|          CurrentThreadVar := LThread;
 | |
|          WRITE_DEBUG('going into LThread.Execute - ', LThread.ClassName);
 | |
|          LThread.Execute;
 | |
|        end;
 | |
|   except
 | |
|     on e: exception do begin
 | |
|       LThread.FFatalException := TObject(AcquireExceptionObject);
 | |
| {$ifdef DEBUG_MT}
 | |
|       lErrorAddr:=ExceptAddr;
 | |
|       lErrorBase:=ExceptFrames^;
 | |
|       writeln(stderr,'Exception caught in thread $',hexstr(LThread),
 | |
|         ' at $',hexstr(lErrorAddr));
 | |
|       writeln(stderr,BackTraceStrFunc(lErrorAddr));
 | |
|       dump_stack(stderr,lErrorBase);
 | |
|       writeln(stderr);
 | |
| {$endif}
 | |
|       // not sure if we should really do this...
 | |
|       // but .Destroy was called, so why not try FreeOnTerminate?
 | |
|       if e is EThreadDestroyCalled then LThread.FFreeOnTerminate := true;
 | |
|     end;
 | |
|   end;
 | |
|   WRITE_DEBUG('thread done running');
 | |
|   Result := LThread.FReturnValue;
 | |
|   WRITE_DEBUG('Result is ',Result);
 | |
|   LFreeOnTerminate := LThread.FreeOnTerminate;
 | |
|   LThread.DoTerminate;
 | |
|   LThread.FFinished := True;
 | |
|   if LFreeOnTerminate then
 | |
|     begin
 | |
|       WRITE_DEBUG('Thread ',ptruint(lthread),' should be freed');
 | |
|       LThread.Free;
 | |
|       WRITE_DEBUG('Thread freed');
 | |
|       WRITE_DEBUG('thread func calling EndThread');
 | |
|       // we can never come here if the thread has already been joined, because
 | |
|       // this function is the thread's main function (so it would have terminated
 | |
|       // already in case it was joined)
 | |
|       EndThread(Result);
 | |
|     end;
 | |
| end;
 | |
| 
 | |
| { TThread }
 | |
| procedure TThread.SysCreate(CreateSuspended: Boolean;
 | |
|                             const StackSize: SizeUInt);
 | |
| begin
 | |
|   FSuspendEvent := RtlEventCreate;
 | |
|   WRITE_DEBUG('thread ', ptruint(self), ' created RTLEvent ', ptruint(FSuspendEvent));
 | |
|   FSuspended := CreateSuspended;
 | |
|   FThreadReaped := false;
 | |
|   FInitialSuspended := CreateSuspended;
 | |
|   FFatalException := nil;
 | |
|   FSuspendedInternal := not CreateSuspended;
 | |
|   WRITE_DEBUG('creating thread, self = ',ptruint(self));
 | |
|   FHandle:= BeginThread(@ThreadFunc, Pointer(Self), FThreadID, StackSize);
 | |
|   if FHandle = TThreadID(0) then
 | |
|     raise EThread.create('Failed to create new thread');
 | |
|   WRITE_DEBUG('TThread.Create done, fhandle = ', ptruint(fhandle));
 | |
| end;
 | |
| 
 | |
| 
 | |
| procedure TThread.SysDestroy;
 | |
| begin
 | |
|   if not assigned(FSuspendEvent) then
 | |
|     { exception in constructor }
 | |
|     exit;
 | |
|   if (FHandle = TThreadID(0)) then
 | |
|   { another exception in constructor }
 | |
|     begin
 | |
|       RtlEventDestroy(FSuspendEvent);
 | |
|       exit;
 | |
|     end;
 | |
|   if (FThreadID = GetCurrentThreadID) then
 | |
|     begin
 | |
|       if not(FFreeOnTerminate) and not FFinished then
 | |
|         raise EThreadDestroyCalled.Create('A thread cannot destroy itself except by setting FreeOnTerminate and leaving!');
 | |
|       FFreeOnTerminate := false;
 | |
|     end
 | |
|   else
 | |
|     begin
 | |
|       // if someone calls .Free on a thread with not(FreeOnTerminate), there
 | |
|       // is no problem. Otherwise, FreeOnTerminate must be set to false so
 | |
|       // when ThreadFunc exits the main runloop, it does not try to Free
 | |
|       // itself again
 | |
|       FFreeOnTerminate := false;
 | |
|       { you can't join yourself, so only for FThreadID<>GetCurrentThreadID }
 | |
|       { and you can't join twice -> make sure we didn't join already       }
 | |
|       if not FThreadReaped then
 | |
|         begin
 | |
|           Terminate;
 | |
|           if (FSuspendedInternal or FInitialSuspended) then
 | |
|             Resume;
 | |
|           WaitFor;
 | |
|         end;
 | |
|     end;
 | |
|   RtlEventDestroy(FSuspendEvent);
 | |
|   FFatalException.Free;
 | |
|   FFatalException := nil;
 | |
|   { threadvars have been released by cthreads.ThreadMain -> DoneThread, or  }
 | |
|   { or will be released (in case of FFreeOnTerminate) after this destructor }
 | |
|   { has exited by ThreadFunc->EndThread->cthreads.CEndThread->DoneThread)   }
 | |
| end;
 | |
| 
 | |
| procedure TThread.SetSuspended(Value: Boolean);
 | |
| begin
 | |
|   if Value <> FSuspended then
 | |
|     if Value then
 | |
|       Suspend
 | |
|     else
 | |
|       Resume;
 | |
| end;
 | |
| 
 | |
| procedure TThread.Suspend;
 | |
| begin
 | |
|   if FThreadID = GetCurrentThreadID then
 | |
|     begin
 | |
|       if not FSuspended and
 | |
|          (InterLockedExchange(longint(FSuspended),longint(longbool(true))) = longint(longbool(false))) then
 | |
|         RtlEventWaitFor(FSuspendEvent)
 | |
|     end
 | |
|   else
 | |
|     begin
 | |
|       Raise EThread.create('Suspending one thread from inside another one is unsupported (because it is unsafe and deadlock prone) by *nix and posix operating systems');
 | |
|     end;
 | |
| end;
 | |
| 
 | |
| 
 | |
| procedure TThread.Resume;
 | |
| begin
 | |
|   if FSuspendedInternal and (InterLockedExchange(longint(FSuspendedInternal),ord(false)) = longint(longbool(true))) then
 | |
|     begin
 | |
|       WRITE_DEBUG('resuming thread after TThread construction',ptruint(self));
 | |
|       RtlEventSetEvent(FSuspendEvent);
 | |
|     end
 | |
|   else
 | |
|     begin
 | |
|       if FSuspended and
 | |
|          { don't compare with ord(true) or ord(longbool(true)), }
 | |
|          { becaue a longbool's "true" value is anyting <> false }
 | |
|          (InterLockedExchange(longint(FSuspended),longint(false)) <> longint(longbool(false))) then
 | |
|         begin
 | |
|           WRITE_DEBUG('resuming ',ptruint(self));
 | |
|           RtlEventSetEvent(FSuspendEvent);
 | |
|         end
 | |
|     end
 | |
| end;
 | |
| 
 | |
| 
 | |
| function TThread.WaitFor: Integer;
 | |
| begin
 | |
|   WRITE_DEBUG('waiting for thread ',ptruint(FHandle));
 | |
|   If (MainThreadID=GetCurrentThreadID) then 
 | |
|     {
 | |
|      FFinished is set after DoTerminate, which does a synchronize of OnTerminate, 
 | |
|      so make sure synchronize works (or indeed any other synchronize that may be 
 | |
|      in progress)
 | |
|     }
 | |
|     While not FFinished do  
 | |
|       CheckSynchronize(100);
 | |
|   WaitFor := WaitForThreadTerminate(FHandle, 0);
 | |
|   { should actually check for errors in WaitForThreadTerminate, but no }
 | |
|   { error api is defined for that function                             }
 | |
|   FThreadReaped:=true;
 | |
|   WRITE_DEBUG('thread terminated');
 | |
| end;
 | |
| 
 | |
| procedure TThread.CallOnTerminate;
 | |
| begin
 | |
|   // no need to check if FOnTerminate <> nil, because
 | |
|   // thats already done in DoTerminate
 | |
|   FOnTerminate(self);
 | |
| end;
 | |
| 
 | |
| procedure TThread.DoTerminate;
 | |
| begin
 | |
|   if Assigned(FOnTerminate) then
 | |
|     Synchronize(@CallOnTerminate);
 | |
| end;
 | |
| 
 | |
| function TThread.GetPriority: TThreadPriority;
 | |
| var
 | |
|   P: Integer;
 | |
|   I: TThreadPriority;
 | |
| begin
 | |
|   P := ThreadGetPriority(FHandle);
 | |
|   Result := tpNormal;
 | |
|   for I := Low(TThreadPriority) to High(TThreadPriority) do
 | |
|     if Priorities[I] = P then
 | |
|       Result := I;
 | |
| end;
 | |
| 
 | |
| 
 | |
| procedure TThread.SetPriority(Value: TThreadPriority);
 | |
| begin
 | |
|   ThreadSetPriority(FHandle, Priorities[Value]);
 | |
| end;
 |