Multithreaded Programming Guide

Synchronization Across Process Boundaries

Each of the synchronization primitives can be set up to be used across process boundaries. This is done quite simply by ensuring that the synchronization variable is located in a shared memory segment and by calling the appropriate init routine with type set to USYNC_PROCESS.

If this has been done, then the operations on the synchronization variables work just as they do when type is USYNC_THREAD.

mutex_init(&m, USYNC_PROCESS, 0);
rwlock_init(&rw, USYNC_PROCESS, 0);
cond_init(&cv, USYNC_PROCESS, 0);
sema_init(&s, count, USYNC_PROCESS, 0);

Using LWPs Between Processes

Using locks and condition variables between processes does not require using the threads library. The recommended approach is to use the threads library interfaces, but when this is not desirable, then the _lwp_mutex_* and _lwp_cond_* interfaces can be used as follows:

  1. Allocate the locks and condition variables as usual in shared memory (either with shmop(2) or mmap(2)).

  2. Then initialize the newly allocated objects appropriately with the USYNC_PROCESS type. Because no interface is available to perform the initialization (_lwp_mutex_init(2) and _lwp_cond_init(2) do not exist), the objects can be initialized using statically allocated and initialized dummy objects.

For example, to initialize lockp:

	lwp_mutex_t *lwp_lockp;
	lwp_mutex_t dummy_shared_mutex = SHAREDMUTEX;
		/* SHAREDMUTEX is defined in /usr/include/synch.h */
	...
	...
	lwp_lockp = alloc_shared_lock();
	*lwp_lockp = dummy_shared_mutex;

Similarly, for condition variables:

	lwp_cond_t *lwp_condp;
	lwp_cond_t dummy_shared_cv = SHAREDCV;
		/* SHAREDCV is defined in /usr/include/synch.h */
	...
	...
	lwp_condp = alloc_shared_cv();
	*lwp_condp = dummy_shared_cv;

Producer/Consumer Problem Example

Example 9-2 shows the producer/consumer problem with the producer and consumer in separate processes. The main routine maps zero-filled memory (that it shares with its child process) into its address space. Note that mutex_init() and cond_init() must be called because the type of the synchronization variables is USYNC_PROCESS.

A child process is created that runs the consumer. The parent runs the producer.

This example also shows the drivers for the producer and consumer. The producer_driver() simply reads characters from stdin and calls producer(). The consumer_driver() gets characters by calling consumer() and writes them to stdout.

The data structure for Example 9-2 is the same as that used for the solution with condition variables (see "Nested Locking with a Singly Linked List").


Example 9-2 The Producer/Consumer Problem, Using USYNC_PROCESS

main() {
    int zfd;
    buffer_t *buffer;

    zfd = open("/dev/zero", O_RDWR);
    buffer = (buffer_t *)mmap(NULL, sizeof(buffer_t),
        PROT_READ|PROT_WRITE, MAP_SHARED, zfd, 0);
    buffer->occupied = buffer->nextin = buffer->nextout = 0;

    mutex_init(&buffer->lock, USYNC_PROCESS, 0);
    cond_init(&buffer->less, USYNC_PROCESS, 0);
    cond_init(&buffer->more, USYNC_PROCESS, 0);
    if (fork() == 0)
        consumer_driver(buffer);
    else
        producer_driver(buffer);
}

void producer_driver(buffer_t *b) {
    int item;

    while (1) {
        item = getchar();
        if (item == EOF) {
            producer(b, `\0');
            break;
        } else
            producer(b, (char)item);
    }
}

void consumer_driver(buffer_t *b) {
    char item;

    while (1) {
        if ((item = consumer(b)) == '\0')
            break;
        putchar(item);
    }
}

A child process is created to run the consumer; the parent runs the producer.