LoginSignup
38

More than 5 years have passed since last update.

GCD(Grand Central Dispatch)をWindows、Mac、Linuxで利用する

Last updated at Posted at 2013-02-13

はじめに

MacでMacOS X Snow Leopardから搭載された、マルチスレッドプログラミング環境「Grand Central Dispatch」(以下、GCD)ですが、Appleがこの技術をオープンソース化した甲斐あり、最近になって、Windows、Linuxにも移植されました。
XDispatch」というものがそれです。
ライセンスはApacheライセンスという、かなり寛大で商用利用も可能なライセンスです。

この記事では、XDispatchを使い、Windows、Mac、Linuxで同一のソースコードで、GCDを利用するサンプルを示していこうと思います。

その前に、GCDの概要、簡単なチュートリアルについては以下の記事をご参照ください。
マルチコア時代の新機軸! Snow LeopardのGCD」(ASCII.jp)

記事を見れば分かるとおり、これまでのマルチスレッドプログラミングで困難だったり面倒だったりする部分をかなり排除してくれる、非常に優れたマルチスレッドプログラミング環境です。
ただ、これまで一番のネックだったのが、「Macでしか使えない」ことでした。しかし、XDispatchが登場したことで、今後はマルチプラットフォームで気兼ねなくGCDを使えることになるでしょう。
これを機に、GCDがもっとメジャーになることを期待しています。

さて、では詳しい使い方を説明していこうと思います。

Notice

  • 以下の解説はXDispatchのバージョン0.7.1を前提とします
  • 当方にLinux環境が無いのと、Linuxでの開発におけるIDEやコンパイラのスタンダードが実質存在しないことから、Linux版の解説は省かせていただきます(申し訳ありません)。でも、Linuxをばりばりに使う方であれば、Win版、Mac版の解説を参考に自力でできると思います。

インストールとプロジェクトセットアップ

Windowの場合

Visual Studio 2010がインストールされていることが前提です。
(Visual C++ 2010 Express Editionでも大丈夫です)
まずは、XDispatchのダウンロードページから、「Windows - x86」をクリックしてダウンロードしましょう。
ダウンロードしたzipファイルを解凍すると、以下のようなフォルダ構成になります。

includeフォルダの中身
includeフォルダの中身
Visual Studioのプロジェクトを作成したら、プロジェクトプロパティの「構成プロパティ」->「VC+ ディレクトリ」->「インクルード ディレクトリ」の項目で、このincludeフォルダのパスを指定しておきましょう。

libフォルダの中身
libフォルダの中身
同じく、Visual Studioのプロジェクトを作成したら、プロジェクトプロパティの「構成プロパティ」->「VC+ ディレクトリ」->「ライブラリ ディレクトリ」の項目で、このlibフォルダのパスを指定しておきましょう。
また、「構成プロパティ」->「リンカー」->「入力」->「追加の依存ファイル」の項目で
xdispatchD.libとdispatchD.lib(Debug構成の場合)
xdispatch.libとdispatch.lib(Release構成の場合)
を指定します。(今回、QtDispatch.lib、QtDispatchD.libは必要ありません)

binフォルダの中身
binフォルダの中身
これらのdllファイルは実行時に必要です。ビルドしてできたexeファイルと同じディレクトリに、これらのdllファイルを放り込んでおきましょう。

プロジェクト作成時は、プロジェクトテンプレートで「Win32コンソールアプリケーション」を選択します。

Macの場合

XCode 4.5がインストールされていることを想定します。
まずは、XDispatchのダウンロードページから、「Mac OS X - amd64」をクリックしてダウンロードしましょう。
インストーラ型なので、起動してインストールします。
(ライブラリが/Library/frameworks/xdispatch.frameworkと/Library/frameworks/QtDispatch.frameworkにインストールされます)

プロジェクト作成で、「Command Line Tool」を選択します。
プロジェクト作成で、「Command Line Tool」を選択します。

次のダイアログで、Typeを「C++」に設定します。(それ以外の項目は適当に……)
次のダイアログで、Typeを「C++」に設定します。

プロジェクトができます。
プロジェクトができます。

プロジェクトを選択し、さらにターゲットを選択し、以下の画面で「Framework Search Paths」のところに「/Library/Frameworks」と設定します。
プロジェクトを選択し、さらにターゲットを選択し、以下の画面で「Framework Search Paths」のところに「/Library/Frameworks」と設定します。

「C++ Standard Library」で「libstdc++ (GNU C++ standard library)」を選択します。
「C++ Standard Library」で「libstdc++ (GNU C++ standard library)」を選択します。

「Build Phase」タブをクリックし、「Link Binary With Libraries」のところで、「System.framework」と「xdispatch.framework」を追加します。
(+ボタンを押して、出てきたダイアログで「Add Other...」ボタンを押し、ファイル選択ダイアログで、それぞれ
/システム/ライブラリ/Frameworks/System.framework
/ライブラリ/Frameworks/xdispatch.framework
を選択します。

フレームワークを追加

コーディング

cppファイルに、以下のコードを入力します。
(以下のコードの意味は、「マルチコア時代の新機軸! Snow LeopardのGCD」(ASCII.jp)を読むと分かります。すみません。手抜きで……)

#include <dispatch/dispatch.h>
#include <xdispatch/dispatch.h>
#ifdef _WIN32
#   include <windows.h>
#   include <conio.h>
#endif


int main(int argc, char* argv[])
{
    for (int i = 1; i <= 10; ++i) 
    {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ${
            printf(" %d", i);
        });
    }
    printf("\nDone!\n");

#ifdef _WIN32 // Windows
    Sleep(1000);
#   ifdef _DEBUG
    printf("\nPress Any Key...\n");
    _getch();
#   endif
#else // Mac, Linux
    sleep(1);
#endif
    return 0;
}

本来、GCDの利用にはBlockというC/C++言語の拡張仕様が必要です。これはいわゆるクロージャに近いもので、
^{ … } と書くことで、処理の中身を他の関数に引き渡すことができます。

ただ、Visual C++などはBlockをサポートしていないので、XDispatchでは
${ … } という書き方をすると、マクロによってVisual C++の場合、これをC++11のlambda(ラムダ)式に置き換えてくれるようです。

なので、Windows、Mac、Linux全ての環境で同一コードで動くようにするためには、^{ … }でなく${ … }と書く必要があります。また、Windows環境でC++11のlambda(ラムダ)式を利用する都合上、.c(C言語)ではなく必ず.cpp(C++)から利用する必要があります。

実行

実行してみると...

Done!
 1 3 4 5 6 7 8 9 2 10

できました!

応用編

先ほどの例では、dispatch_asyncにBlockまたはC++11のラムダ式を直接渡していましたが、いったん外部で変数に保存したBlockまたはC++11のラムダ式をdispatch_asyncに渡すにはどうすればよいのでしょうか?

実は、XDispatchでは、BlockおよびC++11のラムダ式の型を抽象化するようなマクロはまだ用意されていません。
よって、自作する必要があります。
まずは、マクロなしでやってみましょう。

まず、Blockの場合です。

void (^hello1)(int) = $(int x)
    {
        std::cout << "Hello, C++ World! " << x << std::endl;
    };

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
                       ${
                           hello1(1000);
                       });

次、C++11のラムダ式の場合です。
#include <functional> のインクルードを忘れずに )

std::function<void(int)> hello1 = $(int x)
    {
        std::cout << "Hello, C++ World! " << x << std::endl;
    };

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
                       ${
                           hello1(1000);
                       });

つまり、上記の変数定義の記述をマクロ化してしまえばいいですね。
こんな感じでしょうか。

#ifdef _WIN32
#   define $func1(type,funcname,arg1) std::function<type(arg1)>funcname
#else
#   define $func1(type,funcname,arg1) type(^funcname)(arg1)
#endif

すると、以下のような記述で、マルチプラットフォームな共通コードにすることができます。

$func1(void,hello1,int) = $(int x)
    {
        std::cout << "Hello, C++ World! " << x << std::endl;
    };

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
                       ${
                           hello1(1000);
                       });

引数無しバージョン、引数が複数だったバージョン、なども用意しましょう。残念ながら、引数の数だけ、マクロを定義する以外方法はなさそうです。あまり美しくありませんが……。

#ifdef _WIN32
#   define $func(type,funcname) std::function<type()>funcname
#   define $func1(type,funcname,arg1) std::function<type(arg1)>funcname
#   define $func2(type,funcname,arg1,arg2) std::function<type(arg1,arg2)>funcname
    ...
#else
#   define $func(type,funcname) type(^funcname)()
#   define $func1(type,funcname,arg1) type(^funcname)(arg1)
#   define $func2(type,funcname,arg1,arg2) type(^funcname)(arg1,arg2)
    ...
#endif

なお、C++11のautoを使えば、こんなマクロなんぞ使わなくとも、Windows、Mac、Linuxともに

auto hello = $(int x)
    {
        std::cout << "Hello, C++ World! " << x << std::endl;
    };

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
                       ${
                           hello1(1000);
                       });

のように書くことができます。ただ、autoはローカル変数とグローバル変数にしか使えないので、クラスのインスタンス変数にBlockやラムダ式を代入したい場合は、上記のマクロを使う必要があります。
あるいは、テンプレートを使う方法もあるのですが、これだとテンプレート地獄に陥る恐れがあるのであまり推奨しません^^;

そもそも、こんなことをするくらいなら、いっそMacやLinuxでもBlockをやめてC++11のラムダ式を使えばいいじゃない、という話もありますね。私もそう思いますw
でも、現在のバージョンのXDispach、Mac(XCode4.5)でstd::functionがコンパイルを通るように、以下の図のように
「C++ Standard Library」で「libc++ (LLVM C++ standard library with C++ 11 support)」を選択
「C++ Standard Library」で「libc++ (LLVM C++ standard library with C++ 11 support)」を選択すると、XDispachのヘッダファイルのところ(が見つかりませんとか出てくる)でコンパイルエラーになっちゃうんですよね……。無理矢理ヘッダファイルを修正してやってみると、今度はなんか実行結果がめちゃくちゃ変な風になるし……。

当面は、BlockとC++11のラムダ式をマクロで抽象化するやり方、またはautoを使う方法が無難のようです……。
もっとも、将来Visual C++がBlockをサポートしてくれるのが一番良いんですけどね。

libQtDispatchについて

XDispatchには他にもlibQtDispatchという、GUIツールキット「Qt」と相性の良いライブラリも用意されているようです(これについては、まだ詳しく調べられていません。誰か、記事書いてみませんか?)。

おわりに

いかがでしたでしょうか。Mac、Windows、Linuxで同一コードでGCDが使えることがおわかりいただけたと思います。
最初にも書きましたが、マルチプラットフォームで使えるようになったことで、マルチスレッドプログラミング環境としてGCDを使う際の一番の懸念点が解消されました。
皆さんも、どんどんGCD(Grand Central Dispatch)を使っていきましょう!

謝辞

GCDを開発・オープンソース化してくれたApple、そしてXDispatchを開発してくれたチームMLBAの皆様に感謝します。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38