|
Packit |
1470ea |
|
|
Packit |
1470ea |
<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="threading" xml:lang="ko">
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<info>
|
|
Packit |
1470ea |
<link type="guide" xref="index#specific-how-tos"/>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<credit type="author copyright">
|
|
Packit |
1470ea |
<name>Philip Withnall</name>
|
|
Packit |
1470ea |
<email its:translate="no">philip.withnall@collabora.co.uk</email>
|
|
Packit |
1470ea |
<years>2015</years>
|
|
Packit |
1470ea |
</credit>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<include xmlns="http://www.w3.org/2001/XInclude" href="cc-by-sa-3-0.xml"/>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<desc>메인 스레드 외부의 처리를 작업 스레드로 옮깁니다</desc>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<mal:credit xmlns:mal="http://projectmallard.org/1.0/" type="translator copyright">
|
|
Packit |
1470ea |
<mal:name>조성호</mal:name>
|
|
Packit |
1470ea |
<mal:email>shcho@gnome.org</mal:email>
|
|
Packit |
1470ea |
<mal:years>2016, 2017.</mal:years>
|
|
Packit |
1470ea |
</mal:credit>
|
|
Packit |
1470ea |
</info>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<title>스레드 처리</title>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<synopsis>
|
|
Packit |
1470ea |
<title>요약</title>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<list>
|
|
Packit |
1470ea |
<item>가능한 모든 경우에 스레드를 사용하지 마십시오(<link xref="#when-to-use-threading"/>). </item>
|
|
Packit |
1470ea |
<item>스레드를 사용해야하면, GTask 또는 GThreadPool 를 사용하고, 가능한 한 스레드 처리한 코드를 격리하십시오(<link xref="#using-threading"/>). </item>
|
|
Packit |
1470ea |
<item>GThread 를 직접 사용한다면 g_thread_join() 함수를 사용하여 스레드 자원 누수를 막으십시오(<link xref="#using-threading"/>). </item>
|
|
Packit |
1470ea |
<item>스레드를 사용할 경우 어떤 코드를 실행하든 GMainContext 를 주의깊게 다루십시오. 잘못된 컨텍스트에서 코드를 실행하면 레이스 컨티션을 유발하거나 메인 루프가 멈출 수 있습니다(<link xref="#using-threading"/>). </item>
|
|
Packit |
1470ea |
</list>
|
|
Packit |
1470ea |
</synopsis>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<section id="when-to-use-threading">
|
|
Packit |
1470ea |
<title>스레드 처리를 활용할 경우</title>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
GLib로 프로젝트를 작성할 때, 기본 접근에 있어 스레드를 절대 사용하지 말아야합니다. 대신, 메인 컨텍스트에서 다른 이벤트 처리를 계속 하는 동안 백그라운드에서 대부분이 대부분의 블로킹 입출력 처리가 가능하도록 비동기 처리하는 <link xref="main-contexts">GLib 메인 컨텍스트</link>를 적절하게 활용하는 것이 좋습니다. 스레드 처리한 코드의 분석, 검토, 디버깅은 매우 급격하게 난해해집니다.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
GLib 코드에서 호출해야 하는 함수의 동작을 가로 막는 외부 라이브러리를 활용할 경우라면 스레드를 활용해야합니다. 라이브러리에서 동작을 가로막지 않는 대안책을 마련하거나<link href="http://pubs.opengroup.org/onlinepubs/009695399/functions/poll.html">poll() </link> 루프와 잘 동작하는 수단이 있다면 취향에 따라 사용해야합니다. 동작을 가로 막는 함수를 꼭 사용해야 한다면, 작업 스레드에서 동작을 가로 막는 처리를 실행하는 <link href="https://developer.gnome.org/gio/stable/GAsyncResult.html">GAsyncResult 방식</link> 일반 GLib 비동기 함수로 변환하도록 씬 래퍼를 작성해야합니다.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<example>
|
|
Packit |
1470ea |
예제:
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
int
|
|
Packit |
1470ea |
some_blocking_function (void *param1,
|
|
Packit |
1470ea |
void *param2);
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
위 코드를 다음과 같이 래핑해야합니다:
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
void
|
|
Packit |
1470ea |
some_blocking_function_async (void *param1,
|
|
Packit |
1470ea |
void *param2,
|
|
Packit |
1470ea |
GCancellable *cancellable,
|
|
Packit |
1470ea |
GAsyncReadyCallback callback,
|
|
Packit |
1470ea |
gpointer user_data);
|
|
Packit |
1470ea |
int
|
|
Packit |
1470ea |
some_blocking_function_finish (GAsyncResult *result,
|
|
Packit |
1470ea |
GError **error);
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
그리고 다음과 같은 구현체를 넣으십시오:
|
|
Packit |
1470ea |
/* Closure for the call’s parameters. */
|
|
Packit |
1470ea |
typedef struct {
|
|
Packit |
1470ea |
void *param1;
|
|
Packit |
1470ea |
void *param2;
|
|
Packit |
1470ea |
} SomeBlockingFunctionData;
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
static void
|
|
Packit |
1470ea |
some_blocking_function_data_free (SomeBlockingFunctionData *data)
|
|
Packit |
1470ea |
{
|
|
Packit |
1470ea |
free_param (data->param1);
|
|
Packit |
1470ea |
free_param (data->param2);
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
g_free (data);
|
|
Packit |
1470ea |
}
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
static void
|
|
Packit |
1470ea |
some_blocking_function_thread_cb (GTask *task,
|
|
Packit |
1470ea |
gpointer source_object,
|
|
Packit |
1470ea |
gpointer task_data,
|
|
Packit |
1470ea |
GCancellable *cancellable)
|
|
Packit |
1470ea |
{
|
|
Packit |
1470ea |
SomeBlockingFunctionData *data = task_data;
|
|
Packit |
1470ea |
int retval;
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
/* Handle cancellation. */
|
|
Packit |
1470ea |
if (g_task_return_error_if_cancelled (task))
|
|
Packit |
1470ea |
{
|
|
Packit |
1470ea |
return;
|
|
Packit |
1470ea |
}
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
/* Run the blocking function. */
|
|
Packit |
1470ea |
retval = some_blocking_function (data->param1, data->param2);
|
|
Packit |
1470ea |
g_task_return_int (task, retval);
|
|
Packit |
1470ea |
}
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
void
|
|
Packit |
1470ea |
some_blocking_function_async (void *param1,
|
|
Packit |
1470ea |
void *param2,
|
|
Packit |
1470ea |
GCancellable *cancellable,
|
|
Packit |
1470ea |
GAsyncReadyCallback callback,
|
|
Packit |
1470ea |
gpointer user_data)
|
|
Packit |
1470ea |
{
|
|
Packit |
1470ea |
GTask *task = NULL; /* owned */
|
|
Packit |
1470ea |
SomeBlockingFunctionData *data = NULL; /* owned */
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
g_return_if_fail (validate_param (param1));
|
|
Packit |
1470ea |
g_return_if_fail (validate_param (param2));
|
|
Packit |
1470ea |
g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
task = g_task_new (NULL, cancellable, callback, user_data);
|
|
Packit |
1470ea |
g_task_set_source_tag (task, some_blocking_function_async);
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
/* Cancellation should be handled manually using mechanisms specific to
|
|
Packit |
1470ea |
* some_blocking_function(). */
|
|
Packit |
1470ea |
g_task_set_return_on_cancel (task, FALSE);
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
/* Set up a closure containing the call’s parameters. Copy them to avoid
|
|
Packit |
1470ea |
* locking issues between the calling thread and the worker thread. */
|
|
Packit |
1470ea |
data = g_new0 (SomeBlockingFunctionData, 1);
|
|
Packit |
1470ea |
data->param1 = copy_param (param1);
|
|
Packit |
1470ea |
data->param2 = copy_param (param2);
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
g_task_set_task_data (task, data, some_blocking_function_data_free);
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
/* Run the task in a worker thread and return immediately while that continues
|
|
Packit |
1470ea |
* in the background. When it’s done it will call @callback in the current
|
|
Packit |
1470ea |
* thread default main context. */
|
|
Packit |
1470ea |
g_task_run_in_thread (task, some_blocking_function_thread_cb);
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
g_object_unref (task);
|
|
Packit |
1470ea |
}
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
int
|
|
Packit |
1470ea |
some_blocking_function_finish (GAsyncResult *result,
|
|
Packit |
1470ea |
GError **error)
|
|
Packit |
1470ea |
{
|
|
Packit |
1470ea |
g_return_val_if_fail (g_task_is_valid (result,
|
|
Packit |
1470ea |
some_blocking_function_async), -1);
|
|
Packit |
1470ea |
g_return_val_if_fail (error == NULL || *error == NULL, -1);
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
return g_task_propagate_int (G_TASK (result), error);
|
|
Packit |
1470ea |
}
|
|
Packit |
1470ea |
자세한 내용은 <link href="https://developer.gnome.org/gio/stable/GAsyncResult.html">GAsyncResult 문서</link>를 참고하십시오. 작업 스레드를 구현하는 간단한 방법은 <link href="https://developer.gnome.org/gio/stable/GTask.html">GTask </link> 와 <link href="https://developer.gnome.org/gio/stable/GTask.html#g-task-run-in-thread">g_task_run_in_thread() </link> 함수를 활용하는 방법입니다(추가 참고: <link xref="main-contexts#gtask"/>).
|
|
Packit |
1470ea |
</example>
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<section id="using-threading">
|
|
Packit |
1470ea |
<title>스레드 활용</title>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
작업 스레드를 작성할 때 GTask 가 적절한 수단이 아니라면, 좀 더 저수준의 접근 방식을 활용해야합니다. 스레드 코드로 하여금 실행 시간에 예기치 못한 버그가 나타난다거나, 데드락이 갑자기 걸린다거나, 자원을 갑자기 많이 소모한다거나, 프로그램이 끝나는 일은 식은죽 먹기니 매우 신중하게 고려해야 합니다.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
스레드 코드 작성 완벽 설명서는 이 문서의 범위를 벗어나지만, 스레드 코드 작성시 잠재 버그를 줄이려 따를 몇가지 지침서가 있습니다. 무엇보다 우선하는 원칙은 스레드로 영향을 줄 수 있는 코드 및 데이터 양을 줄이는 방식입니다. 예를 들면, 스레드 수를 줄이거나, 작업 스레드 구현의 복잡도를 줄이거나, 스레드간 공유 데이터 양을 줄이는 방법입니다.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<list>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
가능하면 직접 <link href="https://developer.gnome.org/glib/stable/glib-Threads.html">GThread </link>를 만들기보다는 <link href="https://developer.gnome.org/glib/stable/glib-Thread-Pools.html">GThreadPool </link>을 사용핫비시오. <link href="https://developer.gnome.org/glib/stable/glib-Thread-Pools.html">GThreadPool </link>에서는 작업 큐, 가동 스레드 수 제한, 종결 스레드 자동 병합을 지원하여, 스레드가 어디론가 새 나갈 일이 없습니다.
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
GThreadPool 을 활용할 수 없다면(드문 경우):
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<list>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
스레드를 만들려면 <link href="https://developer.gnome.org/glib/stable/glib-Threads.html#g-thread-new">g_thread_new() </link> 함수 대신 <link href="https://developer.gnome.org/glib/stable/glib-Threads.html#g-thread-try-new">g_thread_try_new() </link> 함수를 활용하여 프로그램의 상태를 알 수 없는 상황에서 갑자기 멈추게 하기 보단 스레드 외적 영역에서의 시스템 동작상 나타나는 오류를 탁월하게 처리할 수 있게 하십시오.
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
스레드 자원의 방치를 막으려면 <link href="https://developer.gnome.org/glib/stable/glib-Threads.html#g-thread-join">g_thread_join() </link> 함수를 사용하여 분명하게 스레드를 병합하십시오.
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
</list>
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
스레드간 데이터를 전달할 때, 뮤텍스의 동작을 직접 잠그기보다는 메시지 전달 기능을 활용하십시오. GThreadPool 에서는 <link href="https://developer.gnome.org/glib/stable/glib-Thread-Pools.html#g-thread-pool-push">g_thread_pool_push() </link> 함수로 메시지 전달 기능을 지원합니다.
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
뮤텍스를 활용해야 한다면:
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<list>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
스레드 코드를 최대한 격리하고, 클래스에서 뮤텍스를 자체 용도로 유지하며, 매우 한정적인 클래스 구성원만 다루십시오.
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
모든 뮤텍스는 뮤텍스에 보호 접근할 기타 구조체 또는 변수를 나타내는 선언부에 분명하게 설명을 달아두어야합니다. 이와 비슷하게, 해당 뮤텍스의 변수도 뮤텍스에서만 접근해야 한다고 설명을 달아두어야합니다.
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
</list>
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
메인 컨텍스트와 스레드간 상호 작용을 조심스럽게 취급하십시오. 예를 들면, <link href="https://developer.gnome.org/glib/stable/glib-The-Main-Event-Loop.html#g-timeout-add-seconds">g_timeout_add_seconds() </link> 함수는, 현재 스레드에서 불필요하며, 메인 스레드에서 실행할 전역 메인 컨텍스트의 실행 제한 시간을 설정합니다. 이 부분을 잘못 설정하면 메인 스레드에서 실행한 작업 스레드가 갑자기 끝날 수 있음을 의미합니다(추가 참조: <link xref="main-contexts#default-contexts"/>).
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
</list>
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<section id="debugging">
|
|
Packit |
1470ea |
<title>디버깅</title>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
스레드 문제 디버깅은 상당히 까다로운데 재현하기도 어려울 뿐더러 왜 인지도 이유를 밝히기 어렵기 때문입니다. 이게 바로 제일 먼저 스레드를 활용할 때 피해야 할 가장 큰 이유중 하나입니다.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
그러나, 스레드 문제가 터지면 <link xref="tooling#helgrind-and-drd">Valgrind의 drd 도구와 helgrind 도구가 도움될 수 있습니다</link>.
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
</page>
|