On Wed, Apr 03, 2013 at 01:19:31PM -0400, Kevyn-Alexandre Paré
<kap...@rogue-research.com> wrote:
>
> As my past habits was to always create a thread when something requires
> more processing time. I'm a bit confused to when to use thread with
> libev and how to manage longer tasks. My first thought is that I have
> started to use libev to remove the overhead of thread, context switching
> and mutex.

Well, the major thing to keep in mind is that libev is "just" an event
lib, and doesn't try to do anything else. It does, however, try to give
you all the options, i.e., it provides ev_async watchers so you can signal
it from other threads, and it provides the hooks required to add locking
when that is deemed necessary.

Indeed, you remove the thread switching overhead (which often involves the
kernel and thus is higher than the overhead introduced by multiplexing
between callbacks), and you don't need mutexes to start other thtreads and
let them signal e.g. completion or other events, unless you touch shared
data structures.

In return, however, you will run into latency problems for long-running
jobs.

There are many ways to deal with this, and in ym experienc,e there is no
best way.

For example, a commonly overlooked method is to run multiple threads, and,
when a callback "realises" that it takes longer, it can hand off the libev
loop to another thread to continue, which gives more flexibility.

If all you have is a few long running jobs and most things are fast, then
starting threads in the background might be easier (and possibly fater as
well).

In perl, I often use cooperative (userspace) threads (using Coro, which
uses libcoro) and IO::AIO (== libeio) together with EV (== libev). While
perl is much slower than C, it is also way more convenient, often allowing
me to express an algorithm that overall gives lower response time that I
wouldn't bother implementing in C, because it would feel so anal.

What I mean here is that often it might pay off to use some high level
scripting language for program logic, if that allows me to express algorithms
conveniently.

(Of course, one can do the same in C, but without sugaring for features
like closures it's often quite painful).

Now, to come back to the background jobs - in perl's Coro for example,
threads that run at a lower priority than the thread running the event
loop will be time-multiplexed with event fetching, that is, when such a
thread blocks or yields, the event loop runs to fetch new events and so
on.

The same effect can be had by having an idle watcher with a callback that
does "some work" and then waits for the next idle event. OR by using some
cooperative threads for C etc. etc.

I am not recommending this, just giving some ideas.

> 1- How long could I stay in a callback (ex: io watcher callback) and
> process data until the rest of my system become non responsive (ex:
> server)?

Well, as long as it takes to beocme unresponsive. What is considered
"unresponsive" depends heavily on the application. Libev sets some
limits to responsiveness, for example, it doesn't guarantee a very high
resolution for timers, and event processing can take an unbounded time in
theory (proportional to the event load of course), but mostly, the
responsiveness is limited by the callbacks.

Obviously, if your callback is doing some blocking database lookup, or
some disk access, you are in for a potentially very long wait.

However, you can basically always know in advance whether an opertaion
potentially takes a lot of time, and can take precautions by pushing it to
the backgroud.

> 2- Will it be preferable to simply start a thread if we know that the
> processing could go higher to a certain threshold?

If your threshhold exceeds the limit on responsiveness you want to endure,
then starting a thread might help. Or using a threadpool, to reduce thread
start overhead. Or pass libev to another thread, which makes it easier to
only switch to another thread when needed, as you can dynamically decide
whether to continue event processing or hand off event processing to
another thread.

> 3- How to determine that acceptable threshold?

Well, if you have some medical machine and the patient dies if you don't
respond within a second, then the acceptable threshold is lower than one
second.

In practise you usually don't have such hard limits, and all you want to
do is make sure you don't have very long avoidable pauses.

That means that, for the most part, there are some obviously unbounded
operations, such as waiting fro the disk (which can take 0.01s, but can
also take seconds when some other process does heavy disk activity, or
even many minutes when linux thinks you suck and need to suffer), or waiting
for a database (which might involve the disk), or waiting for data to arrive
on a network socket.

libev can help with the latter directly, and with the former ones
indirectly, as long as your database library, I/O library etc. can work in
an event-based fashion.

> Do you have some thought on that subject?

Lots :) But it's all down to experience (to gather), and mostly depends on
the problem at hand.

I would suggets starting either with having multiple threads and handing
over libev to another thread before startign along operation, or using
some existing threadpool (no recommendations here) and ev_async to signal
completion, or simply by starting a new thread each time you need a
long-running job, and seeing if that is fats enough for you.

Of coruse, you can hardly avoid some mutexes and context switching when
using threads, but at least you can reduce them to when they are really
needed.

-- 
                The choice of a       Deliantra, the free code+content MORPG
      -----==-     _GNU_              http://www.deliantra.net
      ----==-- _       generation
      ---==---(_)__  __ ____  __      Marc Lehmann
      --==---/ / _ \/ // /\ \/ /      schm...@schmorp.de
      -=====/_/_//_/\_,_/ /_/\_\

_______________________________________________
libev mailing list
libev@lists.schmorp.de
http://lists.schmorp.de/cgi-bin/mailman/listinfo/libev

Reply via email to