sys/posix/pthread: added dynamic pthread thread local storage
This commit is contained in:
parent
319f1b25ae
commit
6e90ad6d73
@ -39,6 +39,7 @@
|
|||||||
#include "pthread_scheduling.h"
|
#include "pthread_scheduling.h"
|
||||||
#include "pthread_cancellation.h"
|
#include "pthread_cancellation.h"
|
||||||
#include "pthread_cond.h"
|
#include "pthread_cond.h"
|
||||||
|
#include "pthread_tls.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|||||||
91
sys/posix/pthread/include/pthread_tls.h
Normal file
91
sys/posix/pthread/include/pthread_tls.h
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Hamburg University of Applied Sciences (HAW)
|
||||||
|
*
|
||||||
|
* This file is subject to the terms and conditions of the GNU Lesser
|
||||||
|
* General Public License v2.1. See the file LICENSE in the top level
|
||||||
|
* directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ingroup pthread
|
||||||
|
* @{
|
||||||
|
* @file
|
||||||
|
* @brief RIOT POSIX thread local storage
|
||||||
|
* @author Martin Landsmann <martin.landsmann@haw-hamburg.de>
|
||||||
|
* @author René Kijewski <rene.kijewski@fu-berlin.de>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __SYS__POSIX__PTHREAD_TLS__H
|
||||||
|
#define __SYS__POSIX__PTHREAD_TLS__H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Internal representation of a thread-specific key.
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
struct __pthread_tls_key;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A single thread-specific datum.
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
struct __pthread_tls_datum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A thread-specific key.
|
||||||
|
*/
|
||||||
|
typedef struct __pthread_tls_key *pthread_key_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the requested tls
|
||||||
|
* @param[in] key the identifier for the requested tls
|
||||||
|
* @return returns pointer to the storage on success, a 0 value otherwise
|
||||||
|
*/
|
||||||
|
void *pthread_getspecific(pthread_key_t key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set and binds a specific tls to a key
|
||||||
|
* @param[in] key the identifier for the tls
|
||||||
|
* @param[in] value pointer to the location of the tls
|
||||||
|
* @return returns 0 on success, an errorcode otherwise
|
||||||
|
*/
|
||||||
|
int pthread_setspecific(pthread_key_t key, const void *value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates a new key to be used to identify a specific tls
|
||||||
|
* @param[out] key the created key is scribed to the given pointer
|
||||||
|
* @param[in] destructor function pointer called when non NULL just befor the pthread exits
|
||||||
|
* @return returns 0 on success, an errorcode otherwise
|
||||||
|
*/
|
||||||
|
int pthread_key_create(pthread_key_t *key, void (*destructor)(void *));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deletes a pthread_key_t that was previously created with pthread_key_create.
|
||||||
|
* @details does not call the destructor of the key
|
||||||
|
* @param[in] key the identifier of the key to be deleted
|
||||||
|
* @return returns 0 on success, an errorcode otherwise
|
||||||
|
*/
|
||||||
|
int pthread_key_delete(pthread_key_t key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Destroys all thread-specific keys for pthread `self_id`.
|
||||||
|
* @param[in] self_id the result of pthread_self().
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
void __pthread_keys_exit(int self_id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the pointer to the head of the list of thread-specific data.
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
struct __pthread_tls_datum **__pthread_get_tls_head(int self_id) PURE;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* __SYS__POSIX__PTHREAD_TLS__H */
|
||||||
|
/** @} */
|
||||||
@ -66,6 +66,8 @@ typedef struct pthread_thread {
|
|||||||
|
|
||||||
char *stack;
|
char *stack;
|
||||||
|
|
||||||
|
struct __pthread_tls_datum *tls_head;
|
||||||
|
|
||||||
__pthread_cleanup_datum_t *cleanup_top;
|
__pthread_cleanup_datum_t *cleanup_top;
|
||||||
} pthread_thread_t;
|
} pthread_thread_t;
|
||||||
|
|
||||||
@ -87,6 +89,7 @@ static int insert(pthread_thread_t *pt)
|
|||||||
{
|
{
|
||||||
int result = -1;
|
int result = -1;
|
||||||
mutex_lock(&pthread_mutex);
|
mutex_lock(&pthread_mutex);
|
||||||
|
|
||||||
for (int i = 0; i < MAXTHREADS; i++){
|
for (int i = 0; i < MAXTHREADS; i++){
|
||||||
if (!pthread_sched_threads[i]) {
|
if (!pthread_sched_threads[i]) {
|
||||||
pthread_sched_threads[i] = pt;
|
pthread_sched_threads[i] = pt;
|
||||||
@ -94,6 +97,7 @@ static int insert(pthread_thread_t *pt)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mutex_unlock(&pthread_mutex);
|
mutex_unlock(&pthread_mutex);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -175,7 +179,7 @@ void pthread_exit(void *retval)
|
|||||||
DEBUG("ERROR called pthread_self() returned 0 in \"%s\"!\n", __func__);
|
DEBUG("ERROR called pthread_self() returned 0 in \"%s\"!\n", __func__);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
pthread_thread_t *self = pthread_sched_threads[self_id-1];
|
pthread_thread_t *self = pthread_sched_threads[self_id - 1];
|
||||||
|
|
||||||
while (self->cleanup_top) {
|
while (self->cleanup_top) {
|
||||||
__pthread_cleanup_datum_t *ct = self->cleanup_top;
|
__pthread_cleanup_datum_t *ct = self->cleanup_top;
|
||||||
@ -184,6 +188,12 @@ void pthread_exit(void *retval)
|
|||||||
ct->__routine(ct->__arg);
|
ct->__routine(ct->__arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Prevent linking in pthread_tls.o if no TSS functions were used. */
|
||||||
|
extern void __pthread_keys_exit(int self_id) __attribute__((weak));
|
||||||
|
if (__pthread_keys_exit) {
|
||||||
|
__pthread_keys_exit(self_id);
|
||||||
|
}
|
||||||
|
|
||||||
self->thread_pid = KERNEL_PID_UNDEF;
|
self->thread_pid = KERNEL_PID_UNDEF;
|
||||||
DEBUG("pthread_exit(%p), self == %p\n", retval, (void *) self);
|
DEBUG("pthread_exit(%p), self == %p\n", retval, (void *) self);
|
||||||
if (self->status != PTS_DETACHED) {
|
if (self->status != PTS_DETACHED) {
|
||||||
@ -353,3 +363,9 @@ void __pthread_cleanup_pop(__pthread_cleanup_datum_t *datum, int execute)
|
|||||||
datum->__routine(datum->__arg);
|
datum->__routine(datum->__arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct __pthread_tls_datum **__pthread_get_tls_head(int self_id)
|
||||||
|
{
|
||||||
|
pthread_thread_t *self = pthread_sched_threads[self_id-1];
|
||||||
|
return self ? &self->tls_head : NULL;
|
||||||
|
}
|
||||||
|
|||||||
198
sys/posix/pthread/pthread_tls.c
Normal file
198
sys/posix/pthread/pthread_tls.c
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Hamburg University of Applied Sciences (HAW)
|
||||||
|
*
|
||||||
|
* This file is subject to the terms and conditions of the GNU Lesser
|
||||||
|
* General Public License v2.1. See the file LICENSE in the top level
|
||||||
|
* directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ingroup pthread
|
||||||
|
* @{
|
||||||
|
* @file
|
||||||
|
* @brief RIOT POSIX thread local storage
|
||||||
|
* @author Martin Landsmann <martin.landsmann@haw-hamburg.de>
|
||||||
|
* @author René Kijewski <rene.kijewski@fu-berlin.de>
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <malloc.h>
|
||||||
|
|
||||||
|
#include "pthread.h"
|
||||||
|
|
||||||
|
#define ENABLE_DEBUG (0)
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
|
typedef struct __pthread_tls_datum {
|
||||||
|
pthread_key_t key;
|
||||||
|
struct __pthread_tls_datum *next;
|
||||||
|
void *value;
|
||||||
|
} tls_data_t;
|
||||||
|
|
||||||
|
struct __pthread_tls_key {
|
||||||
|
void (*destructor)(void *);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Used while manipulating the TLS of a pthread.
|
||||||
|
*/
|
||||||
|
static struct mutex_t tls_mutex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find a thread-specific datum.
|
||||||
|
* @param[in] tls Pointer to the list of the thread-specific datums.
|
||||||
|
* @param[in] key The key to look up.
|
||||||
|
* @param[out] prev The datum before the result. `NULL` if the result is the first key. Spurious if the key was not found.
|
||||||
|
* @returns The datum or `NULL`.
|
||||||
|
*/
|
||||||
|
static tls_data_t *find_specific(tls_data_t **tls, pthread_key_t key, tls_data_t **prev)
|
||||||
|
{
|
||||||
|
tls_data_t *specific = *tls;
|
||||||
|
*prev = NULL;
|
||||||
|
|
||||||
|
while (specific) {
|
||||||
|
if (specific->key == key) {
|
||||||
|
return specific;
|
||||||
|
}
|
||||||
|
|
||||||
|
*prev = specific;
|
||||||
|
specific = specific->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Find or allocate a thread specific datum.
|
||||||
|
* @details The `key` must be initialized.
|
||||||
|
* The result will be the head of the thread-specific datums afterwards.
|
||||||
|
* @param[in] key The key to lookup.
|
||||||
|
* @returns The datum. `NULL` on ENOMEM or if the caller is not a pthread.
|
||||||
|
*/
|
||||||
|
static tls_data_t *get_specific(pthread_key_t key)
|
||||||
|
{
|
||||||
|
pthread_t self_id = pthread_self();
|
||||||
|
if (self_id == 0) {
|
||||||
|
DEBUG("ERROR called pthread_self() returned 0 in \"%s\"!\n", __func__);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
tls_data_t **tls = __pthread_get_tls_head(self_id);
|
||||||
|
tls_data_t *prev, *specific = find_specific(tls, key, &prev);
|
||||||
|
|
||||||
|
/* Did the datum already exist? */
|
||||||
|
if (specific) {
|
||||||
|
if (prev) {
|
||||||
|
/* Move the datum to the front for a faster next lookup. */
|
||||||
|
/* Let's pretend that we have a totally degenerated splay tree. ;-) */
|
||||||
|
prev->next = specific->next;
|
||||||
|
specific->next = *tls;
|
||||||
|
*tls = specific;
|
||||||
|
}
|
||||||
|
return specific;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate new datum. */
|
||||||
|
specific = malloc(sizeof (*specific));
|
||||||
|
if (specific) {
|
||||||
|
specific->key = key;
|
||||||
|
specific->next = *tls;
|
||||||
|
specific->value = NULL;
|
||||||
|
*tls = specific;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DEBUG("ERROR out of memory in %s!\n", __func__);
|
||||||
|
}
|
||||||
|
return specific;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pthread_key_create(pthread_key_t *key, void (*destructor)(void *))
|
||||||
|
{
|
||||||
|
*key = malloc(sizeof (**key));
|
||||||
|
if (!*key) {
|
||||||
|
return ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*key)->destructor = destructor;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pthread_key_delete(pthread_key_t key)
|
||||||
|
{
|
||||||
|
if (!key) {
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&tls_mutex);
|
||||||
|
for (unsigned i = 1; i <= MAXTHREADS; ++i) {
|
||||||
|
tls_data_t **tls = __pthread_get_tls_head(i);
|
||||||
|
if (!tls) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
tls_data_t *prev, *specific = find_specific(tls, key, &prev);
|
||||||
|
if (specific) {
|
||||||
|
if (prev) {
|
||||||
|
prev->next = specific->next;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
*tls = specific->next;
|
||||||
|
}
|
||||||
|
free(specific);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex_unlock(&tls_mutex);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *pthread_getspecific(pthread_key_t key)
|
||||||
|
{
|
||||||
|
if (!key) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&tls_mutex);
|
||||||
|
tls_data_t *specific = get_specific(key);
|
||||||
|
void *result = specific ? specific->value : NULL;
|
||||||
|
mutex_unlock(&tls_mutex);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pthread_setspecific(pthread_key_t key, const void *value)
|
||||||
|
{
|
||||||
|
if (!key) {
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&tls_mutex);
|
||||||
|
tls_data_t *specific = get_specific(key);
|
||||||
|
if (specific) {
|
||||||
|
specific->value = (void *) value;
|
||||||
|
}
|
||||||
|
mutex_unlock(&tls_mutex);
|
||||||
|
|
||||||
|
return specific ? 0 : ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
void __pthread_keys_exit(int self_id)
|
||||||
|
{
|
||||||
|
tls_data_t **tls = __pthread_get_tls_head(self_id);
|
||||||
|
|
||||||
|
/* Calling the dtor could cause another pthread_exit(), so we dehead and free defore calling it. */
|
||||||
|
mutex_lock(&tls_mutex);
|
||||||
|
for (tls_data_t *specific; (specific = *tls); ) {
|
||||||
|
*tls = specific->next;
|
||||||
|
void *value = specific->value;
|
||||||
|
void (*destructor)(void *) = specific->key->destructor;
|
||||||
|
free(specific);
|
||||||
|
|
||||||
|
if (value && destructor) {
|
||||||
|
mutex_unlock(&tls_mutex);
|
||||||
|
destructor(value);
|
||||||
|
mutex_lock(&tls_mutex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex_unlock(&tls_mutex);
|
||||||
|
}
|
||||||
12
tests/pthread_tls/Makefile
Normal file
12
tests/pthread_tls/Makefile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
APPLICATION = pthread_tls
|
||||||
|
include ../Makefile.tests_common
|
||||||
|
|
||||||
|
BOARD_BLACKLIST := arduino-mega2560
|
||||||
|
# arduino-mega2560: unknown type name: clockid_t
|
||||||
|
|
||||||
|
USEMODULE += posix
|
||||||
|
USEMODULE += pthread
|
||||||
|
|
||||||
|
DISABLE_MODULE += auto_init
|
||||||
|
|
||||||
|
include $(RIOTBASE)/Makefile.include
|
||||||
138
tests/pthread_tls/main.c
Normal file
138
tests/pthread_tls/main.c
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2014 Hamburg University of Applied Sciences (HAW)
|
||||||
|
*
|
||||||
|
* This file is subject to the terms and conditions of the GNU Lesser
|
||||||
|
* General Public License v2.1. See the file LICENSE in the top level
|
||||||
|
* directory for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ingroup tests
|
||||||
|
* @{
|
||||||
|
*
|
||||||
|
* @file
|
||||||
|
* @brief pthread tls test application
|
||||||
|
*
|
||||||
|
* @author Martin Landsmann <martin.landsmann@haw-hamburg.de>
|
||||||
|
*
|
||||||
|
* @}
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "pthread.h"
|
||||||
|
|
||||||
|
#define NUMBER_OF_TLS (20)
|
||||||
|
|
||||||
|
void *run(void *parameter)
|
||||||
|
{
|
||||||
|
pthread_key_t aKeys[NUMBER_OF_TLS];
|
||||||
|
int aTLS_values[NUMBER_OF_TLS];
|
||||||
|
|
||||||
|
(void)parameter;
|
||||||
|
|
||||||
|
printf("\n-= TEST 1 - create %d tls with sequencial values 0...%d =-\n", NUMBER_OF_TLS, NUMBER_OF_TLS - 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < NUMBER_OF_TLS; ++i) {
|
||||||
|
aTLS_values[i] = i;
|
||||||
|
pthread_key_create(&(aKeys[i]), NULL);
|
||||||
|
pthread_setspecific(aKeys[i], &aTLS_values[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("now rise sequencial by one values 1...%d\n", NUMBER_OF_TLS);
|
||||||
|
|
||||||
|
for (int i = 0; i < NUMBER_OF_TLS; ++i) {
|
||||||
|
aTLS_values[i]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("pick deliberate storage (key[3]:%d) and change the value\n", (int)aKeys[3]);
|
||||||
|
void *val = pthread_getspecific(aKeys[3]);
|
||||||
|
*((int *)val) = 42;
|
||||||
|
|
||||||
|
printf("show tls values:\n");
|
||||||
|
|
||||||
|
for (int i = 0; i < NUMBER_OF_TLS; ++i) {
|
||||||
|
void *val = pthread_getspecific(aKeys[i]);
|
||||||
|
int x = *(int *)val;
|
||||||
|
printf("key[%d]: %d, val: %d\n",i, (int)aKeys[i], x);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n -= TEST 2 - delete deliberate key (key[5]:%d) =-\n", (int)aKeys[5]);
|
||||||
|
pthread_key_delete(aKeys[5]);
|
||||||
|
|
||||||
|
printf("show tls values:\n");
|
||||||
|
|
||||||
|
for (int i = 0; i < NUMBER_OF_TLS; ++i) {
|
||||||
|
void *val = pthread_getspecific(aKeys[i]);
|
||||||
|
|
||||||
|
if (val != NULL) {
|
||||||
|
int x = *(int *)val;
|
||||||
|
printf("key[%d]: %d, val: %d\n",i, (int)aKeys[i], x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n-= TEST 3 - create new tls =-\n");
|
||||||
|
int new_val = 99;
|
||||||
|
pthread_key_t new_key;
|
||||||
|
pthread_key_create(&new_key, NULL);
|
||||||
|
pthread_setspecific(new_key, &new_val);
|
||||||
|
|
||||||
|
printf("added new tls, key: %d, val: %d\n", (int)new_key, new_val);
|
||||||
|
printf("show tls values:\n");
|
||||||
|
|
||||||
|
for (int i = 0; i < NUMBER_OF_TLS; ++i) {
|
||||||
|
void *val = pthread_getspecific(aKeys[i]);
|
||||||
|
|
||||||
|
if (val != NULL) {
|
||||||
|
int x = *(int *)val;
|
||||||
|
printf("key[%d]: %d, val: %d\n",i, (int)aKeys[i], x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n-= TEST 4 - delete all keys =-\n");
|
||||||
|
|
||||||
|
for (int i = 0; i < NUMBER_OF_TLS; ++i) {
|
||||||
|
pthread_key_delete(aKeys[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("show tls values:\n");
|
||||||
|
|
||||||
|
for (int i = 0; i < NUMBER_OF_TLS; ++i) {
|
||||||
|
void *val = pthread_getspecific(aKeys[i]);
|
||||||
|
|
||||||
|
if (val != NULL) {
|
||||||
|
int x = *(int *)val;
|
||||||
|
printf("key[%d]: %d, val: %d\n",i, (int)aKeys[i], x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n-= TEST 5 - try delete non-existing key =-\n");
|
||||||
|
printf("try to delete returns: %d\n", pthread_key_delete((pthread_key_t)99));
|
||||||
|
|
||||||
|
printf("\n-= TEST 6 - add key and delete without a tls =-\n");
|
||||||
|
pthread_key_create(&new_key, NULL);
|
||||||
|
printf("crated key: %d\n", (int)new_key);
|
||||||
|
printf("try to delete returns: %d\n", pthread_key_delete(new_key));
|
||||||
|
|
||||||
|
printf("\n-= TEST 7 - add key without tls =-\n");
|
||||||
|
pthread_key_create(&new_key, NULL);
|
||||||
|
printf("crated key: %d\n", (int)new_key);
|
||||||
|
void* test_7_val = pthread_getspecific(new_key);
|
||||||
|
printf("test_7_val: %p\n", test_7_val);
|
||||||
|
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
pthread_t th_id;
|
||||||
|
pthread_attr_t th_attr;
|
||||||
|
|
||||||
|
pthread_attr_init(&th_attr);
|
||||||
|
pthread_create(&th_id, &th_attr, run, NULL);
|
||||||
|
|
||||||
|
size_t res;
|
||||||
|
pthread_join(th_id, (void **) &res);
|
||||||
|
puts("\ntls tests finished.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user