/
threads.txt
196 lines (154 loc) · 9.42 KB
/
threads.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
Notes on using BASIClib's Thread support:
BASIClib is (hopefully) threadsafe, and has an abstraction layer for
creating and managing threads. Here's all you need to know:
- For reference, the thread abstraction layer is defined in
BASIClib/Threads.c ... The current implementation uses POSIX threads, but
there's not necessarily a need to concern yourself with the underlying
library. As long as a threads package can fulfill the rather undemanding
needs of BASIClib's API, it can be fitted as the backend. POSIX just seems
like the best way to go, but BASIClib as a whole is not at all chained to it.
- There're NO POSIX threads on Win95. Cygnus is working on this, but god
knows how long it will take them to write a POSIX compliant pthreads
package AND make all their libraries thread-safe. WinNT has some DCE
threads libraries (older draft of POSIX standard) bundled with the OS,
but this is not 100% compatible with Linux (read: POSIX standard) threads.
This isn't a problem, necessarily. We can use the Win32 API directly, as
the vbSlacker Threads subsystem is pretty abstract, but we need a
thread-safe C library. Apparently, the latest code shapshots of cygwin32
have the needed support, but after months of existance, STILL have not
made it into an official beta release. Microsoft Visual C++ has a thread-safe
library, but no POSIX support. I might write POSIX wrappers for Win32 threads
someday...
- You should #include "StdBasic.h" in any modules that use threads in
anyway. Do not directly include Threads.h, as it expects to be included
from StdBasic.h ... #including "BasicLib.h" will also suffice. Do not
include both.
- For single-threaded programs and modules, you may define SINGLE_THREADED
ON THE COMMAND LINE when compiling your code. This replaces all Thread API
function calls with do-nothing "++ safe" macros. This allows you to use
identical (but more efficient) code for both single-threaded and multi-
threaded versions of your program. (For example, __getCurrentThreadIndex
is replaced with (0) (and no function call) when SINGLE_THREADED is
defined, since you'd always be dealing with thread index (0) anyhow.)
- You need to, even for single-threaded code, define _REENTRANT.
gcc -D_REENTRANT MyCode.c
_REENTRANT changes the behavior of the C library headers to be thread safe.
(Most notably, (errno) works in a downright wrong way if you don't use this.)
YOU SHOULD DEFINE _REENTRANT EVEN IF YOU DON'T DIRECTLY USE THREADS.
Threads could be calling your functions, even if you don't specifically
create a thread do so. Including StdBasic.h and not defining _REENTRANT
will cause an error, just as a friendly reminder. You are still on your
own for thread-proofing your code, and should do so, even if in single
threaded mode. (Since the performance hit is so infintestimal for the
single-thread macros, but it will be ready for multithreaded apps.)
- Spin a thread like this:
void myThreadEntryPoint(void *_args)
{
MyDataType *args = (MyDataType *) _args;
/* do something. */
} /* myThreadEntryPoint */
void main(void)
{
/*
* The NULL can be a pointer to anything you like. It will
* be passed to myThreadEntryPoint() as the only argument...
*/
__spinThread(myThreadEntryPoint, NULL);
} /* main */
- Some thread libraries (like POSIX threads) allow for threads to be "joined",
that is, terminating threads can return a value. This threads package
does not allow for that (all threads run "detached"), but there are a
thousand workarounds for this, and you'll probably never need a single one.
- Every thread has an index number (a "tidx," or "Thread Index"). The first
thread, the one that runs the mainline, is index (0). The next thread spun
is index (1). As threads are destroyed, their index numbers are recycled
for new threads that are spun. You can find out the currently executing
thread by calling __getCurrentThreadIndex(). It will return the index of
the thread that called the function.
- Threads are destroyed automatically when they return. You can have one
destroy itself by calling __terminateCurrentThread(). You can kill other
threads, if you know its thread index, by calling __terminateThread(tidx)...
calling __terminateThread() with the current thread's index will cause
BASIClib to call __terminateCurrentThread() for you. It's that smart. :)
- Behavior is ALWAYS undefined if you try to manipulate threads with any
functions other than BASIClib's threads API. Don't assume that the
underlying code is a POSIX implementation, or even the WIN32 API, even
if you are running under Windows...maybe we got witty and created
a "green threads" package that runs multiple threads on one.
- Most (if not all) of your thread-bug problems will come from global and
static variables. Expect to be thinking about how to protect that data.
Remember that even thread-proofed data in a dynamically-allocated array
isn't safe if you plan to reallocate that array at any point.
- Just about every other thread-bug comes from i/o to the same device/file
simultaneously from two different threads. These are sometimes even harder
to track, because crashes/unexpected behavior tend to occur inside the C
library, which you probably aren't willing to trace into. :)
- Almost every thread-bug can be prevented by correct use of ThreadLocks.
Keep reading.
- There is no EnterCriticalThreadSection() API in the POSIX standard. Use
BASIClib's ThreadLocks instead. This is an abstraction layer over POSIX
mutexes. Like so:
/* (at top of module:) */
#include "StdBasic.h"
ThreadLock myThreadLock;
/* (in init code for module:) */
__createThreadLock(&myThreadLock);
/* (Whenever you need to protect data from race conditions...) */
__obtainThreadLock(&myThreadLock);
/* Mess with data... */
__releaseThreadLock(&myThreadLock);
/* (in deinit code for module:) */
__destroyThreadLock(&myThreadLock);
Try to group sensitive data by ThreadLocks, so we don't hog too much
memory/processor time with a ThreadLock for every bit of data, but
there are enough locks in the right places so there aren't any massive
slowdowns while threads wait unnecessarily to obtain a lock. Use common
sense. Too many locks == slow. Too few locks == slower, but more idling.
No locks == inexplicable crashes.
- If you have to wait for a thread to terminate for any reason, you can call
__waitForThreadToDie(tidx), and the calling thread will block until
the thread with index (tidx) terminates, either by returning or suicide or
another thread terminating it. Be careful whenever blocking is involved,
like this. Two threads waiting for each other to die will be forever
deadlocked.
- If your thread is looping for any amount of time, it might be wise to
place a call to __timesliceThread in your loop. (Note this is a macro,
so don't use "()"...) This call surrenders control of the processor so
other threads and processes can use it. Eventually control will return
to the caller. VERY handy when waiting endlessly for user input, or
calculating a jazillion places of Pi.
- For accounting purposes, you can call __getThreadCount, (a macro) which
returns a count of all threads in this process...although this isn't as
useful as __getHighestThreadIndex, (a macro) which returns the highest
thread index in use. This is good for allocating arrays that keep one
element for each thread, since such arrays are inherently thread-safe
(as long as we use ThreadLocks when reallocating the array.)
- To be notified when a thread is created, add a function to your module
called (for example) "void __initThreadMyModule(int tidx)", and add a
call to it inside of __initThread(int tidx) (found in Initialize.c).
This new function will be called whenever a new thread is created, with
tidx set to the thread index of the new thread. This will allow you to
update thread-separated arrays and such. Be careful about reallocating
arrays, since this function can be called at any time, so any access to
those array elements will need to be protected by ThreadLocks.
The newly created thread will be the one to call __initThread().
- The same rules apply for thread termination...add your notification
function calls to __deinitThread(int tidx) in Initialize.c ... The
thread that did the terminating (the one who called __terminateThread()
or __terminateCurrentThread()) will be the one to call __deinitThread().
- Be careful about obtaining thread locks and error handling; If you own
a thread lock, and there's a possibility of a runtime error being thrown,
register an OnError handler that releases possession of the lock,
and rethrows the error. Other protections include making sure you don't
call any routines that could possibly throw (standard C library routines
never do so, any BASIClib function may), or registering an OnError
handler that fixes problems, and executes a RESUME of some sort. If you
don't take measures, other threads will block inexplicably and
probably permenently if they try to obtain the lock that your error-throwing
thread has possession of.
- The best idea is just to avoid global and static data wherever possible.
Remember that every thread has it's own stack, and data stored on it will
be separate from other functions...therefore local variables and arguments
are thread-proofed already. This doesn't solve EVERY problem, but keep it
in mind.
/* end of threads.txt ... */