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
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
"http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML>
<HEAD><TITLE>The OMNI Thread Abstraction</TITLE>
<META http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<META name="GENERATOR" content="hevea 1.06">
</HEAD>
<BODY >
<!--HEVEA command line is: /usr/local/bin/hevea omnithread -->
<!--HTMLHEAD-->
<!--ENDHTML-->
<!--PREFIX <ARG ></ARG>-->
<!--CUT DEF section 1 -->
<H1 ALIGN=center>The OMNI Thread Abstraction</H1>
<H3 ALIGN=center>Tristan Richardson<BR>
AT&T Laboratories Cambridge<BR>
</H3>
<H3 ALIGN=center><I>Revised</I> November 2001</H3>
<!--TOC section Introduction-->
<H2><A NAME="htoc1">1</A> Introduction</H2><!--SEC END -->
The OMNI thread abstraction is designed to provide a common set of
thread operations for use in programs written in C++. Programs
written using the abstraction should be much easier to port between
different architectures with different underlying threads primitives.<BR>
<BR>
The programming interface is designed to be similar to the C language
interface to POSIX threads (IEEE draft standard 1003.1c --- previously
1003.4a, often known as ``pthreads'' [<A HREF="#pthreads"><CITE>POSIX94</CITE></A>]).<BR>
<BR>
Much of the abstraction consists of simple C++ object wrappers around
pthread calls. However for some features such as thread-specific
data, a better interface can be offered because of the use of C++.<BR>
<BR>
Some of the more complex features of pthreads are not supported
because of the difficulty of ensuring the same features can be offered
on top of other thread systems. Such features include thread
cancellation and complex scheduling control (though simple thread
priorities are supported).<BR>
<BR>
The abstraction layer is currently implemented for the following
architectures / thread systems:
<UL><LI>Solaris 2.x using pthreads draft 10
<LI>Solaris 2.x using solaris threads (but pthreads version is now standard)
<LI>Alpha OSF1 using pthreads draft 4
<LI>Windows NT using NT threads
<LI>Linux 2.x using Linuxthread 0.5 (which is based on pthreads draft 10)
<LI>Linux 2.x using MIT pthreads (which is based on draft 8)
<LI>ATMos using pthreads draft 6 (but not Virata ATMos)</UL>
See the <TT>omnithread.h</TT> header file for full details of the API.
The descriptions below assume you have some previous knowledge of
threads, mutexes, condition variables and semaphores. Also refer to
other documentation ([<A HREF="#birrell"><CITE>Birrell89</CITE></A>], [<A HREF="#pthreads"><CITE>POSIX94</CITE></A>]) for further
explanation of these ideas (particularly condition variables, the use
of which may not be particularly intuitive when first encountered).<BR>
<BR>
<!--TOC section Synchronisation objects-->
<H2><A NAME="htoc2">2</A> Synchronisation objects</H2><!--SEC END -->
Synchronisation objects are used to synchronise threads within the
same process. There is no inter-process synchronisation provided.
The synchronisation objects provided are mutexes, condition variables
and counting semaphores.<BR>
<BR>
<!--TOC subsection Mutex-->
<H3><A NAME="htoc3">2.1</A> Mutex</H3><!--SEC END -->
An object of type <TT>omni_mutex</TT> is used for mutual exclusion.
It provides two operations, <TT>lock()</TT> and <TT>unlock()</TT>.
The alternative names <TT>acquire()</TT> and <TT>release()</TT> can be
used if preferred. Behaviour is undefined when a thread attempts to
lock the same mutex again or when a mutex is locked by one thread and
unlocked by a different thread.<BR>
<BR>
<!--TOC subsection Condition Variable-->
<H3><A NAME="htoc4">2.2</A> Condition Variable</H3><!--SEC END -->
A condition variable is represented by an <TT>omni_condition</TT> and
is used for signalling between threads. A call to <TT>wait()</TT>
causes a thread to wait on the condition variable. A call to
<TT>signal()</TT> wakes up at least one thread if any are waiting. A
call to <TT>broadcast()</TT> wakes up all threads waiting on the
condition variable.<BR>
<BR>
When constructed, a pointer to an <TT>omni_mutex</TT> must be given.
A condition variable <TT>wait()</TT> has an implicit mutex
<TT>unlock()</TT> and <TT>lock()</TT> around it. The link between
condition variable and mutex lasts for the lifetime of the condition
variable (unlike pthreads where the link is only for the duration of
the wait). The same mutex may be used with several condition
variables.<BR>
<BR>
A wait with a timeout can be achieved by calling
<TT>timed_wait()</TT>. This is given an absolute time to wait until.
The routine <TT>omni_thread::get_time()</TT> can be used to turn a
relative time into an absolute time. <TT>timed_wait()</TT> returns
<TT>true</TT> if the condition was signalled, <TT>false</TT> if the
time expired before the condition variable was signalled.<BR>
<BR>
<!--TOC subsection Counting semaphores-->
<H3><A NAME="htoc5">2.3</A> Counting semaphores</H3><!--SEC END -->
An <TT>omni_semaphore</TT> is a counting semaphore. When created it
is given an initial unsigned integer value. When <TT>wait()</TT> is
called, the value is decremented if non-zero. If the value is zero
then the thread blocks instead. When <TT>post()</TT> is called, if
any threads are blocked in <TT>wait()</TT>, exactly one thread is
woken. If no threads were blocked then the value of the semaphore is
incremented.<BR>
<BR>
If a thread calls <TT>try_wait()</TT>, then the thread won't block if
the semaphore's value is 0, returning <TT>false</TT> instead.<BR>
<BR>
There is no way of querying the value of the semaphore.<BR>
<BR>
<!--TOC section Thread object-->
<H2><A NAME="htoc6">3</A> Thread object</H2><!--SEC END -->
A thread is represented by an <TT>omni_thread</TT> object. There are
broadly two different ways in which it can be used.<BR>
<BR>
The first way is simply to create an <TT>omni_thread</TT> object,
giving a particular function which the thread should execute. This is
like the POSIX (or any other) C language interface.<BR>
<BR>
The second method of use is to create a new class which inherits from
<TT>omni_thread</TT>. In this case the thread will execute the
<TT>run()</TT> member function of the new class. One advantage of
this scheme is that thread-specific data can be implemented simply by
having data members of the new class.<BR>
<BR>
When constructed a thread is in the "new" state and has not actually
started. A call to <TT>start()</TT> causes the thread to begin
executing. A static member function <TT>create()</TT> is provided to
construct and start a thread in a single call. A thread exits by
calling <TT>exit()</TT> or by returning from the thread function.<BR>
<BR>
Threads can be either detached or undetached. Detached threads are
threads for which all state will be lost upon exit. Other threads
cannot determine when a detached thread will disappear, and therefore
should not attempt to access the thread object unless some explicit
synchronisation with the detached thread guarantees that it still
exists.<BR>
<BR>
Undetached threads are threads for which storage is not reclaimed
until another thread waits for its termination by calling
<TT>join()</TT>. An exit value can be passed from an undetached
thread to the thread which joins it.<BR>
<BR>
Detached / undetached threads are distinguished on creation by the
type of function they execute. Undetached threads execute a function
which has a <TT>void*</TT> return type, whereas detached threads
execute a function which has a <TT>void</TT> return type.
Unfortunately C++ member functions are not allowed to be distinguished
simply by their return type. Thus in the case of a derived class of
<TT>omni_thread</TT> which needs an undetached thread, the member
function executed by the thread is called <TT>run_undetached()</TT>
rather than <TT>run()</TT>, and it is started by calling
<TT>start_undetached()</TT> instead of <TT>start()</TT>.<BR>
<BR>
The abstraction currently supports three priorities of thread, but no
guarantee is made of how this will affect underlying thread
scheduling. The three priorities are <TT>PRIORITY_LOW</TT>,
<TT>PRIORITY_NORMAL</TT> and <TT>PRIORITY_HIGH</TT>. By default all
threads run at <TT>PRIORITY_NORMAL</TT>. A different priority can be
specified on thread creation, or while the thread is running using
<TT>set_priority().</TT> A thread's current priority is returned by
<TT>priority()</TT>.<BR>
<BR>
Other functions provided are <TT>self()</TT> which returns the calling
thread's <TT>omni_thread</TT> object, <TT>yield()</TT> which
requests that other threads be allowed to run, <TT>id()</TT> which
returns an integer id for the thread for use in debugging,
<TT>state()</TT>, <TT>sleep()</TT> and <TT>get_time()</TT>.<BR>
<BR>
<!--TOC section Per-thread data-->
<H2><A NAME="htoc7">4</A> Per-thread data</H2><!--SEC END -->
omnithread supports per-thread data, via member functions of the
<TT>omni_thread</TT> object.<BR>
<BR>
First, you must allocate a key for with the
<TT>omni_thread::allocate_key()</TT> function. Then, any object
whose class is derived from <TT>omni_thread::value_t</TT> can be
stored using the <TT>set_value()</TT> function. Values are retrieved
or removed with <TT>get_value()</TT> and <TT>remove_value()</TT>
respectively.<BR>
<BR>
When the thread exits, all per-thread data is deleted (hence the base
class with virtual destructor).<BR>
<BR>
Note that the per-thread data functions are <B>not</B> thread safe,
so although you can access one thread's storage from another thread,
there is no concurrency control. Unless you really know what you are
doing, it is best to only access per-thread data from the thread it is
attached to.<BR>
<BR>
<!--TOC section Using OMNI threads in your program-->
<H2><A NAME="htoc8">5</A> Using OMNI threads in your program</H2><!--SEC END -->
Obviously you need to include the <TT>omnithread.h</TT> header file in
your source code, and link in the omnithread library with your
executable. Because there is a single <TT>omnithread.h</TT> for all
platforms, certain preprocessor defines must be given as compiler
options. The easiest way to do this is to study the makefiles given
in the examples provided with this distribution. If you are to
include OMNI threads in your own development environment, these are
the necessary preprocessor defines:<BR>
<TABLE BORDER=1 CELLSPACING=0 CELLPADDING=1>
<TR><TD ALIGN=left NOWRAP>Platform</TD>
<TD ALIGN=left NOWRAP>Preprocessor Defines</TD>
</TR>
<TR><TD ALIGN=left NOWRAP>Sun Solaris 2.x</TD>
<TD ALIGN=left NOWRAP><CODE>-D__sunos__ -D__sparc__ -D__OSVERSION__=5</CODE></TD>
</TR>
<TR><TD ALIGN=left NOWRAP> </TD>
<TD ALIGN=left NOWRAP><CODE>-DSVR4 -DUsePthread -D_REENTRANT</CODE></TD>
</TR>
<TR><TD ALIGN=left NOWRAP>x86 Linux 2.0</TD>
<TD ALIGN=left NOWRAP><CODE>-D__linux__ -D__i86__ -D__OSVERSION__=2</CODE></TD>
</TR>
<TR><TD ALIGN=left NOWRAP>with linuxthreads 0.5</TD>
<TD ALIGN=left NOWRAP><CODE>-D_REENTRANT</CODE></TD>
</TR>
<TR><TD ALIGN=left NOWRAP>Digital Unix 3.2</TD>
<TD ALIGN=left NOWRAP><CODE>-D__osf1__ -D__alpha__ -D__OSVERSION__=3</CODE></TD>
</TR>
<TR><TD ALIGN=left NOWRAP> </TD>
<TD ALIGN=left NOWRAP><CODE>-D_REENTRANT</CODE></TD>
</TR>
<TR><TD ALIGN=left NOWRAP>Windows NT</TD>
<TD ALIGN=left NOWRAP><CODE>-D__NT__ -MD</CODE></TD>
</TR></TABLE><BR>
<!--TOC section Threaded I/O shutdown for Unix-->
<H2><A NAME="htoc9">6</A> Threaded I/O shutdown for Unix</H2><!--SEC END -->
or, how one thread should tell another thread to shut down when it
might be doing a blocking call on a socket.<BR>
<BR>
<B>If you are using omniORB, you don't need to worry about all
this, since omniORB does it for you.</B> This section is only relevant
if you are using omnithread in your own socket-based programming. It
is also seriously out of date.<BR>
<BR>
Unfortunately there doesn't seem to be a standard way of doing this
which works across all Unix systems. I have investigated the
behaviour of Solaris 2.5 and Digital Unix 3.2. On Digital Unix
everything is fine, as the obvious method using shutdown() seems to
work OK. Unfortunately on Solaris shutdown can only be used on a
connected socket, so we need devious means to get around this
limitation. The details are summarised below:<BR>
<BR>
<!--TOC subsection read()-->
<H3><A NAME="htoc10">6.1</A> read()</H3><!--SEC END -->
Thread A is in a loop, doing <CODE>read(sock)</CODE>, processing the data,
then going back into the read.<BR>
<BR>
Thread B comes along and wants to shut it down --- it can't cancel
thread A since (i) working out how to clean up according to where A is
in its loop is a nightmare, and (ii) this isn't available in
omnithread anyway.<BR>
<BR>
On Solaris 2.5 and Digital Unix 3.2 the following strategy works:<BR>
<BR>
Thread B does <CODE>shutdown(sock,2)</CODE>.<BR>
<BR>
At this point thread A is either blocked inside <CODE>read(sock)</CODE>, or
is elsewhere in the loop. If the former then read will return 0,
indicating that the socket is closed. If the latter then eventually
thread A will call <CODE>read(sock)</CODE> and then this will return 0.
Thread A should <CODE>close(sock)</CODE>, do any other tidying up, and exit.<BR>
<BR>
If there is another point in the loop that thread A can block then
obviously thread B needs to be aware of this and be able to wake it up
in the appropriate way from that point.<BR>
<BR>
<!--TOC subsection accept()-->
<H3><A NAME="htoc11">6.2</A> accept()</H3><!--SEC END -->
Again thread A is in a loop, this time doing an accept on listenSock,
dealing with a new connection and going back into accept. Thread B
wants to cancel it.<BR>
<BR>
On Digital Unix 3.2 the strategy is identical to that for read:<BR>
<BR>
Thread B does <CODE>shutdown(listenSock,2)</CODE>. Wherever thread A is in
the loop, eventually it will return <CODE>ECONNABORTED</CODE> from the
accept call. It should <CODE>close(listenSock)</CODE>, tidy up as necessary
and exit.<BR>
<BR>
On Solaris 2.5 thread B can't do <CODE>shutdown(listenSock,2)</CODE> ---
this returns <CODE>ENOTCONN</CODE>. Instead the following strategy can be
used:<BR>
<BR>
First thread B sets some sort of "shutdown flag" associated with
listenSock. Then it does <CODE>getsockaddr(listenSock)</CODE> to find out
which port listenSock is on (or knows already), sets up a socket
dummySock, does <CODE>connect(dummySock,</CODE> <CODE>this host, port)</CODE> and
finally does <CODE>close(dummySock)</CODE>.<BR>
<BR>
Wherever thread A is in the loop, eventually it will call
<CODE>accept(listenSock)</CODE>. This will return successfully with a new
socket, say connSock. Thread A then checks to see if the "shutdown
flag" is set. If not, then it's a normal connection. If it is set,
then thread A closes listenSock and connSock, tidies up and exits.<BR>
<BR>
<!--TOC subsection write()-->
<H3><A NAME="htoc12">6.3</A> write()</H3><!--SEC END -->
Thread A may be blocked in write, or about to go in to a
potentially-blocking write. Thread B wants to shut it down.<BR>
<BR>
On Solaris 2.5:<BR>
<BR>
Thread B does <CODE>shutdown(sock,2)</CODE>.<BR>
<BR>
If thread A is already in <CODE>write(sock)</CODE> then it will return with
<CODE>ENXIO</CODE>. If thread A calls write after thread B calls shutdown
this will return <CODE>EIO</CODE>.<BR>
<BR>
On Digital Unix 3.2:<BR>
<BR>
Thread B does <CODE>shutdown(sock,2)</CODE>.<BR>
<BR>
If thread A is already in <CODE>write(sock)</CODE> then it will return the
number of bytes written before it became blocked. A subsequent call
to write will then generate <CODE>SIGPIPE</CODE> (or <CODE>EPIPE</CODE> will be
returned if <CODE>SIGPIPE</CODE> is ignored by the thread).<BR>
<BR>
<!--TOC subsection connect()-->
<H3><A NAME="htoc13">6.4</A> connect()</H3><!--SEC END -->
Thread A may be blocked in connect, or about to go in to a
potentially-blocking connect. Thread B wants to shut it down.<BR>
<BR>
On Digital Unix 3.2:<BR>
<BR>
Thread B does <CODE>shutdown(sock,2)</CODE>.<BR>
<BR>
If thread A is already in <CODE>connect(sock)</CODE> then it will return a
successful connection. Subsequent reading or writing will show that
the socket has been shut down (i.e. read returns 0, write generates
<CODE>SIGPIPE</CODE> or returns <CODE>EPIPE</CODE>). If thread A calls connect
after thread B calls shutdown this will return <CODE>EINVAL</CODE>.<BR>
<BR>
On Solaris 2.5:<BR>
<BR>
There is no way to wake up a thread which is blocked in connect.
Instead Solaris forces us through a ridiculous procedure whichever way
we try it. One way is this:<BR>
<BR>
First thread A creates a pipe in addition to the socket. Instead of
shutting down the socket, thread B simply writes a byte to the pipe.<BR>
<BR>
Thread A meanwhile sets the socket to non-blocking mode using
<CODE>fcntl(sock,</CODE> <CODE>F_SETFL, O_NONBLOCK)</CODE>. Then it calls connect
on the socket --- this will return <CODE>EINPROGRESS</CODE>. Then it must
call <CODE>select()</CODE>, waiting for either sock to become writable or
for the pipe to become readable. If select returns that just sock is
writable then the connection has succeeded. It then needs to set the
socket back to blocking mode using <CODE>fcntl(sock, F_SETFL, 0)</CODE>. If
instead select returns that the pipe is readable, thread A closes the
socket, tidies up and exits.<BR>
<BR>
An alternative method is similar but to use polling instead of the
pipe. Thread B justs sets a flag and thread A calls select with a
timeout, periodically waking up to see if the flag has been set.<BR>
<BR>
<!--TOC section References-->
<H2>References</H2><!--SEC END -->
<DL COMPACT=compact><DT><A NAME="pthreads"><FONT COLOR=purple>[POSIX94]</FONT></A><DD>
<EM>Portable Operating System Interface (POSIX) Threads Extension</EM>,
P1003.1c Draft 10,
IEEE,
September 1994.<BR>
<BR>
<DT><A NAME="birrell"><FONT COLOR=purple>[Birrell89]</FONT></A><DD>
<EM>An Introduction to Programming with Threads</EM>,
Research Report 35,
DEC Systems Research Center,
Palo Alto, CA,
January 1989.</DL>
<!--HTMLFOOT-->
<!--ENDHTML-->
<!--FOOTER-->
<HR SIZE=2>
<BLOCKQUOTE><EM>This document was translated from L<sup>A</sup>T<sub>E</sub>X by
</EM><A HREF="http://pauillac.inria.fr/~maranget/hevea/index.html"><EM>H<FONT SIZE=2><sup>E</sup></FONT>V<FONT SIZE=2><sup>E</sup></FONT>A</EM></A><EM>.
</EM></BLOCKQUOTE>
</BODY>
</HTML>
|