home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: 10 Tools
/
10-Tools.zip
/
xwphescr.zip
/
XWPH0208.ZIP
/
src
/
helpers
/
threads.c
< prev
next >
Wrap
C/C++ Source or Header
|
2002-08-08
|
23KB
|
691 lines
/*
*@@sourcefile threads.c:
* contains helper functions for creating, destroying, and
* synchronizing threads, including PM threads with a
* message queue which is created automatically.
*
* See thrCreate() for how to start such a thread.
*
* Usage: All OS/2 programs.
*
* Function prefixes (new with V0.81):
* -- thr* Thread helper functions
*
* This file is new with V0.81 and contains all the thread
* functions that used to be in helpers.c.
*
* Note: Version numbering in this file relates to XWorkplace version
* numbering.
*
*@@header "helpers\threads.h"
*/
/*
* Copyright (C) 1997-2002 Ulrich Möller.
* This file is part of the "XWorkplace helpers" source package.
* This is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published
* by the Free Software Foundation, in version 2 as it comes in the
* "COPYING" file of the XWorkplace main distribution.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#define OS2EMX_PLAIN_CHAR
// this is needed for "os2emx.h"; if this is defined,
// emx will define PSZ as _signed_ char, otherwise
// as unsigned char
#define INCL_WINMESSAGEMGR
#define INCL_DOSPROCESS
#define INCL_DOSSEMAPHORES
#define INCL_DOSERRORS
#include <os2.h>
#include <string.h>
#include <stdlib.h>
#include "setup.h" // code generation and debugging options
#include "helpers\linklist.h"
#include "helpers\threads.h"
#pragma hdrstop
/*
*@@category: Helpers\Control program helpers\Thread management
* see threads.c.
*/
/* ******************************************************************
*
* Global variables
*
********************************************************************/
LINKLIST G_llThreadInfos;
// linked list of all THREADINFOS ever created...
// no auto-free
HMTX G_hmtxThreadInfos = NULLHANDLE;
/* ******************************************************************
*
* Functions
*
********************************************************************/
/*
*@@ LockThreadInfos:
*
*@@added V0.9.9 (2001-03-07) [umoeller]
*/
static BOOL LockThreadInfos(VOID)
{
APIRET arc = NO_ERROR;
if (G_hmtxThreadInfos == NULLHANDLE)
{
// first call:
arc = DosCreateMutexSem(NULL, // unnamed
&G_hmtxThreadInfos,
0, // unshared
TRUE); // request!
lstInit(&G_llThreadInfos, FALSE);
}
else
arc = DosRequestMutexSem(G_hmtxThreadInfos,
SEM_INDEFINITE_WAIT);
return (arc == NO_ERROR);
}
/*
*@@ UnlockThreadInfos:
*
*@@added V0.9.9 (2001-03-07) [umoeller]
*/
static VOID UnlockThreadInfos(VOID)
{
DosReleaseMutexSem(G_hmtxThreadInfos);
}
/*
*@@ thr_fntGeneric:
* generic thread function used by thrCreate.
* This in turn calls the actual thread function
* specified with thrCreate.
*
*@@added V0.9.2 (2000-03-05) [umoeller]
*@@changed V0.9.7 (2000-12-18) [lafaix]: THRF_TRANSIENT support added
*/
static VOID _Optlink thr_fntGeneric(PVOID ptiMyself)
{
PTHREADINFO pti = (PTHREADINFO)ptiMyself;
if (pti)
{
HEV hevExitComplete;
if (pti->flFlags & THRF_WAIT)
// "Wait" flag set: thrCreate is then
// waiting on the wait event sem posted...
// note: we do not post if THRF_WAIT_EXPLICIT!
// thread must then post itself
DosPostEventSem(pti->hevRunning);
if (pti->flFlags & THRF_PMMSGQUEUE)
{
// create msg queue
if ((pti->hab = WinInitialize(0)))
{
if ((pti->hmq = WinCreateMsgQueue(pti->hab, 4000)))
{
// run thread func
((PTHREADFUNC)pti->pThreadFunc)(pti);
WinDestroyMsgQueue(pti->hmq);
}
WinTerminate(pti->hab);
}
}
else
// no msgqueue:
// run thread func without msg queue
((PTHREADFUNC)pti->pThreadFunc)(pti);
if (pti->flFlags & (THRF_WAIT | THRF_WAIT_EXPLICIT)) // V0.9.9 (2001-03-14) [umoeller]
// "Wait" flag set: delete semaphore
DosCloseEventSem(pti->hevRunning);
// V0.9.9 (2001-03-07) [umoeller]
// remove thread from global list
if (LockThreadInfos())
{
lstRemoveItem(&G_llThreadInfos, pti);
UnlockThreadInfos();
}
// copy event sem before freeing pti
hevExitComplete = pti->hevExitComplete;
// set exit flags
// V0.9.7 (2000-12-20) [umoeller]
pti->tid = NULLHANDLE;
if (pti->ptidRunning)
// clear "running" flag
*(pti->ptidRunning) = 0;
// (2000-12-18) [lafaix] clean up pti if thread is transient.
if (pti->flFlags & THRF_TRANSIENT)
free(pti);
if (hevExitComplete)
// caller wants notification:
DosPostEventSem(hevExitComplete);
// V0.9.16 (2001-12-08) [umoeller]
}
// thread func exits
}
/*
*@@ thrCreate:
* this function fills a THREADINFO structure and starts
* a new thread using _beginthread.
*
* You must pass the thread function in pfn, which will
* then be executed. The thread will be passed a pointer
* to the THREADINFO structure as its thread parameter.
* The ulData field in that structure is set to ulData
* here. Use whatever you like.
*
* The thread function must be declared like this:
*
+ void _Optlink fntWhatever(PTHREADINFO ptiMyself)
*
* You should manually specify _Optlink because if the
* function is prototyped somewhere, VAC will automatically
* modify the function's linkage, and you'll run into
* crashes.
*
* The thread's ptiMyself is then a pointer to the
* THREADINFO structure passed to this function.
* In your thread function, ulData may be obtained like this:
*
+ ULONG ulData = ptiMyself->ulData;
*
* thrCreate does not start your thread func directly,
* but only through the thr_fntGeneric wrapper to
* provide additional functionality. As a consequence,
* in your own thread function, NEVER call _endthread
* explicitly, because this would skip the exit processing
* (cleanup) in thr_fntGeneric. Instead, just fall out of
* your thread function, or return().
*
* The THREADINFO structure passed to this function must
* be accessible all the time while the thread is running
* because the thr* functions will use it for maintenance.
* This function does NOT check whether a thread is
* already running in *pti. Do not use the same THREADINFO
* for several threads.
*
* If you do not want to manage the structure yourself,
* you can pass the THRF_TRANSIENT flag (see below).
*
* ptidRunning is an optional parameter and can be NULL.
* If non-null, it must point to a ULONG which will contain
* the thread's TID while the thread is still running.
* Once the thread exits, the ULONG will automatically
* be set to zero; this is useful for a quick check whether
* the thread is currently busy.
*
* Note: ptidRunning is only reliable if you set the
* THRF_WAIT or THRF_WAIT_EXPLICIT flags.
*
* flFlags can be any combination of the following:
*
* -- THRF_PMMSGQUEUE: creates a PM message queue on the
* thread. Your thread function will find the HAB and
* the HMQ in its THREADINFO. These are automatically
* destroyed when the thread terminates.
*
* WARNING: Be careful with this setting for short-lived
* threads which are started synchronously while some
* other thread is locking PM, e.g. in a WinSendMsg
* call. While creation of the thread will still
* succeed, the thread will _not_ terminate properly
* while PM is locked, so do not wait for such a thread
* to terminate!! (V0.9.16)
*
* -- THRF_WAIT: if this is set, thrCreate does not
* return to the caller until your thread function
* has successfully started running. This is done by
* waiting on an event semaphore which is automatically
* posted by thr_fntGeneric. This is useful for the
* typical PM "Worker" thread where you need to disable
* menu items on thread 1 while the thread is running.
*
* -- THRF_WAIT_EXPLICIT: like THRF_WAIT, but in this case,
* your thread function _must_ post THREADINFO.hevRunning
* yourself (thr_fntGeneric will not automatically
* post it). Useful for waiting until your own thread
* function is fully initialized, e.g. if it creates
* an object window.
*
* WARNING: if your thread forgets to post this, we'll
* hang.
*
* Added V0.9.9 (2001-03-14) [umoeller].
*
* -- THRF_TRANSIENT: creates a "transient" thread where
* pti may be NULL. A THREADINFO structure is then
* allocated from the heap internally, but not visible
* to the caller.
*
* This now (V0.9.9) returns the TID of the new thread or
* null on errors.
*
*@@changed V0.9.0 [umoeller]: default stack size raised for Watcom (thanks, Rüdiger Ihle)
*@@changed V0.9.0 [umoeller]: _beginthread is now only called after all variables have been set (thanks, Rüdiger Ihle)
*@@changed V0.9.2 (2000-03-04) [umoeller]: added stack size parameter
*@@changed V0.9.2 (2000-03-06) [umoeller]: now using thr_fntGeneric; thrGoodbye is no longer needed
*@@changed V0.9.3 (2000-04-29) [umoeller]: removed stack size param; added fCreateMsgQueue
*@@changed V0.9.3 (2000-05-01) [umoeller]: added pbRunning and flFlags
*@@changed V0.9.5 (2000-08-26) [umoeller]: now using PTHREADINFO
*@@changed V0.9.7 (2000-12-18) [lafaix]: THRF_TRANSIENT support added
*@@changed V0.9.9 (2001-02-06) [umoeller]: now returning TID
*@@changed V0.9.9 (2001-03-07) [umoeller]: added pcszThreadName
*@@changed V0.9.9 (2001-03-14) [umoeller]: added THRF_WAIT_EXPLICIT
*@@changed V0.9.12 (2001-05-20) [umoeller]: changed pfRunning to ptidRunning
*@@changed V0.9.16 (2001-10-28) [umoeller]: made ptidRunning volatile to prohibit compiler optimizations
*/
ULONG thrCreate(PTHREADINFO pti, // out: THREADINFO data
PTHREADFUNC pfn, // in: _Optlink thread function
volatile unsigned long *ptidRunning,
// out: variable set to TID while thread is running;
// ptr can be NULL
const char *pcszThreadName, // in: thread name (for identification)
ULONG flFlags, // in: THRF_* flags
ULONG ulData) // in: user data to be stored in THREADINFO
{
ULONG ulrc = 0; // V0.9.9 (2001-02-06) [umoeller]
// (2000-12-18) [lafaix] TRANSIENT
if (flFlags & THRF_TRANSIENT)
{
if (pti == NULL)
pti = (PTHREADINFO)malloc(sizeof(THREADINFO));
// cleaned up by thr_fntGeneric on exit
}
if (pti)
{
memset(pti, 0, sizeof(THREADINFO));
pti->cbStruct = sizeof(THREADINFO);
pti->pThreadFunc = (PVOID)pfn;
pti->ptidRunning = ptidRunning;
pti->pcszThreadName = pcszThreadName; // V0.9.9 (2001-03-07) [umoeller]
pti->flFlags = flFlags;
pti->ulData = ulData;
if (flFlags & (THRF_WAIT | THRF_WAIT_EXPLICIT)) // V0.9.9 (2001-03-14) [umoeller]
// "Wait" flag set: create an event semaphore which
// will be posted by thr_fntGeneric (THRF_WAIT only)
if (DosCreateEventSem(NULL, // unnamed
&pti->hevRunning,
0, // unshared
FALSE) // not posted (reset)
!= NO_ERROR)
{
if (flFlags & THRF_TRANSIENT)
free(pti);
// stop right here
pti = NULL;
}
if (pti)
{
pti->tid = _beginthread( // moved, V0.9.0 (hint: Rüdiger Ihle)
thr_fntGeneric, // changed V0.9.2 (2000-03-06) [umoeller]
0, // unused compatibility param
3*96000, // plenty of stack
pti); // parameter passed to thread
ulrc = pti->tid;
if (pti->ptidRunning)
// set "running" flag // V0.9.12 (2001-05-20) [umoeller]
*(pti->ptidRunning) = pti->tid;
if (ulrc)
{
if (LockThreadInfos())
{
lstAppendItem(&G_llThreadInfos, pti);
UnlockThreadInfos();
}
if (flFlags & (THRF_WAIT | THRF_WAIT_EXPLICIT))
{
// "Wait" flag set: wait on event semaphore
DosWaitEventSem(pti->hevRunning,
SEM_INDEFINITE_WAIT);
}
}
}
}
return (ulrc);
}
/*
*@@ thrRunSync:
* runs the specified thread function synchronously.
*
* This is a wrapper around thrCreate. However, this
* function does not return until the thread function
* finishes. This creates a modal message loop on the
* calling thread so that the PM message queue is not
* blocked while the thread is running. Naturally this
* implies that the calling thread has a message queue.
*
* As a special requirement, your thread function (pfn)
* must post WM_USER to THREADINFO.hwndNotify just before
* exiting. The mp1 value of WM_USER will then be returned
* by this function.
*
*@@added V0.9.5 (2000-08-26) [umoeller]
*@@changed V0.9.9 (2001-03-07) [umoeller]: added pcszThreadName
*@@changed V0.9.16 (2001-12-08) [umoeller]: fixed hang with thrWait
*/
ULONG thrRunSync(HAB hab, // in: anchor block of calling thread
PTHREADFUNC pfn, // in: passed to thrCreate
const char *pcszThreadName, // in: passed to thrCreate
ULONG ulData) // in: passed to thrCreate
{
ULONG ulrc = 0;
QMSG qmsg;
BOOL fQuit = FALSE;
HWND hwndNotify;
if (hwndNotify = WinCreateWindow(HWND_OBJECT,
WC_BUTTON,
(PSZ)"",
0,
0,0,0,0,
0,
HWND_BOTTOM,
0,
0,
NULL))
{
THREADINFO ti = {0};
volatile unsigned long tidRunning = 0;
thrCreate(&ti,
pfn,
&tidRunning,
pcszThreadName,
THRF_PMMSGQUEUE,
ulData);
ti.hwndNotify = hwndNotify;
// create event sem to wait on V0.9.16 (2001-12-08) [umoeller]
DosCreateEventSem(NULL,
&ti.hevExitComplete,
0,
FALSE); // not posted
while (WinGetMsg(hab,
&qmsg, 0, 0, 0))
{
// current message for our object window?
if ( (qmsg.hwnd == hwndNotify)
&& (qmsg.msg == WM_USER)
)
{
fQuit = TRUE;
ulrc = (ULONG)qmsg.mp1;
}
WinDispatchMsg(hab, &qmsg);
if (fQuit)
break;
}
// we must wait for the thread to finish, or
// otherwise THREADINFO is deleted from the stack
// before the thread exits... will crash!
// thrWait(&ti);
// now using event sem V0.9.16 (2001-12-08) [umoeller]
WinWaitEventSem(ti.hevExitComplete, 5000);
DosCloseEventSem(ti.hevExitComplete);
WinDestroyWindow(hwndNotify);
}
return (ulrc);
}
/*
*@@ thrListThreads:
* returns an array of THREADINFO structures
* for all threads that have been started using
* thrCreate (or thrRunSync).
*
* If no threads are running yet, this returns
* NULL.
*
* Otherwise, this returns the pointer to the
* first array item, and *pcThreads receives
* the array item count (NOT the total array
* size). The array is a copied snapshot of all
* current THREADINFO's and must be free()'d
* by the caller.
*
*@@added V0.9.9 (2001-03-07) [umoeller]
*/
PTHREADINFO thrListThreads(PULONG pcThreads)
{
PTHREADINFO pArray = 0;
if (LockThreadInfos())
{
PTHREADINFO pThis;
PLISTNODE pNode;
*pcThreads = lstCountItems(&G_llThreadInfos);
if (pArray = (PTHREADINFO)malloc(*pcThreads * sizeof(THREADINFO)))
{
pThis = pArray;
pNode = lstQueryFirstNode(&G_llThreadInfos);
while (pNode)
{
memcpy(pThis,
(PTHREADINFO)pNode->pItemData,
sizeof(THREADINFO));
pThis++;
pNode = pNode->pNext;
}
}
UnlockThreadInfos();
}
return (pArray);
}
/*
*@@ thrFindThread:
* attempts to find the thread with the specified
* TID; if found, returns TRUE and copies its
* THREADINFO into *pti.
*
*@@added V0.9.9 (2001-03-07) [umoeller]
*/
BOOL thrFindThread(PTHREADINFO pti,
ULONG tid)
{
BOOL brc = FALSE;
if (LockThreadInfos())
{
PLISTNODE pNode = lstQueryFirstNode(&G_llThreadInfos);
while (pNode)
{
PTHREADINFO ptiThis = (PTHREADINFO)pNode->pItemData;
if (ptiThis->tid == tid)
{
memcpy(pti, ptiThis, sizeof(THREADINFO));
brc = TRUE;
break;
}
pNode = pNode->pNext;
}
UnlockThreadInfos();
}
return brc;
}
/*
*@@ thrClose:
* this functions sets the "fExit" flag in
* THREADINFO to TRUE.
*
* The thread should monitor this flag
* periodically and then terminate itself.
*/
BOOL thrClose(PTHREADINFO pti)
{
if (pti)
{
pti->fExit = TRUE;
return (TRUE);
}
return (FALSE);
}
/*
*@@ thrWait:
* this function waits for a thread to end by calling
* DosWaitThread. Note that this blocks the calling
* thread, so only use this function when you're sure
* the thread will actually terminate.
*
* Update V0.9.16: Do not use this with PM theads at
* all. DosWaitThread can hang the system then.
*
* Returns FALSE if the thread wasn't running or TRUE
* if it was and has terminated.
*
*@@changed V0.9.0 [umoeller]: now checking for whether pti->tid is still != 0
*/
BOOL thrWait(PTHREADINFO pti)
{
if (pti)
if (pti->tid)
{
DosWaitThread(&pti->tid, DCWW_WAIT);
pti->tid = NULLHANDLE;
return (TRUE);
}
return (FALSE);
}
/*
*@@ thrFree:
* this is a combination of thrClose and
* thrWait, i.e. this func does not return
* until the specified thread has ended.
*/
BOOL thrFree(PTHREADINFO pti)
{
if (pti->tid)
{
thrClose(pti);
thrWait(pti);
}
return (TRUE);
}
/*
*@@ thrKill:
* just like thrFree, but the thread is
* brutally killed, using DosKillThread.
*
* Note: DO NOT USE THIS. DosKillThread
* cannot clean up the C runtime. In the
* worst case, this hangs the system
* because the runtime hasn't released
* a semaphore or something like that.
*/
BOOL thrKill(PTHREADINFO pti)
{
if (pti->tid)
{
DosResumeThread(pti->tid);
// this returns an error if the thread
// is not suspended, but otherwise the
// system might hang
DosKillThread(pti->tid);
}
return (TRUE);
}
/*
*@@ thrQueryID:
* returns thread ID or NULLHANDLE if
* the specified thread is not or no
* longer running.
*/
TID thrQueryID(const THREADINFO* pti)
{
if (pti)
return (pti->tid);
return (NULLHANDLE);
}
/*
*@@ thrQueryPriority:
* returns the priority of the calling thread.
* The low byte of the low word is a hexadecimal value
* representing a rank (value 0 to 31) within a priority class.
* Class values, found in the high byte of the low word, are
* as follows:
* -- 0x01 idle
* -- 0x02 regular
* -- 0x03 time-critical
* -- 0x04 server
*
* Note: This cannot retrieve the priority of a
* thread other than the one on which this function
* is running. Use prc16QueryThreadInfo for that.
*/
ULONG thrQueryPriority(VOID)
{
PTIB ptib;
PPIB ppib;
if (DosGetInfoBlocks(&ptib, &ppib) == NO_ERROR)
if (ptib)
if (ptib->tib_ptib2)
return (ptib->tib_ptib2->tib2_ulpri);
return (0);
}