mirror of /home/gitosis/repositories/libev.git
*** empty log message ***
parent
f3845e53fe
commit
c2c6a511a3
6
Changes
6
Changes
|
@ -2,8 +2,12 @@ Revision history for libev, a high-performance and full-featured event loop.
|
|||
|
||||
TODO: ev_walk
|
||||
TODO: remain
|
||||
TODO: on_call_pending, on_suspend_resume ev_invoke_pending (EV_P)
|
||||
TODO: EV_MINIMAL
|
||||
|
||||
- new functionality: ev_set_userdata, ev_userdata,
|
||||
ev_set_invoke_pending_cb, ev_set_loop_release_cb,
|
||||
ev_invoke_pending, together with a long example about thread
|
||||
locking.
|
||||
- ev_unloop and ev_loop wrongly used a global variable to exit loops,
|
||||
instead of using a per-loop variable (bug caught by accident...).
|
||||
- calling ev_unloop in fork/prepare watchers will no longer poll
|
||||
|
|
145
ev.pod
145
ev.pod
|
@ -892,6 +892,19 @@ afterwards.
|
|||
Ideally, C<release> will just call your mutex_unlock function, and
|
||||
C<acquire> will just call the mutex_lock function again.
|
||||
|
||||
While event loop modifications are allowed between invocations of
|
||||
C<release> and C<acquire> (that's their only purpose after all), no
|
||||
modifications done will affect the event loop, i.e. adding watchers will
|
||||
have no effect on the set of file descriptors being watched, or the time
|
||||
waited. USe an C<ev_async> watcher to wake up C<ev_loop> when you want it
|
||||
to take note of any changes you made.
|
||||
|
||||
In theory, threads executing C<ev_loop> will be async-cancel safe between
|
||||
invocations of C<release> and C<acquire>.
|
||||
|
||||
See also the locking example in the C<THREADS> section later in this
|
||||
document.
|
||||
|
||||
=item ev_set_userdata (loop, void *data)
|
||||
|
||||
=item ev_userdata (loop)
|
||||
|
@ -3930,6 +3943,138 @@ watcher callback into the event loop interested in the signal.
|
|||
|
||||
=head4 THREAD LOCKING EXAMPLE
|
||||
|
||||
Here is a fictitious example of how to run an event loop in a different
|
||||
thread than where callbacks are being invoked and watchers are
|
||||
created/added/removed.
|
||||
|
||||
For a real-world example, see the C<EV::Loop::Async> perl module,
|
||||
which uses exactly this technique (which is suited for many high-level
|
||||
languages).
|
||||
|
||||
The example uses a pthread mutex to protect the loop data, a condition
|
||||
variable to wait for callback invocations, an async watcher to notify the
|
||||
event loop thread and an unspecified mechanism to wake up the main thread.
|
||||
|
||||
First, you need to associate some data with the event loop:
|
||||
|
||||
typedef struct {
|
||||
mutex_t lock; /* global loop lock */
|
||||
ev_async async_w;
|
||||
thread_t tid;
|
||||
cond_t invoke_cv;
|
||||
} userdata;
|
||||
|
||||
void prepare_loop (EV_P)
|
||||
{
|
||||
// for simplicity, we use a static userdata struct.
|
||||
static userdata u;
|
||||
|
||||
ev_async_init (&u->async_w, async_cb);
|
||||
ev_async_start (EV_A_ &u->async_w);
|
||||
|
||||
pthread_mutex_init (&u->lock, 0);
|
||||
pthread_cond_init (&u->invoke_cv, 0);
|
||||
|
||||
// now associate this with the loop
|
||||
ev_set_userdata (EV_A_ u);
|
||||
ev_set_invoke_pending_cb (EV_A_ l_invoke);
|
||||
ev_set_loop_release_cb (EV_A_ l_release, l_acquire);
|
||||
|
||||
// then create the thread running ev_loop
|
||||
pthread_create (&u->tid, 0, l_run, EV_A);
|
||||
}
|
||||
|
||||
The callback for the C<ev_async> watcher does nothing: the watcher is used
|
||||
solely to wake up the event loop so it takes notice of any new watchers
|
||||
that might have been added:
|
||||
|
||||
static void
|
||||
async_cb (EV_P_ ev_async *w, int revents)
|
||||
{
|
||||
// just used for the side effects
|
||||
}
|
||||
|
||||
The C<l_release> and C<l_acquire> callbacks simply unlock/lock the mutex
|
||||
protecting the loop data, respectively.
|
||||
|
||||
static void
|
||||
l_release (EV_P)
|
||||
{
|
||||
udat *u = ev_userdata (EV_A);
|
||||
pthread_mutex_unlock (&u->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
l_acquire (EV_P)
|
||||
{
|
||||
udat *u = ev_userdata (EV_A);
|
||||
pthread_mutex_lock (&u->lock);
|
||||
}
|
||||
|
||||
The event loop thread first acquires the mutex, and then jumps straight
|
||||
into C<ev_loop>:
|
||||
|
||||
void *
|
||||
l_run (void *thr_arg)
|
||||
{
|
||||
struct ev_loop *loop = (struct ev_loop *)thr_arg;
|
||||
|
||||
l_acquire (EV_A);
|
||||
pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0);
|
||||
ev_loop (EV_A_ 0);
|
||||
l_release (EV_A);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Instead of invoking all pending watchers, the C<l_invoke> callback will
|
||||
signal the main thread via some unspecified mechanism (signals? pipe
|
||||
writes? C<Async::Interrupt>?) and then waits until all pending watchers
|
||||
have been called:
|
||||
|
||||
static void
|
||||
l_invoke (EV_P)
|
||||
{
|
||||
udat *u = ev_userdata (EV_A);
|
||||
|
||||
wake_up_other_thread_in_some_magic_or_not_so_magic_way ();
|
||||
|
||||
pthread_cond_wait (&u->invoke_cv, &u->lock);
|
||||
}
|
||||
|
||||
Now, whenever the main thread gets told to invoke pending watchers, it
|
||||
will grab the lock, call C<ev_invoke_pending> and then signal the loop
|
||||
thread to continue:
|
||||
|
||||
static void
|
||||
real_invoke_pending (EV_P)
|
||||
{
|
||||
udat *u = ev_userdata (EV_A);
|
||||
|
||||
pthread_mutex_lock (&u->lock);
|
||||
ev_invoke_pending (EV_A);
|
||||
pthread_cond_signal (&u->invoke_cv);
|
||||
pthread_mutex_unlock (&u->lock);
|
||||
}
|
||||
|
||||
Whenever you want to start/stop a watcher or do other modifications to an
|
||||
event loop, you will now have to lock:
|
||||
|
||||
ev_timer timeout_watcher;
|
||||
udat *u = ev_userdata (EV_A);
|
||||
|
||||
ev_timer_init (&timeout_watcher, timeout_cb, 5.5, 0.);
|
||||
|
||||
pthread_mutex_lock (&u->lock);
|
||||
ev_timer_start (EV_A_ &timeout_watcher);
|
||||
ev_async_send (EV_A_ &u->async_w);
|
||||
pthread_mutex_unlock (&u->lock);
|
||||
|
||||
Note that sending the C<ev_async> watcher is required because otherwise
|
||||
an event loop currently blocking in the kernel will have no knowledge
|
||||
about the newly added timer. By waking up the loop it will pick up any new
|
||||
watchers in the next event loop iteration.
|
||||
|
||||
=head3 COROUTINES
|
||||
|
||||
Libev is very accommodating to coroutines ("cooperative threads"):
|
||||
|
|
Loading…
Reference in New Issue