misc: add a synchronization helper

This is almost like rendezvous(), except it allows async wakeup, and
does not require global state. It will be used by a later commit.

struct mp_waiter is intended to be allocated on the stack, and uses an
initializer including PTHREAD_MUTEX_INITIALIZER. This is the first case
in mpv that it uses PTHREAD_MUTEX_INITIALIZER for stack-allocated
mutexes. It seems POSIX still does not allow this formally, but since
POSIX is worth less than used toilet paper, I don't really care. Modern
OSes use futexes, which means you can make _every_ memory location a
lock, and this code tries to make use of it, without using OS specific
code.

The name of the source file is rather generic, because I intend to dump
further small helpers there (or maybe move mp_rendezvous() to it).
This commit is contained in:
wm4 2018-05-06 13:00:05 +02:00
parent 383da1bfd5
commit 22c002138d
3 changed files with 102 additions and 0 deletions

62
misc/thread_tools.c Normal file
View File

@ -0,0 +1,62 @@
/* Copyright (C) 2018 the mpv developers
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <assert.h>
#include <string.h>
#include "thread_tools.h"
uintptr_t mp_waiter_wait(struct mp_waiter *waiter)
{
pthread_mutex_lock(&waiter->lock);
while (!waiter->done)
pthread_cond_wait(&waiter->wakeup, &waiter->lock);
pthread_mutex_unlock(&waiter->lock);
uintptr_t ret = waiter->value;
// We document that after mp_waiter_wait() the waiter object becomes
// invalid. (It strictly returns only after mp_waiter_wakeup() has returned,
// and the object is "single-shot".) So destroy it here.
// Normally, we expect that the system uses futexes, in which case the
// following functions will do nearly nothing. This is true for Windows
// and Linux. But some lesser OSes still might allocate kernel objects
// when initializing mutexes, so destroy them here.
pthread_mutex_destroy(&waiter->lock);
pthread_cond_destroy(&waiter->wakeup);
memset(waiter, 0xCA, sizeof(*waiter)); // for debugging
return ret;
}
void mp_waiter_wakeup(struct mp_waiter *waiter, uintptr_t value)
{
pthread_mutex_lock(&waiter->lock);
assert(!waiter->done);
waiter->done = true;
waiter->value = value;
pthread_cond_signal(&waiter->wakeup);
pthread_mutex_unlock(&waiter->lock);
}
bool mp_waiter_poll(struct mp_waiter *waiter)
{
pthread_mutex_lock(&waiter->lock);
bool r = waiter->done;
pthread_mutex_unlock(&waiter->lock);
return r;
}

39
misc/thread_tools.h Normal file
View File

@ -0,0 +1,39 @@
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include <pthread.h>
// This is basically a single-shot semaphore, intended as light-weight solution
// for just making a thread wait for another thread.
struct mp_waiter {
// All fields are considered private. Use MP_WAITER_INITIALIZER to init.
pthread_mutex_t lock;
pthread_cond_t wakeup;
bool done;
uintptr_t value;
};
// Initialize a mp_waiter object for use with mp_waiter_*().
#define MP_WAITER_INITIALIZER { \
.lock = PTHREAD_MUTEX_INITIALIZER, \
.wakeup = PTHREAD_COND_INITIALIZER, \
}
// Block until some other thread calls mp_waiter_wakeup(). The function returns
// the value argument of that wakeup call. After this, the waiter object must
// not be used anymore. Although you can reinit it with MP_WAITER_INITIALIZER
// (then you must make sure nothing calls mp_waiter_wakeup() before this).
uintptr_t mp_waiter_wait(struct mp_waiter *waiter);
// Unblock the thread waiting with mp_waiter_wait(), and make it return the
// provided value. If the other thread did not enter that call yet, it will
// return immediately once it does (mp_waiter_wakeup() always returns
// immediately). Calling this more than once is not allowed.
void mp_waiter_wakeup(struct mp_waiter *waiter, uintptr_t value);
// Query whether the waiter was woken up. If true, mp_waiter_wait() will return
// immediately. This is useful if you want to use another way to block and
// wakeup (in parallel to mp_waiter).
// You still need to call mp_waiter_wait() to free resources.
bool mp_waiter_poll(struct mp_waiter *waiter);

View File

@ -323,6 +323,7 @@ def build(ctx):
( "misc/rendezvous.c" ),
( "misc/ring.c" ),
( "misc/thread_pool.c" ),
( "misc/thread_tools.c" ),
## Options
( "options/m_config.c" ),