Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

400 linhas
9.2 KiB

  1. unit uutlThreads;
  2. { Package: Utils
  3. Prefix: utl - UTiLs
  4. Beschreibung: diese Unit implementiert Hilfsklassen für multithreaded Anwendungen }
  5. {$mode objfpc}{$H+}
  6. {$modeswitch nestedprocvars}
  7. interface
  8. uses
  9. Classes, SysUtils, syncobjs, uutlGenerics;
  10. type
  11. TAutoResetEvent = class(TEvent)
  12. public
  13. constructor Create(aInitial: boolean = false);
  14. end;
  15. // aliased to stay in LCL naming scheme for TSimpleEvent
  16. TutlAutoResetEvent = TAutoResetEvent;
  17. TutlSpinLock = class
  18. private
  19. fLock: DWord;
  20. fLockReused: integer;
  21. public
  22. constructor Create;
  23. destructor Destroy; override;
  24. procedure Enter;
  25. procedure Leave;
  26. end;
  27. IutlThreadPoolRunnable = interface
  28. ['{DFEC1832-30DA-4D12-A321-7762057D2D17}']
  29. function Execute: PtrUInt;
  30. end;
  31. IutlThreadPoolWaitable = interface
  32. ['{8FEB8A6B-B659-4A48-929F-650CE7455BC7}']
  33. function WaitFor: TWaitResult;
  34. function TaskResult: PtrUInt;
  35. end;
  36. TAsyncFuncNested = function : PtrUInt is nested;
  37. TAsyncFuncParamNested = function (Param: PtrUInt) : PtrUInt is nested;
  38. TutlThreadPool = class
  39. private type
  40. TQueueItem = class(TInterfacedObject, IutlThreadPoolWaitable)
  41. private
  42. fTask: IutlThreadPoolRunnable;
  43. fDoneEvent: TEvent;
  44. fResult: PtrUInt;
  45. public
  46. constructor Create(const aTask: IutlThreadPoolRunnable);
  47. destructor Destroy; override;
  48. procedure Execute;
  49. procedure Cancel;
  50. function WaitFor: TWaitResult;
  51. function TaskResult: PtrUInt;
  52. end;
  53. TRunnableFuncNested = class(TInterfacedObject, IutlThreadPoolRunnable)
  54. private
  55. fProc: TAsyncFuncNested;
  56. public
  57. constructor Create(Proc: TAsyncFuncNested);
  58. function Execute: PtrUInt;
  59. end;
  60. TRunnableFuncParamNested = class(TInterfacedObject, IutlThreadPoolRunnable)
  61. private
  62. fProc: TAsyncFuncParamNested;
  63. fParam: PtrUInt;
  64. public
  65. constructor Create(Proc: TAsyncFuncParamNested; Param: PtrUInt);
  66. function Execute: PtrUInt;
  67. end;
  68. TWorker = class(TThread)
  69. private
  70. fOwner: TutlThreadPool;
  71. public
  72. constructor Create(Owner: TutlThreadPool);
  73. destructor Destroy; override;
  74. procedure Execute; override;
  75. end;
  76. TWorkerThreadList = specialize TutlList<TThread>;
  77. TWorkItemList = specialize TutlList<TQueueItem>;
  78. private
  79. fTerminating: Boolean;
  80. fThreads: TWorkerThreadList;
  81. fThreadMgmtSection: TCriticalSection;
  82. fQueue: TWorkItemList;
  83. fQueueSection: TutlSpinLock;
  84. fNewItemEvent: TEvent;
  85. procedure SetMaxThreads(AValue: integer);
  86. procedure Shutdown;
  87. function GetMaxThreads: integer;
  88. protected
  89. function FetchWork: TQueueItem;
  90. procedure ThreadWaitForEvent;
  91. public
  92. constructor Create;
  93. destructor Destroy; override;
  94. // TODO: attention: if tasks queue more tasks and then wait for them, recursing more than MaxThreads levels is a deadlock ATM. This will be fixed later
  95. property MaxThreads: integer read GetMaxThreads write SetMaxThreads;
  96. function Queue(const Task: IutlThreadPoolRunnable): IutlThreadPoolWaitable; overload;
  97. function Queue(const Task: TAsyncFuncNested): IutlThreadPoolWaitable;
  98. function Queue(const Task: TAsyncFuncParamNested; const Param: PtrUInt): IutlThreadPoolWaitable;
  99. end;
  100. implementation
  101. { TAutoResetEvent }
  102. constructor TAutoResetEvent.Create(aInitial: boolean);
  103. begin
  104. inherited Create(Nil, false, aInitial, '');
  105. end;
  106. { TutlSpinLock }
  107. constructor TutlSpinLock.Create;
  108. begin
  109. inherited Create;
  110. fLock:= 0;
  111. fLockReused:= 0;
  112. end;
  113. destructor TutlSpinLock.Destroy;
  114. begin
  115. Enter;
  116. inherited Destroy;
  117. end;
  118. procedure TutlSpinLock.Enter;
  119. var
  120. ti: dword;
  121. begin
  122. ti:= ThreadID;
  123. if ti = InterlockedCompareExchange(fLock, ti, ti) then begin
  124. {
  125. The lock is already held by this thread. This means it cannot be modified by a concurrent
  126. operation (assuming Enter/Leave bracket correctly), and we can act non-atomar on other variables.
  127. }
  128. inc(fLockReused);
  129. end else begin
  130. while InterlockedCompareExchange(fLock, ti, 0) <> 0 do ;
  131. end;
  132. end;
  133. procedure TutlSpinLock.Leave;
  134. var
  135. ti: DWord;
  136. begin
  137. ti:= ThreadID;
  138. // Unlock only if we hold the lock
  139. if ti = InterlockedCompareExchange(fLock, ti, ti) then begin
  140. // our lock, but we haven't yet done anything (note the above is essentially a threadsafe CMP if successful)
  141. if fLockReused = 0 then
  142. InterLockedExchange(fLock, 0) // normal lock
  143. else
  144. dec(fLockReused); // nested locks
  145. end;
  146. end;
  147. { TutlThreadPool }
  148. constructor TutlThreadPool.Create;
  149. begin
  150. inherited Create;
  151. fTerminating:= false;
  152. fQueue:= TWorkItemList.Create(False);
  153. fQueueSection:= TutlSpinLock.Create;
  154. fThreads:= TWorkerThreadList.Create(true);
  155. fThreadMgmtSection:= TCriticalSection.Create;
  156. fNewItemEvent:= TEvent.Create(nil, false, true, '');
  157. MaxThreads:= 2;
  158. end;
  159. destructor TutlThreadPool.Destroy;
  160. begin
  161. Shutdown;
  162. FreeAndNil(fNewItemEvent);
  163. FreeAndNil(fThreads);
  164. FreeAndNil(fThreadMgmtSection);
  165. FreeAndNil(fQueue);
  166. FreeAndNil(fQueueSection);
  167. inherited Destroy;
  168. end;
  169. function TutlThreadPool.GetMaxThreads: integer;
  170. begin
  171. Result:= fThreads.Count;
  172. end;
  173. procedure TutlThreadPool.SetMaxThreads(AValue: integer);
  174. var
  175. i: integer;
  176. begin
  177. fThreadMgmtSection.Enter;
  178. try
  179. if MaxThreads=AValue then Exit;
  180. if AValue < MaxThreads then begin
  181. for i:= MaxThreads - 1 downto AValue do begin
  182. fThreads.Delete(i); // frees the item, which causes the thread to Terminate, Waitfor and Destroy
  183. end;
  184. end else begin
  185. for i:= MaxThreads to AValue - 1 do begin
  186. fThreads.Add(TWorker.Create(Self));
  187. end;
  188. end;
  189. finally
  190. fThreadMgmtSection.Leave;
  191. end;
  192. end;
  193. procedure TutlThreadPool.Shutdown;
  194. var
  195. i: integer;
  196. begin
  197. fTerminating:= true;
  198. // kill all threads
  199. fThreadMgmtSection.Enter;
  200. try
  201. for i:= 0 to fThreads.Count - 1 do
  202. fThreads[i].Terminate;
  203. fNewItemEvent.SetEvent;
  204. for i:= 0 to fThreads.Count - 1 do
  205. fThreads[i].WaitFor;
  206. fThreads.Clear;
  207. finally
  208. fThreadMgmtSection.Leave;
  209. end;
  210. // kill all remaining tasks and notifiy Waitables
  211. fQueueSection.Enter;
  212. try
  213. for i:= 0 to fQueue.Count - 1 do begin
  214. fQueue[i].Cancel;
  215. fQueue[i]._Release;
  216. end;
  217. fQueue.Clear;
  218. finally
  219. fQueueSection.Leave;
  220. end;
  221. end;
  222. function TutlThreadPool.Queue(const Task: IutlThreadPoolRunnable): IutlThreadPoolWaitable;
  223. var
  224. itm: TQueueItem;
  225. begin
  226. if fTerminating then
  227. Exit(nil);
  228. itm:= TQueueItem.Create(Task);
  229. Result:= itm;
  230. fQueueSection.Enter;
  231. try
  232. itm._AddRef;
  233. fQueue.Add(itm);
  234. fNewItemEvent.SetEvent;
  235. finally
  236. fQueueSection.Leave;
  237. end;
  238. end;
  239. function TutlThreadPool.FetchWork:TQueueItem;
  240. begin
  241. if fTerminating then
  242. Exit(nil);
  243. fQueueSection.Enter;
  244. try
  245. if fQueue.Count > 0 then begin
  246. Result:= fQueue[0];
  247. fQueue.Delete(0);
  248. end
  249. else
  250. Result:= nil;
  251. finally
  252. fQueueSection.Leave;
  253. end;
  254. end;
  255. procedure TutlThreadPool.ThreadWaitForEvent;
  256. begin
  257. fNewItemEvent.WaitFor(INFINITE);
  258. if fTerminating then // this one will soon leave .Execute, wake all others
  259. fNewItemEvent.SetEvent;
  260. end;
  261. function TutlThreadPool.Queue(const Task: TAsyncFuncNested): IutlThreadPoolWaitable;
  262. begin
  263. Result:= Queue(TRunnableFuncNested.Create(Task));
  264. end;
  265. function TutlThreadPool.Queue(const Task: TAsyncFuncParamNested; const Param: PtrUInt): IutlThreadPoolWaitable;
  266. begin
  267. Result:= Queue(TRunnableFuncParamNested.Create(Task, Param));
  268. end;
  269. { TutlThreadPool.TWorker }
  270. constructor TutlThreadPool.TWorker.Create(Owner: TutlThreadPool);
  271. begin
  272. inherited Create(false);
  273. fOwner:= Owner;
  274. end;
  275. destructor TutlThreadPool.TWorker.Destroy;
  276. begin
  277. Terminate;
  278. WaitFor;
  279. inherited Destroy;
  280. end;
  281. procedure TutlThreadPool.TWorker.Execute;
  282. var
  283. qi: TQueueItem;
  284. begin
  285. while not Terminated do begin
  286. qi:= fOwner.FetchWork;
  287. if Assigned(qi) then begin
  288. qi.Execute;
  289. qi._Release;
  290. end else
  291. fOwner.ThreadWaitForEvent;
  292. end;
  293. end;
  294. { TutlThreadPool.TQueueItem }
  295. constructor TutlThreadPool.TQueueItem.Create(const aTask: IutlThreadPoolRunnable);
  296. begin
  297. inherited Create;
  298. fDoneEvent:= TEvent.Create(nil, true, false, '');
  299. fTask:= aTask;
  300. end;
  301. destructor TutlThreadPool.TQueueItem.Destroy;
  302. begin
  303. FreeAndNil(fDoneEvent);
  304. inherited Destroy;
  305. end;
  306. function TutlThreadPool.TQueueItem.WaitFor: TWaitResult;
  307. begin
  308. Result:= fDoneEvent.WaitFor(INFINITE);
  309. end;
  310. procedure TutlThreadPool.TQueueItem.Execute;
  311. begin
  312. fResult:= fTask.Execute;
  313. fDoneEvent.SetEvent;
  314. end;
  315. function TutlThreadPool.TQueueItem.TaskResult: PtrUInt;
  316. begin
  317. Result:= fResult;
  318. end;
  319. procedure TutlThreadPool.TQueueItem.Cancel;
  320. begin
  321. fResult:= 0;
  322. fDoneEvent.SetEvent;
  323. end;
  324. { TutlThreadPool.TRunnableFuncNested }
  325. constructor TutlThreadPool.TRunnableFuncNested.Create(Proc: TAsyncFuncNested);
  326. begin
  327. inherited Create;
  328. fProc:= Proc;
  329. end;
  330. function TutlThreadPool.TRunnableFuncNested.Execute: PtrUInt;
  331. begin
  332. Result:= fProc();
  333. end;
  334. { TutlThreadPool.TRunnableFuncParamNested }
  335. constructor TutlThreadPool.TRunnableFuncParamNested.Create(
  336. Proc: TAsyncFuncParamNested; Param: PtrUInt);
  337. begin
  338. inherited Create;
  339. fProc:= Proc;
  340. fParam:= Param;
  341. end;
  342. function TutlThreadPool.TRunnableFuncParamNested.Execute: PtrUInt;
  343. begin
  344. Result:= fProc(fParam);
  345. end;
  346. end.