C Interface


The C interface to the Dinkum Threads Library is very similar to the thread support interface defined in the Posix Standard. It consists of two headers:

Here is a sample program to illustrate the use of the C interface:

#include <stdio.h>
#include <stdlib.h>

#include "Dinkum/threads/threads.h"
#include "Dinkum/threads/xtimec.h"

#define DATA_SIZE 20
#define DATA_LIMIT 100

/* data queue */
static int data[DATA_SIZE];
static int begin;
static int end;
static int queue_not_full;

/* thread-specfic storage */
static tss_t key;

/* initialization */
static once_flag init_flag = ONCE_FLAG_INIT;
static void init(void)
	{	/* initialize global data from first calling thread */
	queue_not_full = 1;
	tss_create(&key, free);
	}

static void setup(void)
	{	/* initialize thread data */
	call_once(&init_flag, init);
	tss_set(key, malloc(sizeof(int)));
	*(int*)tss_get(key) = 0;
	}

/*	synchronization objects */
static mtx_t data_mtx;
static cnd_t queue_full_c;
static cnd_t queue_empty_c;

/* utilities */
static void delay(void)
	{	/* delay randomly from 0 to 200 milliseconds */
	xtime xt;
	xtime_get(&xt, TIME_UTC);
	xt.nsec += (rand() * 200) / (RAND_MAX + 1) * 1000000;
	thrd_sleep(&xt);
	}

/* produce data */
static int producer(void *arg)
	{	/* insert sequential values from 0 to DATA_LIMIT into queue */
	setup();
	while (*(int*)tss_get(key) < DATA_LIMIT)
		{	/* insert *val into queue */
		delay();
		mtx_lock(&data_mtx);
		while (queue_not_full == 0)
			cnd_wait(&queue_full_c, &data_mtx);
		data[end++] = (*(int*)tss_get(key))++;
		if (end == DATA_SIZE)
			end = 0;
		if (end + 1 == begin
			|| end + 1 == DATA_SIZE && begin == 0)
			queue_not_full = 0;
		cnd_signal(&queue_empty_c);
		mtx_unlock(&data_mtx);
		}
	return 0;
	}

/* consume data */
static int finished = 0;
static int consumer(void *arg)
	{	/* remove data values from queue and display values */
	setup();
	while (!finished || begin != end)
		{	/* remove and display data values */
		delay();
		mtx_lock(&data_mtx);
		while (!finished && begin == end)
			cnd_wait(&queue_empty_c, &data_mtx);
		if (begin != end)
			{	/* remove and display a data value */
			*(int*)tss_get(key) = -data[begin++];
			printf("%d\n", *(int*)tss_get(key));
			if (begin == DATA_SIZE)
				begin = 0;
			queue_not_full = 1;
			cnd_signal(&queue_full_c);
			}
		mtx_unlock(&data_mtx);
		}
	return 0;
	}

int main()
	{	/* create a consumer thread and two producer threads */
	thrd_t thr0, thr1, thr2;
	if (mtx_init(&data_mtx, mtx_plain) != thrd_success)
		{	/* display error message and quit */
		puts("Error: unable to initialize mutex");
		exit(EXIT_FAILURE);
		}
	/* error checking omitted for clarity */
	cnd_init(&queue_full_c);
	cnd_init(&queue_empty_c);
	thrd_create(&thr0, consumer, 0);
	thrd_create(&thr1, producer, 0);
	thrd_create(&thr2, producer, 0);
	thrd_join(thr1, 0);
	thrd_join(thr2, 0);
	mtx_lock(&data_mtx);
	finished = 1;
	mtx_unlock(&data_mtx);
	thrd_join(thr0, 0);
	return 0;
	}

Threads · Condition Variables · Mutexes · Once Functions · Thread-specific Storage · Return Values


Threads

Use the functions and types with the prefix thrd to manage threads. Each thread has an identifier of type thrd_t, which is passed as an argument to the functions that manage specific threads. Each thread begins execution in a function of type thrd_start_t. To create a new thread call the function thrd_create with the address of the thread identifier, the address of the thread function, and an argument to be passed to the thread function. The thread ends when it returns from the thread function or when it calls thrd_exit. For convenience, a thread can provide a result code of type int when it ends, either by returning the code from the thread function or by passing the code to thrd_exit. To block a thread until another thread ends call thrd_join, passing the identifier of the thread to wait for and, optionally, the address of a variable of type int where the result code will be stored. To properly clean up resources allocated by the operating system, an application should call either thrd_join or thrd_detach once for each thread created by thrd_create.

Two functions operate on the current thread; they do not take a thread identifier argument. Use thrd_sleep to suspend execution of the current thread until a particular time. Use thrd_yield to permit other threads to run even if the current thread would ordinarily continue to run.

Two functions operate on thread identifiers. Use thrd_equal to determine whether two thread identifiers refer to the same thread. Use thrd_current to get a thread identifier that refers to the current thread.

Condition Variables

Use the functions and type with the prefix cnd to manage condition variables. Each condition variable has an identifier of type cnd_t, which is passed as an argument to the functions that manage condition variables. Use cnd_init to create a condition variable and cnd_destroy to release any resources associated with a condition variable when it is no longer needed. To wait for a condition variable to be signalled call cnd_wait or cnd_timedwait. To unblock threads waiting for a condition variable call cnd_signal or cnd_broadcast.

Mutexes

Use the functions and type with the prefix mtx to manage mutexes. Each mutex has an identifier of type mtx_t, which is passed as an argument to the functions that manage mutexes. Use mtx_init to create a mutex and mtx_destroy to release any resources associated with a mutex when it is no longer needed. To lock a mutex call mtx_lock, mtx_timedlock or mtx_trylock. To unlock a mutex call mtx_unlock.

Once Functions

Use a value of type once_flag, initialized to the value ONCE_FLAG_INIT, to ensure that a function is called exactly once by passing a function pointer and the address of the once_flag object to call_once.

Thread-specific Storage

Use the functions and types with the prefix tss to manage thread-specific storage. Each thread-specific storage pointer has an identifier of type tss_t, which is passed as an argument to the functions that manage thread-specific storage. Call tss_create to create a thread-specific storage pointer and tss_delete to release any resources associated with a thread-specific storage pointer when it is no longer needed. To get the value held by the pointer in the current thread call tss_get. To change the value held by the pointer in the current thread call tss_set.

Each thread-specific storage pointer may have an associated destructor, specified in the call to tss_create. The destructor will be called when a thread terminates and the value of the pointer associated with that thread is not 0. The value of the pointer for that thread is set to 0 before calling the destructor and the old value is passed to the destructor. Since a destructor can store non-0 values in thread-specific storage pointers, this process will be repeated until no pointers for the terminating thread hold non-0 values or until a system-specific maximum number of iterations TSS_DTOR_ITERATIONS has been made.

Return Values

Most of the functions return a value of type int that indicates whether the function succeeded. The values are as follows:


See also the Table of Contents and the Index.

Copyright © 1992-2013 by Dinkumware, Ltd. Portions derived from work copyright © 2001 by William E. Kempf. All rights reserved.