本記事について
macOS、iOS、watchOS、tvOSのマルチコアハードウェアにおいて配列でタスクを実行させるための技術、Grand Central Dispatch(GCD)の理解を深めるため、敢えてC言語縛りで使ってみました。
(嘘です…本当はまだObject-CやSwiftをあまり使ったことがないからです…)
実行環境
・macOS Ventura 13.0.1
・Apple clang version 14.0.0 (clang-1400.0.29.202)
・lldb-1400.0.38.17
・MacBook Pro 13インチ M2
※Homebrew GCCは使えなかったです…
サンプルソース
5秒周期でログを10回出力するソースです。
1 #include <dispatch/dispatch.h>
2 #include <stdio.h>
3 #include <Block.h>
4
5 int main(void)
6 {
7 __block int i = 0;
8
9 dispatch_queue_t queue;
10 dispatch_source_t source;
11 dispatch_time_t start_time;
12
13 /* 新規ディスパッチキューを作成 */
14 queue = dispatch_queue_create("jp.test_queue", DISPATCH_QUEUE_SERIAL);
15
16 /* タイマーのディスパッチソースを生成 */
17 source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
18
19 /* イベント発生後に呼ばれるブロックを設定 */
20 dispatch_source_set_event_handler(source, ^{
21 printf("5秒後に起動。%d番目。\n", i);
22 i++;
23 if(i == 10){
24 printf("実行停止。\n");
25 dispatch_suspend(source);
26 dispatch_source_cancel(source);
27
28 exit(0);
29 }
30 });
31
32 /* タイマーを設定 */
33 start_time = dispatch_time(DISPATCH_TIME_NOW, 0);
34 dispatch_source_set_timer(source, start_time, 5.0*NSEC_PER_SEC, 0);
35
36 /* 中断されている新規ディスパッチキューを再開 */
37 printf("実行開始!\n");
38 dispatch_resume(source);
39
40 /* 新規ディスパッチキューを実行させるため、メインスレッドを停止 */
41 dispatch_main();
42
43 return 0;
44 }
実行結果
実行開始!
5秒後に起動。0番目。
5秒後に起動。1番目。
5秒後に起動。2番目。
5秒後に起動。3番目。
5秒後に起動。4番目。
5秒後に起動。5番目。
5秒後に起動。6番目。
5秒後に起動。7番目。
5秒後に起動。8番目。
5秒後に起動。9番目。
実行停止。
自分なりの解釈
まず、下記の関数dispatch_queue_create()にて、新規のディスパッチキューを作成します。
第二引数にDISPATCH_QUEUE_SERIALを指定することにより、シリアルにFIFO 順でブロックを実行するディスパッチキューにすることが出来ます。
12
13 /* 新規ディスパッチキューを作成 */
14 queue = dispatch_queue_create("jp.test_queue", DISPATCH_QUEUE_SERIAL);
15
次に、下記の関数dispatch_source_create()で、第一引数にDISPATCH _SOURCE_TIMER を指定することにより、タイマーに基づいて、新規ディスパッチキューqueueにイベントハンドラブロックを提出するディスパッチソースにを作成します。
15
16 /* タイマーのディスパッチソースを生成 */
17 source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
18
ただ、これだけだと
①タイマーの周期はまだ不明瞭。
②タイマー満了後に新規ディスパッチキューに提出されるイベントハンドラブロックは???
という状態になってしまうので、最低でも更に2種類の関数を使用する必要があります。
まずは、関数dispatch_source_set_event_handler()にて、ディスパッチキューに提出されるイベントハンドラブロックを定義します。
(5秒周期のタイマー満了後にメッセージを10回出力。
メッセージを10回出力した後は、そのディスパッチソースを中断・キャンセルし、そのまま終了するという処理を実装。)
19 /* イベント発生後に呼ばれるブロックを設定 */
20 dispatch_source_set_event_handler(source, ^{
21 printf("5秒後に起動。%d番目。\n", i);
22 i++;
23 if(i == 10){
24 printf("実行停止。\n");
25 dispatch_suspend(source);
26 dispatch_source_cancel(source);
27
28 exit(0);
29 }
30 });
この後、関数dispatch_source_set_timer()にて、そのタイマーのディスパッチソースのタイマー開始時刻や間隔などを定義します。
(今回はすぐに5秒間隔のタイマー起動という感じに設定しときます。)
31
32 /* タイマーを設定 */
33 start_time = dispatch_time(DISPATCH_TIME_NOW, 0);
34 dispatch_source_set_timer(source, start_time, 5.0*NSEC_PER_SEC, 0);
35
これであらかたのコーディングは終了なのですが、最後に忘れてはならないのが関数dispatch_resume()です!!!
新規ディスパッチソースは作った段階ではsuspend状態ですので、resumeで発動させなければいけません。
36 /* 中断されている新規ディスパッチキューを再開 */
37 printf("実行開始!\n");
38 dispatch_resume(source);
39
40 /* 新規ディスパッチキューを実行させるため、メインスレッドを停止 */
41 dispatch_main();