1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
|
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE chapter
[
<!ENTITY libagsso "libags.so.3">
<!ENTITY libagsthreadso "libags_thread.so.3">
<!ENTITY libagsserverso "libags_server.so.3">
<!ENTITY libagsaudioso "libags_audio.so.3">
]>
<!-- Copyright (C) 2005-2018 Jo\u00EBl Kr\u00E4hemann -->
<!-- Permission is granted to copy, distribute and/or modify this document -->
<!-- under the terms of the GNU Free Documentation License, Version 1.3 -->
<!-- or any later version published by the Free Software Foundation; -->
<!-- with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. -->
<!-- A copy of the license is included in the section entitled "GNU -->
<!-- Free Documentation License". -->
<chapter xmlns="http://docbook.org/ns/docbook"
xmlns:xi="http://www.w3.org/2001/XInclude" version="5.0">
<title>Multi-/Super-threaded tree</title>
<para>
Advanced Gtk+ Sequencer comes with an AgsThread object. It is organized
as a tree structure. The API provides many functions to work with it.
These threads do the ::clock event where all threads synchronize.
</para>
<para>
The AgsTaskLauncher runs synchronized as well but is going to be waited
after syncing to run all tasks. The AgsTask signal ::launch runs
asynchronous exclusively. Every thread tree shall have at toplevel a
thread implementing AgsMainLoop interface.
</para>
<para>
There is an object call AgsThreadPool serving prelaunched threads. It
returns on pull AgsReturnableThread instances. They can be used with
a callback ::safe-run.
</para>
<para>
There is a interface to implement by your application context. Thus the AgsConcurrencyProvider
interface is used. It has some common get/set functions to do basic multi-threaded work
by well defined objects.
</para>
<sect1>
<title>The main loop interface</title>
<para>
AgsMainLoop should be implemented by toplevel threads. Within a thread
tree this is the topmost element. It has various get and set methods
you would expect.
</para>
<para>
To control the AgsThread::clock signal AgsMainLoop's methods are going to be invoked.
The involved functions are:
</para>
<para>
As it shall be implemented by AGS_TYPE_THREAD subtypes, this parent object provides
a mutex to properly lock the object. You should obtain the GRecMutex pointer
by accessing its field:
<programlisting language="C">
<xi:include href="../listings/thread_obj_mutex.c" parse="text" />
</programlisting>
</para>
</sect1>
<sect1>
<title>Threads in general</title>
<para>
Libags provides a thread wrapper built on top of GLib's threading API. The AgsThread object
synchronizes the thread tree by AgsThread::clock() event. It is somekind of parallelism trap.
</para>
<imagedata fileref="../images/ags-threading.png" format="PNG" />
<para>
All threads within tree synchronize to AgsThread:max-precision per second, because all threads
shall have the very same time running in parallel. I talk of tic-based parallelism, with a max-precision
of 1000 Hz, each thread synchronizes 1000 times within tree. Giving you strong semantics to compute
a deterministic result in a multi-threaded fashion.
</para>
<para>
Since we want to run tasks exclusively without any interference from competing threads. There is a
mutex lock involved just after synchronization and then invokes ags_task_launcher_sync_run(). Be
aware the conditional lock can be evaluate to true for many threads.
</para>
<para>
After how many tics the flow is repeated depends on samplerate and buffer size. If you have an AgsThread
with max-precision 1000, samplerate of 44100 common for audio CDs and a buffer size of 512 frames, then
the delay until its repeated calculates as following:
</para>
<example>
<title>Calculating tic delay</title>
<programlisting language="C">
tic_delay = 1000.0 / 44100.0 * 512.0; // 11.609977324263039
</programlisting>
</example>
<para>
As you might have pre-/post-synchronization needing 3 tics to do its work you get 8 unused tics.
</para>
<para>
Pre-synchronization is used for reading from soundcard or MIDI device. The intermediate tic does the
actual audio processing. Post-synchronization is used by outputing to soundcard or exporting to audio file.
</para>
<para>
Within thread tree context you have to take care not to hang it up with a dead-lock.
Usually you have to use the :start_queue to start threads.
Alternatively you may want to use <code language="C">void ags_thread_start(AgsThread*)</code>.
Use :start_cond, which is protect it with :start_mutex, to notify about running thread.
</para>
<para>
The following example creates a thread and does add an other thread to :start_queue.
This causes it to be started as well. Note you want to access :start_queue using :start_mutex
to avoid data races. But there is a convience function which does it for you.
</para>
<example>
<title>Starting threads</title>
<programlisting language="C">
<xi:include href="../listings/start_thread.c" parse="text" />
</programlisting>
</example>
<para>
There many other functions not covered like mutex wrappers ags_thread_lock() and
ags_thread_unlock(). As doing a closer look to the API there are functions to lock
different parts of the tree. But all these functions should be carefully used,
since you might run into a dead-lock.
</para>
<para>
To find a specific thread type use ags_thread_find(). You can use ags_thread_self()
to retrieve your own running thread in case your using Advanced Gtk+ Sequencer
thread wrapper.
</para>
</sect1>
<sect1>
<title>Pulling threads of thread pool</title>
<para>
AgsThreadPool serves you instantiated and running threads. To pull an AgsReturnableThread
issue ags_thread_pool_pull(). The following example does instantiate a thread pool and
starts it. After, it pulls two threads and the callbacks are invoked.
</para>
<example>
<title>Pulling threads of thread-pool</title>
<programlisting language="C">
<xi:include href="../listings/pull_thread.c" parse="text" />
</programlisting>
</example>
</sect1>
<sect1>
<title>Worker-threads to do tic-less parallelism</title>
<para>
Worker threads are used to perform heavy load tasks that run completely asynchronous. This
means they don't do any sync with the tree. You start worker threads like any other thread
by calling <code language="C">void ags_thread_start(AgsThread*)</code> or
<code language="C">void ags_thread_stop(AgsThread*)</code> to stop it.
</para>
<para>
The AgsWorkerThread overrides ::start of AgsThread class and won't do any synchronization.
The worker implementation is responsible to delay computation by calling usleep() or nanosleep().
</para>
<para>
You can either connect to the ::do-poll signal or inherit of the AgsWorkerThread object. This
requires to override ::do-poll.
</para>
<sect2>
<title>Asynchronously destroy objects</title>
<para>
AgsDestroyWorker is intended to unref or free objects asynchrously. Note the use of this worker
for one certain instance, requires it to do it throughout with the worker for all unref calls.
Else you would probably end in a data-race ending in accessing a freed instance. This can especially
happen as using g_object_run_dispose().
</para>
<para>
The destroy function takes exactly one parameter like g_free() or g_object_unref(). To add an entry
call ags_destroy_worker_add(). The first parameter is the worker, second the pointer to free/unref
and third the destroy function.
</para>
</sect2>
</sect1>
<sect1>
<title>Launching tasks</title>
<para>
It's for thread-safety for sure to run tasks asynchronously exclusive. This means what ever
you do it's safe exceptional in view of third-party libraries that might have their own threads.
To do your own task you should inherit AgsTask base object and implement ::launch. This signal
is invoked after syncing the thread tree.
</para>
<para>
You can use either ags_task_launcher_add_task() or ags_task_launcher_add_task_all() to add one
respectively a GList of tasks. The task shall report failures by calling ::failure signal.
</para>
</sect1>
<sect1>
<title>Async message delivery</title>
<para>
AgsMessageDelivery is a singleton. In order to get the instance of it call
<code language="C">AgsMessageDelivery* ags_message_delivery_get_instance()</code>.
The library routines only provide messages until you have added an AgsMessageQueue with the
appropriate namespace.
</para>
<itemizedlist>
<listitem>
libags - namespace used by &libagsso;, &libagsthreadso; and &libagsserverso;
</listitem>
<listitem>
libags-audio - namespace used by &libagsaudioso;
</listitem>
</itemizedlist>
<para>
As you usually have one object or widget mapped to a specific object, you can poll the queue
by <code language="C">guint g_timeout_add(guint, GSourceFunc, gpointer)</code>. Then forward
the event as you like. GSequencer does look for matching messages by sender using following
<code language="C">GList* ags_message_queue_find_sender(AgsMessageQueue*, GObject*)</code>.
This not at least because the recipient is most of the time not defined.
</para>
</sect1>
</chapter>
|