<?xml version="1.0" encoding="utf-8"?>
<page xmlns="http://projectmallard.org/1.0/" xmlns:its="http://www.w3.org/2005/11/its" xmlns:xi="http://www.w3.org/2003/XInclude" type="topic" id="main-contexts" xml:lang="cs">
<info>
<link type="guide" xref="index#specific-how-tos"/>
<credit type="author copyright">
<name>Philip Withnall</name>
<email its:translate="no">philip.withnall@collabora.co.uk</email>
<years>2014 – 2015</years>
</credit>
<include xmlns="http://www.w3.org/2001/XInclude" href="cc-by-sa-3-0.xml"/>
<desc>Hlavní kontext GLib, vyvolání funkcí v jiných vláknech a smyčka událostí</desc>
</info>
<title>Hlavní kontexty GLib</title>
<synopsis>
<title>Shrnutí</title>
<list>
<item><p>K vyvolání funkcí z jiných vláknech použijte funkci <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-main-context-invoke-full"><code>g_main_context_invoke_full()</code></link>, která předpokládá, že každé vlákno má výchozí hlavní kontext, který běží po celou dobu existence vlákna (<link xref="#g-main-context-invoke-full"/>)</p></item>
<item><p>Ke spuštění funkce na pozadí bez starostí o použití konkrétního vlákna použijte <link href="https://developer.gnome.org/gio/stable/GTask.html"><code>GTask</code></link> (<link xref="#gtask"/>)</p></item>
<item><p>Nešetřete používáním asercí ke kontrole, který kontext spouští kterou funkci, a přidávejte tyto aserce hned při prvním psaní kódu. (<link xref="#checking-threading"/>)</p></item>
<item><p>Výslovně zdokumentujte, ve kterém kontextu funkce očekává, že bude volána, zpětné volání vyvoláno a signál vyslán (<link xref="#using-gmaincontext-in-a-library"/>)</p></item>
<item><p>Dávejte si pozor na <code>g_idle_add()</code> a podobné funkce, které implicitně používají globální výchozí hlavní kontext (<link xref="#implicit-use-of-the-global-default-main-context"/>)</p></item>
</list>
</synopsis>
<section id="what-is-gmaincontext">
<title>Co je to <code>GMainContext</code>?</title>
<p><link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#GMainContext"><code>GMainContext</code></link> je zobecněná implementace <link href="http://en.wikipedia.org/wiki/Event_loop">smyčky událostí</link>, která je vhodná pro implementaci skupinových V/V souborových operací nebo systému widgetů založeného na událostech (jako je GTK+). Je jádrem většiny aplikací GLib. Pochopení <code>GMainContext</code> vyžaduje pochopení <link href="man:poll(2)">poll()</link> a skupinových V/V.</p>
<p><code>GMainContext</code> má sadu objektů <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#GSource"><code>GSource</code></link>, které jsou k němu „připojené“. O každém z nich můžeme přemýšlet jako o očekávané události s přiřazenou funkcí zpětného volání, která bude vyvolána, když je událost přijata, nebo obdobně, jako o sadě popisovačů souborů, které se mají kontrolovat. Událostí může být například doběhnutí časovače nebo přijetí dat na soketu. Každá jednotlivá iterace smyčky události:</p>
<list type="enumerated">
<item><p>Připraví zdroje, přičemž určí, jestli je některý z nich připraven okamžitě vysílat.</p></item>
<item><p>Seskupí zdroje a zablokuje aktuální vlákno, dokud není přijata událost pro jeden ze zdrojů.</p></item>
<item><p>Zkontroluje, který ze zdrojů přijal událost (může jich být několik).</p></item>
<item><p>Vyšle zpětná volání z těchto zdrojů.</p></item>
</list>
<p><link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#mainloop-states">Velmi dobře je to vysvětlené</link> v <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#GSourceFuncs">dokumentaci k GLib</link>.</p>
<p>V jádru je <code>GMainContext</code> vlastně jen smyčka <code>poll()</code> s přípravnou, kontrolní a vysílací fází smyčky odpovídající běžnému úvodu a závěru v typické implementaci smyčky <code>poll()</code>, jako je například naslouchání 1 v <link href="http://www.linux-mag.com/id/357/">tomto článku</link>. Typicky je zapotřebí jistá míra složitosti v aplikacích s netriviálním využitím <code>poll()</code> ke sledování seznamů popisovačů souborů, které nás zajímají. <code>GMainContext</code> navíc přidává spoustu užitečné funkcionality, kterou varianta <code>poll()</code> nepodporuje. Z toho nejdůležitější je bezpečné použití ve více vláknech.</p>
<p><code>GMainContext</code> je z hlediska vláken zcela bezpečný, což znamená, že <code>GSource</code> může být vytvořen v jednom vlákně a napojen na <code>GMainContext</code> běžící v jiném vlákně. (Viz také <link xref="threading"/>). Typickým využitím je umožnit pracovnímu vláknu řídit, kterým soketům <code>GMainContext</code> naslouchá v centrálním V/V vlákně. Každý <code>GMainContext</code> je „získán“ vláknem pro každou iteraci, kterou prochází. Ostatní vlákna nemohou přes <code>GMainContext</code> iterovat bez jeho získání, což zajistí, že <code>GSource</code> a jeho popisovače souborů budou seskupeny jen jedním vláknem v každý okamžik (protože každý <code>GSource</code> je napojen nejvýše na jeden kontext <code>GMainContext</code>). <code>GMAinContext</code> může být během iterací napříč vlákny přehazován, ale je to nákladná operace.</p>
<p><code>GMainContext</code> se používá místo <code>poll()</code> hlavně kvůli pohodlí, protože transparentně obsluhuje dynamickou správu polí s popisovači souborů předávanými do <code>poll()</code>, hlavně když se pracuje napříč více vlákny. Provádí se to zapouzdřením popisovačů polí do objektů <code>GSource</code>, které rozhodují, jestli mají být tyto popisovače souborů předány do volání <code>poll()</code> v každé „přípravné“ fázi iterace hlavního kontextu.</p>
</section>
<section id="what-is-gmainloop">
<title>Co je to <code>GMainLoop</code>?</title>
<p>Když si odmyslíme počítání referencí a zamykání, je <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#GMainLoop"><code>GMainLoop</code></link> v podstatě jen pár následujících řádků kódu (z <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-main-loop-run"><code>g_main_loop_run()</code></link>):</p>
<code mime="text/x-csrc">loop->is_running = TRUE;
while (loop->is_running)
{
g_main_context_iteration (context, TRUE);
}</code>
<p>Plus čtyři řádky v <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-main-loop-quit"><code>g_main_loop_quit()</code></link>, které nastaví <code>loop->is_running = FALSE</code> a které způsobí, že se smyčka ukončí, jakmile doběhne aktuální iterace hlavního kontextu.</p>
<p>Proto je <code>GMainLoop</code> vhodným, a z hlediska vláken bezpečným, způsobem, jak spustit <code>GMainContext</code>, aby zpracovával události, dokud není dosažena požadovaná výstupní podmínka. V tu chvíli by měla být zavolána funkce <code>g_main_loop_quit()</code>. V programech s uživatelským rozhraním je to typicky uživatelovo kliknutí na „Ukončit“. V programech obsluhujících sokety do může být závěrečné zavření soketu.</p>
<p>Je důležité neplést si hlavní kontext s hlavní smyčkou. Hlavní kontext dělá většinu práce: připravuje seznam zdrojů, čeká na události a obesílá zpětná volání. Hlavní smyčka prostě jen opakovaně cykluje kontextem.</p>
</section>
<section id="default-contexts">
<title>Výchozí kontexty</title>
<p>Jednou z důležitých vlastností <code>GMainContext</code> je, že podporuje „výchozí“ kontext. Existují dvě úrovně výchozího kontextu: výchozí pro vlákno a výchozí globální. Globální (přístupný přes <code>g_main_context_default()</code>) je spuštěn GTK+ ve chvíli, kdy je zavoláno <code>gtk_main()</code>. Používá se také pro časovače (<code>g_timeout_add()</code>) a zpětná volání při nečinnosti (<code>g_idle_add()</code>) — ty nechceme obesílat, když neběží výchozí kontext! (Viz <link xref="#implicit-use-of-the-global-default-main-context"/>.)</p>
<p>Výchozí kontexty vláken byly do GLib přidány později (od verze 2.22) a obecně se používají pro V/V operace, které potřebují spouštět a obesílat zpětná volání ve vlákně. Zavoláním <code>g_main_context_push_thread_default()</code> před spuštěním V/V operace se nastaví výchozí kontext vlákna a V/V operace může přidat své zdroje do tohoto kontextu. Kontext pak může být spuštěn v nové hlavní smyčce ve V/V vlákně, což způsobí, že zpětná volání budou obesílána v zásobníku tohoto vlákna namísto v zásobníku vlákna, ve kterém běží globální výchozí kontext. To umožňuje, aby V/V operace běžely celé v odděleném vlákně bez vyloženého předávání konkrétního ukazatele <code>GMainContext</code> všude kolem.</p>
<p>Naopak, spuštěním dlouho trvající operace s nastaveným konkrétním výchozím kontextem vlákna, může volající kód zaručit, že zpětná volání operace budou vyslána v tomto kontextu, i když operace samotná běží v pracovním vlákně. Jde o princip stojící za <link href="https://developer.gnome.org/gio/stable/GTask.html"><code>GTask</code></link>: když je vytvořen nový objekt <code>GTask</code>, uloží si reference na výchozí kontext aktuálního vlákna a a vyšle jeho dokončovací zpětné volání v tomto kontextu, i když úloha samotná běží pomocí <link href="https://developer.gnome.org/gio/stable/GTask.html#g-task-run-in-thread"><code>g_task_run_in_thread()</code></link>.</p>
<example>
<p>Například následující kód spustí <code>GTask</code>, který provede z vlákna souběžně dva zápisy. Zpětná volání pro zápisy budou obeslána v pracovním vlákně, zatímco zpětné volání z úlohy jako celku bude obesláno v <code>interesting_context</code>.</p>
<code mime="text/x-csrc" style="valid">
typedef struct {
GMainLoop *main_loop;
guint n_remaining;
} WriteData;
/* Toto je voláno vždy ve stejném vlákně jako thread_cb(), protože je to
* vždy vysláno v kontextu @worker_context. */
static void
write_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
WriteData *data = user_data;
GOutputStream *stream = G_OUTPUT_STREAM (source_object);
GError *error = NULL;
gssize len;
/* Dokončí zápis */
len = g_output_stream_write_finish (stream, result, &error);
if (error != NULL)
{
g_error ("Error: %s", error->message);
g_error_free (error);
}
/* Zkontroluje, jestli byly dokončeny všechny paralelní operace */
write_data->n_remaining--;
if (write_data->n_remaining == 0)
{
g_main_loop_quit (write_data->main_loop);
}
}
/* Toto je voláno v novém vlákně */
static void
thread_cb (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
/* Tento datový proud přichází z jiného místa v programu: */
GOutputStream *output_stream1, *output_stream;
GMainContext *worker_context;
GBytes *data;
const guint8 *buf;
gsize len;
/* Nastaví pracovní kontext pro zpětná volání zápisu */
worker_context = g_main_context_new ();
g_main_context_push_thread_default (worker_context);
/* Nastaví zápis */
write_data.n_remaining = 2;
write_data.main_loop = g_main_loop_new (worker_context, FALSE);
data = g_task_get_task_data (task);
buf = g_bytes_get_data (data, &len);
g_output_stream_write_async (output_stream1, buf, len,
G_PRIORITY_DEFAULT, NULL, write_cb,
&write_data);
g_output_stream_write_async (output_stream2, buf, len,
G_PRIORITY_DEFAULT, NULL, write_cb,
&write_data);
/* Spustí hlavní smyčku, dokud nejsou dokončeny oba zápisy */
g_main_loop_run (write_data.main_loop);
g_task_return_boolean (task, TRUE); /* ignore errors */
g_main_loop_unref (write_data.main_loop);
g_main_context_pop_thread_default (worker_context);
g_main_context_unref (worker_context);
}
/* Toto může být voláno z kteréhokoliv vlákna. Jeho funkce @callback bude
* vždy vyslána ve vlákně, které aktuálně vlastní @interesting_context */
void
parallel_writes_async (GBytes *data,
GMainContext *interesting_context,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
GTask *task;
g_main_context_push_thread_default (interesting_context);
task = g_task_new (NULL, cancellable, callback, user_data);
g_task_set_task_data (task, data,
(GDestroyNotify) g_bytes_unref);
g_task_run_in_thread (task, thread_cb);
g_object_unref (task);
g_main_context_pop_thread_default (interesting_context);
}</code>
</example>
<section id="implicit-use-of-the-global-default-main-context">
<title>Výchozí použití globálního výchozího hlavního kontextu</title>
<p>Několik funkcí přidává zdroje do globálního výchozího hlavního kontextu implicitně. Ty <em>nesmí</em> být použity v kódu vláken. Místo toho použijte <code>g_source_attach()</code> s <code>GSource</code> vytvořeným nahrazením funkce z tabulky níže.</p>
<p>Implicitní použití globálního výchozího hlavního kontextu znamená, že jsou funkce zpětného volání vyvolány v hlavním vlákně, typicky jako výsledek práce práce přenesené zpět z pracovního vlákna do hlavního vlákna.</p>
<table shade="rows">
<colgroup><col/></colgroup>
<colgroup><col/><col/></colgroup>
<thead>
<tr>
<td><p>Nepoužívat</p></td>
<td><p>Místo toho použít</p></td>
</tr>
</thead>
<tbody>
<tr>
<td><p><code>g_timeout_add()</code></p></td>
<td><p><code>g_timeout_source_new()</code></p></td>
</tr>
<tr>
<td><p><code>g_idle_add()</code></p></td>
<td><p><code>g_idle_source_new()</code></p></td>
</tr>
<tr>
<td><p><code>g_child_watch_add()</code></p></td>
<td><p><code>g_child_watch_source_new()</code></p></td>
</tr>
</tbody>
</table>
<example>
<p>Takže prodlevu v nějakém výpočtu v pracovním vlákně uděláte pomocí následujícího kódu:</p>
<code mime="text/x-csrc">
static guint
schedule_computation (guint delay_seconds)
{
GSource *source = NULL;
GMainContext *context;
guint id;
/* Získat kontext volání. */
context = g_main_context_get_thread_default ();
source = g_timeout_source_new_seconds (delay_seconds);
g_source_set_callback (source, do_computation, NULL, NULL);
id = g_source_attach (source, context);
g_source_unref (source);
/* Když je potřeba, může být ID použito se stejným
* @context ke zrušení naplánovaného výpočtu. */
return id;
}
static void
do_computation (gpointer user_data)
{
/* … */
}</code>
</example>
</section>
</section>
<section id="using-gmaincontext-in-a-library">
<title>Použití <code>GMainContext</code> v knihovně</title>
<p>Na nejvyšší úrovni kód knihovny nesmí provádět změny v hlavním kontextu, který by mohly ovlivnit provádění aplikace, která používá knihovnu. Například změnou, když je vyslán <code>GSource</code> patřící aplikaci. Existuje řada správných zvyklostí, kterých je dobré se držet, aby tomu tak bylo.</p>
<p>Nikdy neprovádějte iteraci kontextu vytvořeného mimo knihovnu. Týká se to i výchozího globálního kontextu a výchozího kontextu vlákna. V opačném případě může být <code>GSource</code> vytvořený v aplikaci vyslán, když to aplikace neočekává, což způsobí <link href="http://en.wikipedia.org/wiki/Reentrancy_%28computing%29">problém s opětovným vstupem (reentrance)</link> v kódu aplikace.</p>
<p>Před zahozením poslední reference z knihovny na kontext vždy odstraňte z hlavního kontextu zdroje <code>GSource</code>, především když mohou být vystaveny aplikaci (například jako výchozí vlákno). V opačném případě může aplikace zachovat referenci na hlavní kontext a pokračovat s jeho iteracemi po návratu z knihovny, což případně může způsobit nechtěné vysílání zdroje v knihovně. Je to to stejné, jako když nepředpokládáte, že zahození poslední reference z knihovny na hlavní kontext tento kontext finalizuje.</p>
<p>Pokud je knihovna navržena pro použití z více vláken nebo ve stylu kontextů, zdokumentujte vždy, ve kterém kontextu budou jednotlivá zpětná volání vysílána. Například „callbacks will always be dispatched in the context which is the thread-default at the time of the object’s construction“ (zpětná volání budou v době vytváření objektu vždy vyslána v kontextu, který je pro vlákno výchozí). Vývojáři používající API knihovny tuto informaci potřebují.</p>
<p>Používejte <code>g_main_context_invoke()</code>, abyste zajistili, že zpětné volání bude vysláno ve správném kontextu. Je to mnohem jednodušší, než ruční použití <code>g_idle_source_new()</code> k přenosu práce mezi kontexty. (Viz <link xref="#ensuring-functions-are-called-in-the-right-context"/>.)</p>
<p>Knihovny by nikdy neměly použít <code>g_main_context_default()</code> (nebo udělat to stejné předáním <code>NULL</code> do parametru typu <code>GMainContext</code>). Vždy uložte a výslovně použijte konkrétní <code>GMainContext</code>, i když často ukazuje na nějaký výchozí kontext. V případě potřeby to v budoucnu usnadní rozdělení kódu do vláken, aniž by to způsobilo těžko laditelné probléme vznikající zpětnými voláními vyvolanými v nesprávném kontextu.</p>
<p>Věci vždy pište interně jako asynchronní (kde je to vhodné, tak pomocí <link xref="#gtask"><code>GTask</code></link>) a na nejvyšší úrovni API pak udržujte synchronní obalující funkci, kterou můžete implementovat pomocí volání <code>g_main_context_iteration()</code> na příslušný <code>GMainContext</code>. Pro zopakování: umožní to v budoucnu snadnější přepracování kódu. Ukázáno je to na příkladu výše: vlákno používá raději <code>g_output_stream_write_async()</code> namísto <code>g_output_stream_write()</code>.</p>
<p>Vždy si musí odpovídat počet vložení a vyjmutí výchozího hlavního kontextu vlákna: <code>g_main_context_push_thread_default()</code> a <code>g_main_context_pop_thread_default()</code>.</p>
</section>
<section id="ensuring-functions-are-called-in-the-right-context">
<title>Zajištění, aby funkce byly volány ve správném kontextu</title>
<p>„Ten pravý kontext“ je výchozí hlavní kontext vlákna pro <em>vlákno, ve kterém má být funkce spuštěna</em>. Předpokládá to typický případ, kdy každé vlákno má <em>jeden</em> hlavní kontext, ve kterém běží hlavní smyčka. Hlavní kontext v podstatě poskytuje práci nebo <link href="http://en.wikipedia.org/wiki/Message_queue">frontu zpráv</link> pro vlákno – něco, v čem vlákno může pravidelně kontrolovat, jestli byla dokončena práce v jiném vlákně. Vložením zprávy do fronty – vyvoláním funkce v jiném hlavním kontextu – způsobí její případné vyslání v onom vlákně.</p>
<example>
<p>Například, když aplikace provádí nějaké dlouhé a na výkon procesoru náročné výpočty, měla by je naplánovat ve vlákně na pozadí, aby se neblokovaly aktualizace uživatelského rozhraní v hlavním vlákně. Může ale být zapotřebí výsledky výpočtu zobrazit v uživatelském rozhraní, takže funkce, která to bude mít na starosti, musí být po dokončení výpočtu zavolána v hlavním vlákně.</p>
<p>Mimo to může být výpočetní funkce omezena jen na jedno vlákno, aby se usnadnilo zamezení potřeby zamykat příliš dat při přístupu k nim. To předpokládá, že ostatní vlákna jsou implementována podobně a tudíž je k většině dat přistupováno jen z jediného vlákna a mezi vlákny se komunikuje pomocí <link href="http://en.wikipedia.org/wiki/Message_passing">předávání zpráv</link>. Umožní to jednotlivým vláknům aktualizovat svá data, kdy se jim to hodí, což významně zjednodušuje zamykání.</p>
</example>
<p>U některých funkcí nemusí být žádný důvod se starat o to, ve kterém kontextu jsou spuštěné, protože jsou asynchronní a tudíž kontext neblokují. I přesto je rozumné vědět o tom, který kontext je použit, protože tyto funkce mohou vysílat signály nebo vyvolávat zpětná volání a z důvodu bezpečnosti vlákna je nutné vědět, která vlákna hodlají tyto obsluhy signálů nebo zpětná volání vyvolat.</p>
<example>
<p>Například pro průběh volání v <link href="https://developer.gnome.org/gio/stable/GFile.html#g-file-copy-async"><code>g_file_copy_async()</code></link> je zdokumentováno, že se volá ve výchozím vlákně hlavního kontextu v době volání inicializace.</p>
</example>
<section id="invocation-core-principle">
<title>Principy vyvolání</title>
<p>Klíčový princip vyvolání funkce v konkrétním kontextu je jednoduchý a je probrán níže ve vysvětlení konceptu. V praxi by se místo toho měla použít <link xref="#g-main-context-invoke-full">vhodná metoda <code>g_main_context_invoke_full()</code></link>.</p>
<p>Do cílového <code>GMainContext</code>, který vyvolá funkci, když je obeslán, musí být přidán <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#GSource"><code>GSource</code></link>. Tento <code>GSource</code> by většinou měl být zdroj pro nečinnost vytvořený pomocí <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-idle-source-new"><code>g_idle_source_new()</code></link>, ale nemusí tomu tak být nutně. Může jít například i o zdroj časovače, takže ona funkce bude spuštěna po nějaké prodlevě.</p>
<p><code>GSource</code> bude <link xref="#what-is-gmaincontext">vyslán hned, jak bude připraven</link>, zavoláním funkce na zásobníku vlákna. V případě nečinnosti zdroje to bude hned, jak budou vyslány zdroje s vyšší prioritou – to lze vyladit pomocí parametru s prioritou zdroje v <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-source-set-priority"><code>g_source_set_priority()</code></link>. Typicky je zdroj následně zlikvidován, takže funkce se spustí jen jednou (a když znovu, tak to nemusí být ta samá situace).</p>
<p>Data mezi vlákny mohou být předávána jako <code>user_data</code> předaná do zpětného volání patřícího <code>GSource</code>. Nastaví se to na zdroji pomocí <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-source-set-callback"><code>g_source_set_callback()</code></link> společně s funkcí zpětného volání, která má být vyvolána. K dispozici je jen jeden ukazatel, takže když potřebujete předat více polí, musíte je zabalit do nějaké struktury, kterou si naalokujete.</p>
<example>
<p>Následující příklad demonstruje základní principy, ale existují vhodné metody probrané dále, které vše zjednodušují.</p>
<code mime="text/x-csrc">
/* Hlavní funkce pro vlákno na pozadí thread1 */
static gpointer
thread1_main (gpointer user_data)
{
GMainContext *thread1_main_context = user_data;
GMainLoop *main_loop;
/* Nastaví kontext vlákna a navždy jej spustí */
g_main_context_push_thread_default (thread1_main_context);
main_loop = g_main_loop_new (thread1_main_context, FALSE);
g_main_loop_run (main_loop);
g_main_loop_unref (main_loop);
g_main_context_pop_thread_default (thread1_main_context);
g_main_context_unref (thread1_main_context);
return NULL;
}
/* Zapouzdření dat pro obsluhu více proměnných mezi vlákny */
typedef struct {
gchar *some_string; /* owned */
guint some_int;
GObject *some_object; /* owned */
} MyFuncData;
static void
my_func_data_free (MyFuncData *data)
{
g_free (data->some_string);
g_clear_object (&data->some_object);
g_free (data);
}
static void
my_func (const gchar *some_string,
guint some_int,
GObject *some_object)
{
/* Zde se dělá něco náročného na čas a procesor! */
}
/* Převede zpětné volání nečinnosti na volání my_func() */
static gboolean
my_func_idle (gpointer user_data)
{
MyFuncData *data = user_data;
my_func (data->some_string, data->some_int, data->some_object);
return G_SOURCE_REMOVE;
}
/* Funkce, která má být volána v hlavním vlákně, aby plánovala volání
* my_func() ve vlákně thread1 a přitom předala zadané parametry */
static void
invoke_my_func (GMainContext *thread1_main_context,
const gchar *some_string,
guint some_int,
GObject *some_object)
{
GSource *idle_source;
MyFuncData *data;
/* Vytvoří zapouzdření dat pro předání všech požadovaných proměnných
* mezi vlákny */
data = g_new0 (MyFuncData, 1);
data->some_string = g_strdup (some_string);
data->some_int = some_int;
data->some_object = g_object_ref (some_object);
/* Vytvoří nový zdroj nečinnosti, nastaví my_func() jako zpětné volání
* s nějakými daty, která budou předána mezi vlákny, zvýší prioritu a
* a naplánuje ji napojením na kontext vlákna thread1 */
idle_source = g_idle_source_new ();
g_source_set_callback (idle_source, my_func_idle, data,
(GDestroyNotify) my_func_data_free);
g_source_set_priority (idle_source, G_PRIORITY_DEFAULT);
g_source_attach (idle_source, thread1_main_context);
g_source_unref (idle_source);
}
/* Hlavní funkce pro hlavní vlákno */
static void
main (void)
{
GThread *thread1;
GMainContext *thread1_main_context;
/* Spawn a background thread and pass it a reference to its
* GMainContext. Retain a reference for use in this thread
* too. */
thread1_main_context = g_main_context_new ();
g_thread_new ("thread1", thread1_main,
g_main_context_ref (thread1_main_context));
/* Zde je možné například nastavit uživatelské rozhraní */
/* Vyvolá my_func() v jiném vlákně */
invoke_my_func (thread1_main_context,
"some data which needs passing between threads",
123456, some_object);
/* Zde se pokračuje v jiné práci */
}</code>
<p>Toto vyvolání je <em style="strong">jednosměrné</em>: zavolá se <code>my_func()</code> v <code>thread1</code>, ale neexistuje žádný způsob, jak vrátit hodnotu do hlavního vlákna. Když to potřebujete udělat, je potřeba znovu použít ten samý princip, tj. vyvolat funkci zpětného volání v hlavním vlákně. Jedná se o přímočaré rozšíření, které zde není potřeba rozebírat.</p>
<p>Aby byla zachována bezpečnost vlákna, musí být přístup k datům, ke kterým je potenciálně možné přistupovat z více vláken, vzájemně vyloučen pomocí <link href="http://en.wikipedia.org/wiki/Mutual_exclusion">mutexu</link>. Data s potenciálním přístupem z více vláken: <code>thread1_main_context</code>, předaný při rozvětvení do <code>thread1_main</code>, a <code>some_object</code>, reference, do které jsou předána zapouzdřená data. Podstatné je, že GLib zaručuje, že <code>GMainContext</code> je z hlediska vláken bezpečný, takže sdílení <code>thread1_main_context</code> mezi vlákny je bezpečné. Příklad předpokládá, že ostatní kód přistupující k <code>some_object</code> je z hlediska vláken bezpečný.</p>
<p>Všimněte si, že k proměnným <code>some_string</code> a <code>some_int</code> nelze přistupovat z obou vláken, protože do vlákna <code>thread1</code> jsou místo originálů předávány jejich <code>kopie</code>. Jedná se o standardní techniku používanou z důvodu, aby křížové volání vláken bylo z hlediska vícevláknového zpracování bezpečné, aniž by bylo nutné zamykání. Také se tím předejte problémům se synchronizací při uvolňování <code>some_string</code>.</p>
<p>A obdobně, reference na <code>some_object</code> je přenášena do vlákna <code>thread1</code>, které řeší problém se synchronizací při likvidaci objektu (viz <link xref="memory-management"/>).</p>
<p>Namísto jednodušší <code>g_idle_add()</code> je požita raději funkce <code>g_idle_source_new()</code>, takže lze určit připojený <code>GMainContext</code>.</p>
</example>
</section>
<section id="g-main-context-invoke-full">
<title>Užitečná metoda: <code>g_main_context_invoke_full()</code></title>
<p>To vše se dá úžasně zjednodušit správnou metodou <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-main-context-invoke-full"><code>g_main_context_invoke_full()</code></link>. Ta vyvolá zpětné volání tak, že <code>GMainContext</code> je během něj vlastněn. Vlastnění hlavního kontextu je téměř vždy to stejné, jako jej spustit, a tím pádem je funkce vyvolána ve vlákně, pro které je určený kontext výchozí. </p>
<p>V případě, že uživatelská data není nutné po návratu z volání uvolňovat pomocí zpětného volání <code>GDestroyNotify</code>, dá se místo toho použít funkce <code>g_main_context_invoke()</code>.</p>
<example>
<p>Upravený předchozí příklad – funkce <code>invoke_my_func()</code> může být nahrazena následovně:</p>
<code mime="text/x-csrc">
static void
invoke_my_func (GMainContext *thread1_main_context,
const gchar *some_string,
guint some_int,
GObject *some_object)
{
MyFuncData *data;
/* Vytvoří zapouzdření dat pro předávání všech požadovaných proměnných
* mezi vlákny */
data = g_new0 (MyFuncData, 1);
data->some_string = g_strdup (some_string);
data->some_int = some_int;
data->some_object = g_object_ref (some_object);
/* Vyvolá funkci */
g_main_context_invoke_full (thread1_main_context,
G_PRIORITY_DEFAULT, my_func_idle,
data,
(GDestroyNotify) my_func_data_free);
}</code>
<p>Zamysleme se, co se stane, kdy je <code>invoke_my_func()</code> zavolána z <code>thread1</code>, místo z hlavního vlákna. V původní implementaci by byl nečinný zdroj přidán do kontextu vlákna <code>thread1</code> a vyslán v následující iteraci kontextu (předpokládáme, že nečeká vyslání s vyšší prioritou). Ve vylepšené implementaci si <code>g_main_context_invoke_full()</code> všimne, že uvedený kontext je již vlastněn vláknem (nebo jeho vlastnictví může získat), a zavolá přímo <code>my_func_idle()</code>, namísto aby zdroj připojovala ke kontextu a čekala na vyvolání následující iterace.</p>
<p>Na tomto jemném rozdílu v chování většinou nesejde, ale je dobré na něj pamatovat, protože může ovlivnit chování při blokování (<code>invoke_my_func()</code> by zabrala nezanedbatelné množství času, které by odpovídalo stejnému množství času jako u <code>my_func()</code> před návratem).</p>
</example>
</section>
</section>
<section id="checking-threading">
<title>Kontrola vláken</title>
<p>Je vhodné zdokumentovat, která funkce je volána ve kterém vlákně, a to formou aserce:</p>
<code mime="text/x-csrc">
g_assert (g_main_context_is_owner (expected_main_context));</code>
<p>Když tohle vložíte na začátek každé funkce, selhání aserce zviditelní ty případy, kdy je funkce volána z nesprávného vlákna. Je mnohem jednodušší napsat tyto aserce v rané fázi vývoje kódu, než pak ladit souběh podmínek, který může při volání funkce z nesprávného vlákna snadno nastat.</p>
<p>Tuto techniku lze použít i u vysílání signálů a zpětných volání, díky čemuž se zvýší typová bezpečnost i kontrola, jestli je použit správný kontext. Upozorňujeme, že vyslání signálu přes <link href="https://developer.gnome.org/gobject/stable/gobject-Signals.html#g-signal-emit"><code>g_signal_emit()</code></link> je asynchronní a vůbec se netýká hlavního kontextu.</p>
<example>
<p>Například místo použití následujícího kódu při vyslání signálu:</p>
<code mime="text/x-csrc" style="invalid">
guint param1; /* příklad libovolného parametru */
gchar *param2;
guint retval = 0;
g_signal_emit_by_name (my_object, "some-signal",
param1, param2, &retval);</code>
<p>Může být použito následující:</p>
<code mime="text/x-csrc" style="valid">
static guint
emit_some_signal (GObject *my_object,
guint param1,
const gchar *param2)
{
guint retval = 0;
g_assert (g_main_context_is_owner (expected_main_context));
g_signal_emit_by_name (my_object, "some-signal",
param1, param2, &retval);
return retval;
}</code>
</example>
</section>
<section id="gtask">
<title><code>GTask</code></title>
<p><link href="https://developer.gnome.org/gio/stable/GTask.html"><code>GTask</code></link> poskytuje k vyvolání funkcí v jiných vláknech trochu odlišný přístup, který je zaměřen více na případy, kdy by funkce měla být spuštěna v <em>nějakém</em> vlákně na pozadí, ale není určeno, v kterém konkrétním.</p>
<p><code>GTask</code> přebírá zapouzdřená data a funkci ke spuštění a poskytuje způsob, jak vrátit výsledky z této funkce. Postará se o vše potřebné ke spuštění funkce v libovolném vlákně náležejícímu do stejného svazku vláken sdílených interně v GLib.</p>
<example>
<p>Zkombinováním <link xref="#g-main-context-invoke-full"><code>g_main_context_invoke_full()</code></link> a <code>GTask</code> je možné spustit úlohu v konkrétním kontextu a bez většího úsilí vrátit její výsledek do aktuálního kontextu:</p>
<code mime="text/x-csrc">
/* Toto bude vyvoláno ve vlákně thread1 */
static gboolean
my_func_idle (gpointer user_data)
{
GTask *task = G_TASK (user_data);
MyFuncData *data;
gboolean retval;
/* Zavolá my_func() a vrácenou pravdivostní hodnotu propaguje
* do hlavního vlákna */
data = g_task_get_task_data (task);
retval = my_func (data->some_string, data->some_int,
data->some_object);
g_task_return_boolean (task, retval);
return G_SOURCE_REMOVE;
}
/* Ať je toto vyvoláno z kteréhokoliv vlákna, vyvolá se funkce
* @callback, jakmile je dokončena my_func() a vrátí výsledek */
static void
invoke_my_func_with_result (GMainContext *thread1_main_context,
const gchar *some_string,
guint some_int,
GObject *some_object,
GAsyncReadyCallback callback,
gpointer user_data)
{
MyFuncData *data;
/* Vytvoří zapouzdření dat pro předání všech požadovaných proměnných
* mezi vlákny */
data = g_new0 (MyFuncData, 1);
data->some_string = g_strdup (some_string);
data->some_int = some_int;
data->some_object = g_object_ref (some_object);
/* Vytvoří GTask pro obsluhu vráceného výsledku do hlavního výchozího
* kontextu aktuálního vlákna */
task = g_task_new (NULL, NULL, callback, user_data);
g_task_set_task_data (task, data,
(GDestroyNotify) my_func_data_free);
/* Vyvolá funkci */
g_main_context_invoke_full (thread1_main_context,
G_PRIORITY_DEFAULT, my_func_idle,
task,
(GDestroyNotify) g_object_unref);
}</code>
</example>
</section>
</page>