mirror of
https://github.com/mpv-player/mpv
synced 2025-03-25 04:38:01 +00:00
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:
parent
383da1bfd5
commit
22c002138d
62
misc/thread_tools.c
Normal file
62
misc/thread_tools.c
Normal 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
39
misc/thread_tools.h
Normal 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);
|
@ -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" ),
|
||||
|
Loading…
Reference in New Issue
Block a user