File: chap3.xml

package info (click to toggle)
gsequencer 7.7.5-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 71,040 kB
  • sloc: ansic: 1,145,949; xml: 31,016; cpp: 9,487; sh: 5,798; makefile: 3,845; perl: 155; sed: 16; python: 11
file content (241 lines) | stat: -rw-r--r-- 9,900 bytes parent folder | download | duplicates (2)
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&apos;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&apos;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&apos;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&apos;s for thread-safety for sure to run tasks asynchronously exclusive. This means what ever
      you do it&apos;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>