1 /*
2 Copyright (c) 2015-2021 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license (the "Software") to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12 
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19 
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28 
29 /**
30  * Cross-platform thread class. Supports Windows and Posix
31  *
32  * Description:
33  * This module provides a platform-independent multithreading API.
34  * For now, it supports Windows and Posix threads (pthreads). The interface
35  * is greatly inspired by core.thread from Phobos, but dlib.core.thread
36  * is fully GC-free and can be used with dlib.core.memory allocators.
37  *
38  * Implementation notes:
39  *
40  * - No TLS support, sorry. Any global variables should be marked with
41  *   __gshared for correct access from threads.
42  *
43  * - Internals of Thread class are platform-dependent, so be aware of that
44  *   when inheriting.
45  *
46  * Copyright: Timur Gafarov 2015-2021.
47  * License: $(LINK2 https://boost.org/LICENSE_1_0.txt, Boost License 1.0).
48  * Authors: Timur Gafarov
49  */
50 module dlib.core.thread;
51 
52 version(Windows)
53 {
54     extern(Windows):
55 
56     struct SECURITY_ATTRIBUTES
57     {
58         uint   nLength;
59         void*  lpSecurityDescriptor;
60         int    bInheritHandle;
61     }
62 
63     alias LPTHREAD_START_ROUTINE = extern(Windows) uint function(in void*);
64 
65     void* CreateThread(
66         in SECURITY_ATTRIBUTES*   lpThreadAttributes,
67         in size_t                 dwStackSize,
68         in LPTHREAD_START_ROUTINE lpStartAddress,
69         in void*                  lpParameter,
70         in uint                   dwCreationFlags,
71         uint*                     lpThreadId
72     );
73 
74     uint WaitForMultipleObjects(
75         in uint nCount,
76         in void** lpHandles,
77         in int bWaitAll,
78         in uint dwMilliseconds
79     );
80 
81     int TerminateThread(
82         void* hThread,
83         in uint dwExitCode
84     );
85 
86     int GetExitCodeThread(
87         in void* hThread,
88         uint* lpExitCode
89     );
90 
91     int CloseHandle(
92         in void* hObject
93     );
94 
95     enum INFINITE = uint.max;
96     enum STILL_ACTIVE = 259;
97 }
98 
99 version(Posix)
100 {
101     import core.sys.posix.pthread;
102 }
103 
104 version(Windows)
105 {
106     private extern(Windows) void Sleep(uint msec);
107 }
108 else version(Posix)
109 {
110     import core.stdc.time;
111     import core.sys.posix.time;
112     import core.sys.posix.signal;
113 }
114 
115 /**
116  * Base class for creating threads
117  */
118 class Thread
119 {
120     private void function() func;
121     private void delegate() dlgt;
122     private bool callFunc;
123     private bool initialized = false;
124 
125     version(Windows)
126     {
127         private void* winThread;
128     }
129 
130     version(Posix)
131     {
132         private pthread_t posixThread;
133         private bool running = false;
134     }
135 
136     /// Constructor. Initializes Thread using a function pointer
137     this(void function() func)
138     {
139         this.func = func;
140         callFunc = true;
141     }
142 
143     /// Constructor. Initializes Thread using a delegate
144     this(void delegate() dlgt)
145     {
146         this.dlgt = dlgt;
147         callFunc = false;
148     }
149 
150     /// Destructor
151     ~this()
152     {
153         version(Windows)
154         {
155             if (winThread)
156                 CloseHandle(winThread);
157         }
158 
159         version(Posix)
160         {
161             if (initialized)
162                 pthread_detach(posixThread);
163         }
164     }
165 
166     /// Starts the thread
167     void start()
168     {
169         version(Windows)
170         {
171             if (winThread)
172                 CloseHandle(winThread);
173             uint threadId;
174             winThread = CreateThread(null, cast(size_t)0, &winThreadFunc, cast(void*)this, cast(uint)0, &threadId);
175             assert(winThread !is null);
176             initialized = true;
177         }
178 
179         version(Posix)
180         {
181             running = true;
182             pthread_create(&posixThread, null, &posixThreadFunc, cast(void*)this);
183             // TODO: validate thread creation
184             initialized = true;
185         }
186     }
187 
188     /// Waits for the thread to terminate
189     void join()
190     {
191         version(Windows)
192         {
193             WaitForMultipleObjects(1, &winThread, 1, INFINITE);
194         }
195 
196         version(Posix)
197         {
198             pthread_join(posixThread, null);
199         }
200     }
201 
202     /// Checks if thread is running
203     bool isRunning()
204     {
205         version(Windows)
206         {
207             uint c = 0;
208             GetExitCodeThread(winThread, &c);
209             return (c == STILL_ACTIVE);
210         }
211 
212         version(Posix)
213         {
214             return running;
215         }
216     }
217 
218     /// Stops the thread immediately. This functionality is unsafe, use with care
219     void terminate()
220     {
221         version(Windows)
222         {
223             TerminateThread(winThread, 1);
224         }
225 
226         version(Posix)
227         {
228             pthread_cancel(posixThread);
229             running = false;
230         }
231     }
232 
233     version(Windows)
234     {
235         extern(Windows) static uint winThreadFunc(in void* lpParam)
236         {
237             Thread t = cast(Thread)lpParam;
238             if (t.callFunc)
239                 t.func();
240             else
241                 t.dlgt();
242             return 0;
243         }
244     }
245 
246     version(Posix)
247     {
248         extern(C) static void* posixThreadFunc(void* arg)
249         {
250             Thread t = cast(Thread)arg;
251             if (t.callFunc)
252                 t.func();
253             else
254                 t.dlgt();
255             t.running = false;
256             return null;
257         }
258     }
259 
260     /// Wait for specified amout of milliseconds
261     static void sleep(uint msec)
262     {
263         version(Windows)
264         {
265             Sleep(msec);
266         }
267         else version(Posix)
268         {
269             timespec ts;
270             ts.tv_sec = msec / 1000;
271             ts.tv_nsec = (msec % 1000) * 1000000;
272             nanosleep(&ts, null);
273         }
274     }
275 
276     ///
277     unittest
278     {
279         Thread.sleep(100);
280         assert(true);
281     }
282 }