/*************************************************************************** $name: CondVar.c $version: 1.0 $date_modified: 082499 $description: Demonstrates a reasonably interesting of the condition variable. $owner: NKS Product Manager Copyright (c) 1999 Novell, Inc. All Rights Reserved. THIS WORK IS SUBJECT TO U.S. AND INTERNATIONAL COPYRIGHT LAWS AND TREATIES. USE AND REDISTRIBUTION OF THIS WORK IS SUBJECT TO THE LICENSE AGREEMENT ACCOMPANYING THE SOFTWARE DEVELOPMENT KIT (SDK) THAT CONTAINS THIS WORK. PURSUANT TO THE SDK LICENSE AGREEMENT, NOVELL HEREBY GRANTS TO DEVELOPER A ROYALTY-FREE, NON-EXCLUSIVE LICENSE TO INCLUDE NOVELL'S SAMPLE CODE IN ITS PRODUCT. NOVELL GRANTS DEVELOPER WORLDWIDE DISTRIBUTION RIGHTS TO MARKET, DISTRIBUTE, OR SELL NOVELL'S SAMPLE CODE AS A COMPONENT OF DEVELOPER'S PRODUCTS. NOVELL SHALL HAVE NO OBLIGATIONS TO DEVELOPER OR DEVELOPER'S CUSTOMERS WITH RESPECT TO THIS CODE. ****************************************************************************/ /*-------------------------------------------------------------------------- Shown here is a simple producer-consumer example. It can also be called a work crew. The producer threads keep "producing" work items. Each producer is assumed to produce only one work item at a time. The work queue can have a maximum of MAX_ITEM_COUNT work items outstanding. A producer adding an item must check for an available "slot" and ONLY then add the work item. If no "slots" are available, the producer must block until at least one is available. The work crew has a number of consumer threads (or worker threads) which are available for processing the work items produced by the producers. The number of consumer threads can be adjusted for the required concurrency. A consumer thread picks up only one work item at a time and processes it. If no work item is available, it must wait until at least one work item is available and pick the first work item for processing. Assume functions accomplishing meaningful work according to their names, ProduceOneWorkItem, ProcessWorkItem, AddWorkItem. --------------------------------------------------------------------------*/ #include <nks/synch.h> #ifndef TRUE # define TRUE 1 #endif #ifndef FALSE # define FALSE 0 #endif #define MAX_ITEM_COUNT 100 typedef struct _WorkItem { struct _WorkItem *pNextWorkItem; // anything else in here... } WorkItem_t; typedef struct { NXMutex_t *pWorkCrewLock; NXCond_t *pProducerCV; NXCond_t *pConsumerCV; WorkItem_t *pWorkQue; unsigned int ItemCount; } WorkCrewStruct_t; WorkCrewStruct_t gWorkCrew; extern void ProduceOneWorkItem( void ); extern void RemoveOneWorkItem( void ); extern void AddWorkItem( void ); extern void ProcessWorkItem( void ); void InitWorkCrew(void) { gWorkCrew.pWorkCrewLock = NXMutexAlloc(0, NULL, NULL); gWorkCrew.pProducerCV = NXCondAlloc(NULL); gWorkCrew.pConsumerCV = NXCondAlloc(NULL); gWorkCrew.pWorkQue = NULL; gWorkCrew.ItemCount = 0; } void ProducerThreadRoutine( void ) { for (;;) { ProduceOneWorkItem(); /* ** We will now add it to the work queue for processing by the consumers (aka ** worker threads). However, we need to check for a free "slot" in the work ** queue. If there is no free slot, we need to wait until there is one avail- ** able. Note that when we actually wake up, the free slot may be have been ** used up by another producer. This is why the while loop is used. */ NXLock(gWorkCrew.pWorkCrewLock); while (gWorkCrew.ItemCount == MAX_ITEM_COUNT) NXCondWait(gWorkCrew.pProducerCV, gWorkCrew.pWorkCrewLock); /* ** There is at least one slot available in the work item queue. Add the work ** item to the queue. */ AddWorkItem(); gWorkCrew.ItemCount++; NXUnlock(gWorkCrew.pWorkCrewLock); /* ** Wakeup ONE consumer. Note that we produce only one work item here. If we ** produce multiple work items at a time, we may choose to call NXCondBroad- ** cast(). */ NXCondSignal(gWorkCrew.pConsumerCV); } } void ConsumerThreadRoutine( void ) { for (;;) { int needToSignalFlag; /* ** Attempt to consume one work item. If nothing is available, block until one ** is available. */ NXLock(gWorkCrew.pWorkCrewLock); while (gWorkCrew.ItemCount == 0) NXCondWait(gWorkCrew.pConsumerCV, gWorkCrew.pWorkCrewLock); /* ** There is at least one work item available. Pick up the first work item. */ RemoveOneWorkItem(); /* ** Producers would be waiting only if the item count was the max. We could as ** well blindly do a signal here without checking the count etc. Note that when ** a producer actually wakes up, the free slot may be have been used up by ** another producer. This is why the while() loop is used. */ needToSignalFlag = FALSE; if (gWorkCrew.ItemCount-- == MAX_ITEM_COUNT) needToSignalFlag = TRUE; NXUnlock(gWorkCrew.pWorkCrewLock); /* ** Wake up one producer. Note that we consume only one work item at a time. If ** we had consumed multiple work items at a time, we would have called NXCond- ** Broadcast() here. */ if (needToSignalFlag) NXCondSignal(gWorkCrew.pProducerCV); ProcessWorkItem(); } }