The TriTechnologies Company 11876 N.E. 39th St Bellevue, WA 98005 (206) 957-1579 Compuserve: 70272, 3671 THE VERSION OF PCTHREADS NOW IN YOUR POSSESSION IS THE INITIAL RELEASE OF PCTHREADS(tm). PCTHREADS VERSION 1.0 IS INTENDED TO PROVIDE MULTITHREAD SERVICES THAT ARE COMPLIANT WITH THAT PROPOSED BY THE POSIX 1003.4A DRAFT 4 STANDARD. 1) We want as many users as possible to test and evaluate the product and feed back to us their comments. Especially bug reports and desires for additional functionality and/or extensions. 2) We want to encourage programmers to learn about multiple threads of execution by providing a quality, high performance threads package on a relatively inexpensive development platform, MSDOS. This release is FREE. You incur no obligation by your accepting and using PCthreads V1.0 apart from those included in your license agreement (see LICENSE). RESTRICTIONS The following restrictions apply: - This package is supported on MSDOS version 3.3 and later. - Requires 286, 386, or 486 processor. - Borland BC V2.0 or Microsoft C V6.0 - Small memory model only Documentation is not provided with the PCthreads software, but must be ordered separately. We strongly recommend you invest $30.00 in the documentation. It is of very high quality and consists of a 60 page detailed introduction to developing multithreaded programs combined with a 120 page formal description of each function call, including appendices and a source disk with examples and other auxiliary files (make files for Microsoft's NMAKE and Borland's MAKE utilities). Of course, the manual is laser printed and nicely bound. If you have a printer you're comfortable with, we can also send you an On-Disk version for $20.00. By the way, another source of documentation is the IEEE. You can write to them and request the POSIX 1003.4a draft 4 document. This is the functional specification to which we designed PCthreads and is the final word. It's very well written and should serve you very well. It may even be free! HINTS AND KINKS If you've never programmed with threads before, read this file and then examine the two example applications. Pay special attention to how arguments are passed to the thread procedure in the pcthread_create() function and how data is returned from the child threads to the waiting parent. Also, if you're really a glutton for this kind of thing, study the source code for the exception handling example along with the macro preprocessor source code that implements the exception handling interface. We really would appreciate your buying the manual, though [:-) O.K., here we go: In our experience most bugs not related to the C-language will involve the non-intuitive and unfamiliar nature of asynchronous execution of multiple threads. In the next few sections below, we touch briefly on memory allocation, reentrancy, error handling, and so forth. 1) MEMORY ALLOCATION You can get into trouble real quick if you do not fully understand how memory is allocated. To minimize errors due to faulty memory allocation you should refresh your understanding of the what it means to "allocate memory on the stack"? Said another way, know what local and/or automatic storage means and the problems you can run into when you forget about the duration of local variables. PCthreads implements a "handle-based" API. One of the implications of this is that it is the programmer's responsibility to allocate memory for a handle to a thread, mutex, condition variable, etc. As such, be very careful about how you allocate memory for object handles. We recommend declaring handles (i.e., for thread, mutex, attribute, and condition variable objects) with persistant (i.e., static or global) duration. For example, if you allocate a handle on a procedure's stack be aware that when the procedure completes the handle will no longer be guaranteed to be valid. For example, the following code fragment illustrates how NOT to allocate a handle to a thread object: #include "pcthreads.h" proc() { /* pcthread_t th_handle; * Local and only exists until proc() * completes. */ pcthread_create( &th_handle, ... ); ... } The problem illustrated in this example is the declaration of th_handle. Specifically, the compiler will allocate memory for th_handle on my_proc()'s stack. When my_proc() terminates th_handle may no longer be available. A subsequent reference to th_handle will likely cause disaster. If your code breaks with a "divide by zero", or "null pointer assignment" look for an object handle that's been declared innapropriately. 2) REENTRANCY Most PC-based standard libraries (including the Floating Point Emulator and processor services from Microsoft and Borland) are not reentrant, or "thread-safe". This means that a second thread MAY NOT ENTER a library routine if the first thread has not yet returned. How does this happen? In a multithreaded application thread 1 could call foo() but before it is able to complete gets preempted (e.g., by a higher priority thread). In the absence of techniques to prevent this, the preempting thread could also call (i.e., reenter) foo(). If foo() does not read or write global and/or static variables, then there is no problem. Otherwise, the routine (foo) is non-reentrant and access to the static/global variables must be synchronized. Most PC-based standard library routines depend heavily on library-wide global variables and so are NOT reentrant. In general, unless the vendor's documentation says otherwise, you should assume that your standard libraries are not reentrant. To this end the following policy should be adopted: You should explicitly disable clock-mediated preemption prior to calling a [suspected] non-reentrant function. Take care to remember to enable clock-mediated preemption upon return from the service. Example 2: #include "pcthread.h" main() { foo_t *ptr; int ctx; ... ctx = pcthread_disable_preemption_np(); ptr = (foo_t *) malloc( sizeof( foo_t )); pcthread_enable_preemption_np( ctx ); ... } As stated earlier, you should assume that most standard library routines are not reentrant. This includes Floating Point services, as well. Always bracket floating point operations with disable/enable preemption calls. 3) RUNTIME ERRORS AND RESTORATION OF THE SYSTEM'S INTERRUPT HANDLERS. PCthreads will detect most runtime errors and exit gracefully with an appropriate error/exception message. However, if the code breaks for some reason that the exception mechanism doesn't detect, you should reboot the system. This sounds frightening, but it's not as bad as it sounds. Basically, PCthreads revectors a number of hardware and software interrupts and, when the system terminates, the interrupt handlers installed by PCthreads are removed and the original ones restored. When PCthreads detects a condition from which it can not recover (e.g., a thread stack overflow or PCthreads runs out of memory) it invokes an exception handling mechanism that cleans up the system and returns to MSDOS. You should do the same! Also, use the pcthread_checkstack_np(...) and the pcthread_shutdown_np(...) service religiously. They are not that expensive and will save you lots of grief during development. 4) STACK MANAGEMENT Several of the C-Runtime library services require lots of stack space. This being the case, be very aware of how much stack space you allocate to your threads. In particular any thread that calls one of the "printf()" family of services should have at least 256 words of stack space. PCthreads assigns 512 words of stack space to each thread as its default. NOTE: If you enable the execution trace facility, when your application terminates, the exiting thread will dump the trace to a file called "trace.dat". If you intend to trace all, or any portion of your application, make sure that the initial thread (the thread executing the main() procedure) has sufficient stack space to write this file. If you use Microsoft's C-language compiler, remember that the overall, or application's stack length (from which the individual thread stacks are allocated) must be specified at compile and/or link time. Borland users must set the application's stack length via the _stkln global variable. 5) EXCEPTION AND ERROR HANDLING Note in the PCthread.h header file that all pcthreads services either return a status value or return void. The POSIX standard requires that all services that detect an error of some kind return a value of -1, with the value of errno set to an integer constant that specifies the cause of the error. For PCthreads services that return void, if the service detects an error condition it will gracefully shutdown/terminate the application. Of course, the pcthread_malloc_np() service returns a NULL value if no more memory is available, not a -1 condition. The PCthreads Package offers an exception handling interface that you can build into your multithreaded applications. PCthreads itself, uses the exception handling mechanism internally, but since POSIX specifically requires status values to be returned instead of exceptions to be raised, the PCthreads package does not raise exceptions to client applications. Use the exception handling interface in your applications. Here's why exceptions are important: Exceptions can not be ignored. Return status values can be ignored. If the programmer [inadvertantly] ignores an exception the application is terminated -- No ifs, ands, or buts! Such being the case, PCthreads provides a macro preprocessor based exception handling facility that let's you CATCH and handle exceptions nested arbitrarily deep in a thread's procedure stack. A close examination of the source code for these important macros can prove enlightening (see except.h) Copyright (C) 1991, The TriTechnologies Company. All rights reserved PCthreads(tm) is a registered trademark of The TriTechnologies Company UNIX(tm) is a trademark of AT&T somewhere in New Jersey. MS-DOS(tm) is a trademark of Microsoft Corporation, Redmond, WA.