fc2ブログ

Win32並行処理プログラミング入門42

 この記事は、Win32並行処理プログラミング入門41の続きです。前回はセマフォの背景知識を解説しました。今回はセマフォ関連のWin32APIとセマフォ・カーネルオブジェクトについて解説します。
 セマフォ・カーネルオブジェクトの特徴がよく表れているのが、Win32並行処理プログラミング入門40に掲載したサンプルの次の部分です。

    //セマフォの初期化と破棄が必要
    ConcurrentFoo() : _number( 0 ) 
    { 
        //最大値を1にするとミューテックスに近くなる
        this->_semaphore = CreateSemaphore( 
            __nullptr, 
            1, //利用可能なリソースの数 
            1, //リソースの最大値
            __nullptr );
    }

このコードはバイナリセマフォを作成しています。バイナリセマフォとは、再帰カウンタが0と1の値しか持たないミューテックスに近いものです。セマフォ・カーネルオブジェクトは、CreateSemaphore関数の引数として「現在利用可能なリソースの数」と「同時にアクセス可能なリソースの最大数」を設定できます。
 セマフォ・カーネルオブジェクトは、有限のリソースを排他制御するのに使用します。同時にアクセス可能なリソースの最大値を設定できるので、サーバのプログラムなどで使用すると便利です。それに加え、CreateSemaphoreには別の使い方があります。

LONG semaphoreCount;
this->_semaphore = CreateSemaphore( 
    __nullptr, 
    0, //利用可能なリソースの数を0にしておく 
    10, //リソースの最大値を適切なものにする
    __nullptr );

//この間でリソースを初期化する

//全てのリソースが使用可能になった!
ReleaseSemaphore( this->_semaphore, 10,  &semaphoreCount );

この様にCreateSemaphore関数で現在利用可能なリソースの数を0に設定すると、セマフォ・カーネルオブジェクトは非シグナル状態になり、初期化していない共有リソースがアクセスされるのを防ぐ事が出来ます。リソースの初期化が終了し、ReleaseSemaphore関数により現在利用可能なリソースの数を増やせば、並行処理されている他のプログラムからアクセス可能となります。なお、ReleaseSemaphore関数の3番目の引数は、それまでのカウントの値です。それまでのカウントの値が必要ない場合はNULL値を指定します。
 並行処理プログラミングでは、複数のプログラムが同時に実行されている事を思い出して下さい。もし、運悪く初期化されていない共有リソースにアクセスされれば処理結果が適切なものなりません。カーネルオブジェクトが非シグナル状態になると、WaitForSingleObject関数は制御を戻しません(タイムアウトは除く)。これにより、適切にWin32APIを使っていれば、この様な事態を避ける事が出来ます。もちろん、WaitForSingleObject関数が使用されていなければアクセス出来てしまいますが、それは根本的に間違っています。続く...
スポンサーサイト



テーマ : プログラミング
ジャンル : コンピュータ

Win32並行処理プログラミング入門41

 この記事は、Win32並行処理プログラミング入門40の続きです。前回はセマフォ・カーネルオブジェクトを解説しました。今回も引き続き、セマフォ・カーネルオブジェクトについて解説します。
 先ずはセマフォの概念を解説します。セマフォは1945年にE.W.Dikstra氏が提案したもので、シグナルの紛失問題を解決し、プロセス間で共有するリソース/コードを排他制御するためのものです。
 シグナル紛失問題は難しい概念なので例を挙げて解説します。仮にリソースを共有するスレッドが2つあるとします。この2つのスレッドは、片方が共有リソースにオブジェクトを格納し、片方が共有リソースからオブジェクトを取りだします。この場合、オブジェクトを消費するスレッドは、共有リソースがなければ何もできないのでスリープしなくてはなりません。一方、共有リソースにオブジェクトを格納するスレッドは、共有リソースに格納する時オブジェクトの数が0個であれば、スリープしているであろうスレッドを起こさなくてはなりません。しかし、この状況で並行処理特有の問題が起こる可能性があります。
 オブジェクトを消費するスレッドが活動した結果、共有リソースに格納されているオブジェクトの数が0個になり、スリープする前にオブジェクトを格納するスレッドが処理をすれば問題がおきます。何故ならば、オブジェクトを消費するスレッドはまだスリープしていないからです。この時、スリープを指示するシグナルは紛失してしまいます。
 このシグナル紛失問題を解決するには、スレッド間でチェックする変数を共有し、その共有した変数をアトミックに加算/減算する方法があります。互いにチェックする共有変巣があれば、スリープしなくてはならないタイミングが分かりますので問題は解決します。
 Windowsのセマフォ・カーネルオブジェクトはこの考えに基づき作られています。その特徴は、ミューテックス・カーネルオブジェクト同様プロセス間でリソースもしくはコード片を共有できる上に、リソースの最大数を設定できる事です。続く...

テーマ : プログラミング
ジャンル : コンピュータ

Win32並行処理プログラミング入門40

 この記事は、Win32並行処理プログラミング入門39の続きです。前回はミューテックス・カーネルオブジェクトを解説しました。今回はセマフォ・カーネルオブジェクトを解説します。
 今まで数個の同期オブジェクトを解説してきましたが、まだ解説するべき同期オブジェクトが残っています。それが今回解説を始める、セマフォ・カーネルオブジェクトです。
 分かりやすいように、ミューテックス・カーネルオブジェクトと同じ内容のサンプルを使用して解説を行います。

#include <iostream>
#include <windows.h>
#include <tchar.h>
#include <process.h>
using namespace std;

//インスタンスのポインタとパラメータを保持するクラス
class InstanceAndParameter
{
private:
    void* ins;
    void* param;
public:
    InstanceAndParameter( void* ins, void* param ) 
        : ins( ins ), param( param ){}
    void* getInstance() const { return ins; } 
    void* getParameter() const { return param; }
};

//並行処理を意識したクラス
class ConcurrentFoo 
{
private:
    HANDLE _semaphore;
    long _number;
public:
    //セマフォの初期化と破棄が必要
    ConcurrentFoo() : _number( 0 ) 
    { 
        //最大値を1にするとミューテックスに近くなる
        this->_semaphore = CreateSemaphore( 
            __nullptr, 
            1, //利用可能なリソースの数 
            1, //リソースの最大値
            __nullptr );
    }
    ~ConcurrentFoo()
    { 
        CloseHandle( this->_semaphore );
    }
    long getNumber() const { return _number; }

    //並列的に実行するメソッド
    //※並行度は高いが遅い
    void AddFunc( int count )
    {
        LONG semaphoreCount;
        for ( int i = 0; i < count; i++ ) {
            WaitForSingleObject( this->_semaphore, INFINITE );
            ++this->_number;
            ReleaseSemaphore( this->_semaphore, 1,  &semaphoreCount );
        }
    }

    //スレッドから呼び出すメソッド
    static unsigned __stdcall Add( void* pvParam ) 
    {
        //パラメータのチェック
        _ASSERTE( FALSE == IsBadReadPtr( pvParam, 
            sizeof( InstanceAndParameter ) ) 
            && "スレッドに渡されたパラメータが無効です" ); 

        //インスタンスとパラメータを取り出してメソッドを実行
        InstanceAndParameter* ip = 
            reinterpret_cast< InstanceAndParameter* >( pvParam );
        ConcurrentFoo* obj = 
            reinterpret_cast< ConcurrentFoo* >( ip->getInstance() );
        int count = PtrToInt( 
            reinterpret_cast< INT_PTR >( ip->getParameter() ) );
        obj->AddFunc( count );
        return 0;
    }
};

int _tmain( int, _TCHAR* )
{
    //スレッド数の判定
    const int threadCount = 64;
    if ( threadCount > MAXIMUM_WAIT_OBJECTS ) {
        cout << "【エラー】" << endl;
        cout << "指定するスレッド数が多すぎます。" << endl;
        cout << "指定するスレッド数は" 
            << MAXIMUM_WAIT_OBJECTS 
            << "以下にして下さい。" << endl;
        return -1;
    }

    //スレッドのパラメータを用意する
    ConcurrentFoo obj;
    int count = 100000;
    InstanceAndParameter threadparam( 
        reinterpret_cast< void * >( &obj ), 
        reinterpret_cast< void * >( ( INT_PTR ) count ) );

    //スレッドを作成する
    HANDLE hThreads[ threadCount ];
    for ( int i = 0; i < threadCount; i++ )
    {
        hThreads[ i ] = reinterpret_cast< HANDLE >( 
            _beginthreadex ( 
                __nullptr,
                0U,
                ConcurrentFoo::Add,
                reinterpret_cast< void * >( &threadparam ) ,
                CREATE_SUSPENDED, //直ぐには実行しない
                __nullptr ) );
    }

    //作成したスレッドを実行
    for ( int i = 0; i < threadCount; i++) 
        ResumeThread( hThreads[ i ] );

    //タイムアウト時間をミリ単位で指定して待機
    //※指定した時間は正確ではありません
    DWORD result = WaitForMultipleObjects( 
        threadCount, hThreads, TRUE, 20000 );

    //WaitForSingleObject関数が終了した原因を表示
    cout << "【スレッドの状態を表示します】" << endl;
    unsigned int max = ( WAIT_OBJECT_0 + threadCount - 1 );
    if ( result == WAIT_FAILED ) { 
        //エラーを表示する
        DWORD error = GetLastError();
        LPVOID title = _T( "エラー" );
        LPVOID msg;
        FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
                FORMAT_MESSAGE_FROM_SYSTEM |
                FORMAT_MESSAGE_IGNORE_INSERTS,
            __nullptr,
            GetLastError(),
            MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // 既定の言語
            reinterpret_cast< LPTSTR >( &msg ),
            0,
            __nullptr
        );
        MessageBox( __nullptr, 
            reinterpret_cast< LPCTSTR >( msg ) , 
            reinterpret_cast< LPCTSTR >( title ), 
            MB_OK | MB_ICONERROR );
        LocalFree( msg );
        return -1;
    } 
    if ( result == WAIT_TIMEOUT ) {
        cout << "タイムアウトしてしまいました。" << endl;
    } else if ( ( result >= WAIT_OBJECT_0 ) & ( result <= max ) ) {
        cout << "全スレッドの処理が終わりました。" << endl;
    } 
    cout << endl;

    //変数の合計値を表示
    long correctValue = threadCount * count;
    long number = obj.getNumber();
    cout << "【変数の値を表示します】" << endl;
    cout << "予想値:" << correctValue << endl;  
    cout << "実際の値:" << number << endl;
    cout << "不足値:" << ( correctValue - number ) << endl;

    //ハンドルを閉じる
    for ( int i = 0; i < threadCount; i++ ) 
        CloseHandle( hThreads[ i ] );
    cout << endl;
    return 0;
}

このサンプルは、ミューテックス・カーネルオブジェクトの解説時に使用したものと同じ内容です。違う部分はConcurrentFoo クラスだけです。同期オブジェクトの隠蔽を行うと、この様に変更に強いオブジェクトが作れます。
 長くなりましたので、セマフォ・カーネルオブジェクトの詳しい解説は次回から行います。

テーマ : プログラミング
ジャンル : コンピュータ

Win32並行処理プログラミング入門39

 この記事は、Win32並行処理プログラミング入門38の続きです。前回はミューテックス・カーネルオブジェクトの使用方法を解説しました。今回はミューテックス・カーネルオブジェクトとパフォーマンスについて解説します。
 Win32並行処理プログラミング入門37で掲載したサンプルが遅い理由は、ミューテックスの特性にあります。先ずはサンプル内でパフォーマンスが悪い箇所を見て下さい。

    //並列的に実行するメソッド
    //※並行度は高いが遅い
    void AddFunc( int count )
    {
        for ( int i = 0; i < count; i++ ) {
            WaitForSingleObject( this->_mutex, INFINITE );
            ++this->_number;
            ReleaseMutex( this->_mutex );
        }
    }

このコードは、頻繁にミューテックス・カーネルオブジェクトの待機と開放を繰り返しており、それが遅い原因の内の一つです。ですが、他にも原因があります。それは、ミューテックス・カーネルオブジェクトは、CPUをカーネルモードに切り替える必要があるからです。
 CPUには、あらゆる機械語命令が実行できるカーネルモードと、実行できる機械語命令が制限されたユーザーモードがあります。カーネルモードは、OSやデバイスドライバが動くモードでハードウェアを管理する為に使用します、一方ユーザーモードは、間違って危険な機械語命令を実行したり、クラッカーが意図して危険な機械語命令を実行したりしないように用意されています。
 ミューテックス・カーネルオブジェクトは、プロセス間で共有しているリソースを排他制御するために、カーネルモードに以降せねばなりません。普段はユーザーモードで稼働しているので、カーネルモードへ切り替えるにはコストが必要となります。それが、ミューテック・カーネルオブジェクトが、クリティカルセクションオブジェクトよりも遅い理由です。
 プロセス間で共有しているリソースにアクセスにするために、カーネルモードへの移行が必要な理由は、ユーザーモードでは基本的に違うプロセスのアドレスにアクセスできないからです。CPUとOSは、メモリのアドレスを、セグメントやページなどの単位で区切って管理しています。メモリのアドレスを区切って管理する理由は、ユーザーモードがある理由と同じです。仮に全てのメモリアドレスにアクセス出来てしまうと、プログラマがちょっと間違っただけでハードウェアがクラッシュしてしまいますし、あらゆるクラッキング行為が出来てしまいます。それを防ぐために、OSとCPUはメモリアドレスを区切って管理しているのです。
 今回の記事で、同期オブジェクトを使い分ける必要がある事が分かったと思います。今回取り上げた単純なサンプルでは、アトミックなInterlocked系関数を使用するのがベストです。次に良いのはクリティカルセクションです。一つの命令で済む場合はアトミック関数、数個の命令が必要になる場合はクリティカルセクションを使用し、プロセス間でリソースを共有する必要性がある場合にミューテックス・カーネルオブジェクトを使用しましょう。続く...

テーマ : プログラミング
ジャンル : コンピュータ

Win32並行処理プログラミング入門38

 この記事は、Win32並行処理プログラミング入門37の続きです。前回から引き続き、ミューテックス・カーネルオブジェクトについて解説します。
 ミューテックス・カーネルオブジェクトとは、スレッドが1つの共有リソース、もしくはコード片に、排他的にアクセスできるようにする同期オブジェクトです。
 排他的なアクセスとは、1つのスレッドがアクセスしている間、他のスレッドがアクセス出来ないという事です。並行処理時には、複数のスレッドが1つの共有リソースもしくはコード片にアクセスする事を許すと、正しい結果が得られなくなりますので、排他的にアクセスする同期オブジェクトが必要となります。
 この説明を読むと難しく感じるかもしれませんが、ミューテックス・カーネルオブジェクトの使用方法は非常に簡単です。前回掲載したサンプルを用いて、ミューテックス・カーネルオブジェクトに関する関数の解説をします。

    //ミューテックスの初期化と破棄が必要
    ConcurrentFoo() : _number( 0 ) 
    { 
        this->_mutex = CreateMutex( NULL, FALSE, NULL );
    }
    ~ConcurrentFoo()
    { 
        CloseHandle( this->_mutex );
    }

    //並列的に実行するメソッド
    //※並行度は高いが遅い
    void AddFunc( int count )
    {
        for ( int i = 0; i < count; i++ ) {
            WaitForSingleObject( this->_mutex, INFINITE );
            ++this->_number;
            ReleaseMutex( this->_mutex );
        }
    }

ミューテックス・カーネルオブジェクトに関する部分を抜粋しました。このサンプルを見れば分かると思いますが、ミューテックス・カーネルオブジェクトもクリティカルセクション同様、オブジェクトの初期化・待機・解放・オブジェクトの削除の4つの関数を使用します。
 クリティカルセクションとの違いは、ミューテックス・カーネルオブジェクトは独自の構造体ではなく、普通のハンドルを使用している点です。その点さえ気をつければ、クリティカルセクションとほぼ同様に、ミューテックス・カーネルオブジェクトに関する関数を使用できます。ですから、分からないのはCreateMutex関数だけだと思います。
 CreateMutex関数のパラメータは順に、セキュリティ属性・所有の有無を示すフラグ・名前の3つの引数を受け取ります。所有の有無とは、既にミューテックス・カーネルオブジェクトが誰かに所有されているのかを示します。通常はFALSEを指定し、ミューテックス・カーネルオブジェクトが保持するスレッドIDと再帰カウンタを0に初期化します。
 ミューテックス・カーネルオブジェクトは再帰カウンタを持ち、再帰カウンタの値を調べる事によりアクセスの状態を判断しています。ミューテックス・カーネルオブジェクトは、待機が成功するたびに再帰カウンタをデクリメントし、ReleaseMutex関数が呼び出されるごとに再帰カウンタをインクリメントします。これにより、排他的にアクセスをする事が可能となります。続く...
   

テーマ : プログラミング
ジャンル : コンピュータ

Win32並行処理プログラミング入門37

 この記事は、Win32並行処理プログラミング入門36の続きです。前回は優先順位とスレッドの終了方法について解説しました。今回はミューテックス・カーネルオブジェクトについて解説します。
 今までいくつかの同期オブジェクトを解説してきましたが、Windowsには他にも同期オブジェクトがあります。そのうちの一つが、ミューテックス・カーネルオブジェクトです。ミューテックス・カーネルオブジェクトの使用方法をサンプルを使って解説します。

#include <iostream>
#include <windows.h>
#include <tchar.h>
#include <process.h>
using namespace std;

//インスタンスのポインタとパラメータを保持するクラス
class InstanceAndParameter
{
private:
    void* ins;
    void* param;
public:
    InstanceAndParameter( void* ins, void* param ) 
        : ins( ins ), param( param ){}
    void* getInstance() const { return ins; } 
    void* getParameter() const { return param; }
};

//並行処理を意識したクラス
class ConcurrentFoo 
{
private:
    HANDLE _mutex;
    long _number;
public:
    //ミューテックスの初期化と破棄が必要
    ConcurrentFoo() : _number( 0 ) 
    { 
        this->_mutex = CreateMutex( NULL, FALSE, NULL );
    }
    ~ConcurrentFoo()
    { 
        CloseHandle( this->_mutex );
    }
    long getNumber() const { return _number; }

    //並列的に実行するメソッド
    //※並行度は高いが遅い
    void AddFunc( int count )
    {
        for ( int i = 0; i < count; i++ ) {
            WaitForSingleObject( this->_mutex, INFINITE );
            ++this->_number;
            ReleaseMutex( this->_mutex );
        }
    }

    //スレッドから呼び出すメソッド
    static unsigned __stdcall Add( void* pvParam ) 
    {
        //パラメータのチェック
        _ASSERTE( FALSE == IsBadReadPtr( 
            pvParam, sizeof( InstanceAndParameter ) ) 
            && "スレッドに渡されたパラメータが無効です" ); 

        //インスタンスとパラメータを取り出してメソッドを実行
        InstanceAndParameter* ip = 
            reinterpret_cast< InstanceAndParameter* >( pvParam );
        ConcurrentFoo* obj = 
            reinterpret_cast< ConcurrentFoo* >( ip->getInstance() );
        int count = PtrToInt(
            reinterpret_cast< INT_PTR >( ip->getParameter() ) );
        obj->AddFunc( count );
        return 0;
    }
};

int _tmain( int, _TCHAR* )
{
    //スレッド数の判定
    const int threadCount = 64;
    if ( threadCount > MAXIMUM_WAIT_OBJECTS ) {
        cout << "【エラー】" << endl;
        cout << "指定するスレッド数が多すぎます。" << endl;
        cout << "指定するスレッド数は" 
            << MAXIMUM_WAIT_OBJECTS 
            << "以下にして下さい。" << endl;
        return -1;
    }

    //スレッドのパラメータを用意する
    ConcurrentFoo obj;
    int count = 100000;
    InstanceAndParameter threadparam( 
        reinterpret_cast< void * >( &obj ), 
        reinterpret_cast< void * >( ( INT_PTR ) count ) );

    //スレッドを作成する
    HANDLE hThreads[ threadCount ];
    for ( int i = 0; i < threadCount; i++ )
    {
        hThreads[ i ] = reinterpret_cast< HANDLE >( 
            _beginthreadex ( 
                __nullptr,
                0U,
                ConcurrentFoo::Add,
                reinterpret_cast< void * >( &threadparam ) ,
                CREATE_SUSPENDED, //直ぐには実行しない
                __nullptr ) );
    }

    //作成したスレッドを実行
    for ( int i = 0; i < threadCount; i++) 
        ResumeThread( hThreads[ i ] );

    //タイムアウト時間をミリ単位で指定して待機
    //※指定した時間は正確ではありません
    DWORD result = WaitForMultipleObjects( 
        threadCount, hThreads, TRUE, 20000 );

    //WaitForSingleObject関数が終了した原因を表示
    cout << "【スレッドの状態を表示します】" << endl;
    unsigned int max = ( WAIT_OBJECT_0 + threadCount - 1 );
    if ( result == WAIT_FAILED ) { 
        //エラーを表示する
        DWORD error = GetLastError();
        LPVOID title = _T( "エラー" );
        LPVOID msg;
        FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
                FORMAT_MESSAGE_FROM_SYSTEM |
                FORMAT_MESSAGE_IGNORE_INSERTS,
            __nullptr,
            GetLastError(),
            MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // 既定の言語
            reinterpret_cast< LPTSTR >( &msg ),
            0,
            __nullptr
        );
        MessageBox( __nullptr, 
            reinterpret_cast< LPCTSTR >( msg ) , 
            reinterpret_cast< LPCTSTR >( title ), 
            MB_OK | MB_ICONERROR );
        LocalFree( msg );
        return -1;
    } 
    if ( result == WAIT_TIMEOUT ) {
        cout << "タイムアウトしてしまいました。" << endl;
    } else if ( ( result >= WAIT_OBJECT_0 ) & ( result <= max ) ) {
        cout << "全スレッドの処理が終わりました。" << endl;
    } 
    cout << endl;

    //変数の合計値を表示
    long correctValue = threadCount * count;
    long number = obj.getNumber();
    cout << "【変数の値を表示します】" << endl;
    cout << "予想値:" << correctValue << endl;  
    cout << "実際の値:" << number << endl;
    cout << "不足値:" << ( correctValue - number ) << endl;

    //ハンドルを閉じる
    for ( int i = 0; i < threadCount; i++ ) 
        CloseHandle( hThreads[ i ] );
    cout << endl;
    return 0;
}

このサンプルは、数値を指定回数分インクリメントする単純なものです。実行すれば、加算結果は正しいものの非常に遅い事が分かると思います。ミューテックス・カーネルオブジェクトに関する関数の解説と、処理が遅い原因について次回から詳しく解説します。続く...

テーマ : プログラミング
ジャンル : コンピュータ

Win32並行処理プログラミング入門36

 この記事は、Win32並行処理プログラミング入門35の続きです。前回はスレッドのスケジューリングについて解説しました。今回は優先順位とスレッドの終了方法について解説します。
 前回main関数の優先度を上げて、強制的にスレッドを強制させる事について少し触れました。この方法は、プロセス終了時に、プロセス内の全スレッドが強制終了される事を利用しています。しかしながら、この方法はお勧めできません。その理由は、各スレッドが終了処理をできないからです。仮にデータを扱うスレッドがあるとすると、強制終了されたスレッドはデータを保存できません。この状態は望ましくないので、プロセス終了によるスレッドの強制終了は避けるべきです。
 他にもスレッドを終了させる方法があります。ExitThread関数もしくはTerminateThread関数を使用すれば、プログラムでスレッドを終了させる事が出来ます。ExitThread関数は関数を実行したスレッドを終了させ、TerminateThread関数は他の任意のスレッドを強制終了できます。しかし、この2つの関数の使用も先ほどと同じ理由に加えて、C/C++ランタイムやカーネルリソースの状態が不安定になる可能性があるのでお勧めできません。それよりも、メソッドもしくは関数内で処理を正常に終了させる方法をお勧めします。そうすれば、リソースもデータもプログラマが意図した状態になります。
 あと、スレッドの優先度の変動は極力行わないでください。スレッドの優先度を変えると、飢餓状態と優先順位が逆転が発生する恐れがあります。優先順位の逆転とは、優先度が低いスレッドが共有リソースをロックしているのが原因で、優先度が高いスレッドが処理を進められなくなる現象です。それでも優先度を変える時は慎重に設計しましょう。仕様が理由で優先度を変えなければならない場面もあります。しかし、安易に優先度を変えると、飢餓状態・優先順位の逆転などといった並行処理の特有の問題が発生します。面倒な問題に頭を悩ませるよりも、シンプルな設計になるよう安易な優先度の変更は避けるべきなのです。続く...

テーマ : プログラミング
ジャンル : コンピュータ

Win32並行処理プログラミング入門35

 この記事は、Win32並行処理プログラミング入門34の続きです。前回から引き続きスレッドのスケジューリングについて解説します。
 Win32並行処理プログラミング入門33のサンプル内で、2つのスレッドスケジューリングを操作する関数を使用しました。1つめは下記の部分です。

//鳥の発言
unsigned __stdcall PiyoFunc( void* pvParam ) 
{
    for ( ; ; ) {
        cout << " ピヨ " ;
        SwitchToThread();
    }
    return 0;
}

SwitchToThread関数は、他のスレッドをスケジューリングさせます。このサンプルでは、相手の言葉を聞くのに使用しています。鳥と猫はちゃんと相手の話しを聞いているわけです。この関数の利用価値は、飢餓状態になる事を防げるかもしれない事です。飢餓状態というのは、優先順位が低いスレッドが何時までも実行できない状態の事です。並行処理プログラミングでは、飢餓状態に陥るスレッドがないか注意せねばなりません。
 実はずっと前の回からSwitchToThread関数と似た関数が登場しています。それはSleep関数です。この関数は実行したスレッドを保留状態にし、OSにスレッドスケジューリングさせます。Sleep関数は「寝る」という名から誤解されがちですが、処理を無効化するものではありません。また、Sleep関数はミリ秒単位を指定しますが、正確な時間を保証されていません。WindowsはリアルタイムOSではないので正確に時間を守る事は不可能なのです。
 Sleep関数とSwitchToThread関数の違いは優先度の扱い方です。Sleep関数に0を指定すると、普通にスケジューリングされるので、再度Sleep関数を実行したスレッドが実行される可能性があります。一方SwitchToThread関数は、優先度が低いスレッドが実行される可能性がありますし、SwitchToThread関数を実行したスレッドは再度スケジューリングされません。この違いは重要です。飢餓状態を避けたい場合はSwitchToThread関数を使用しましょう。
 2つめはmain関数の次の部分です。

//強制的に終わらせるためmain関数の優先順位を高くする
SetThreadPriority( 
    GetCurrentThread(), 
    THREAD_PRIORITY_TIME_CRITICAL );

SetThreadPriority関数はスレッドの相対優先度を設定する関数です。第2引数に優先度を指定します。指定できる優先度は、優先度が高い順に書くとTHREAD_PRIORITY_TIME_CRITICAL、THREAD_PRIORITY_HIGHEST、THREAD_PRIORITY_ABOVE_NORMAL、THREAD_PRIORITY_NORMAL、THREAD_PRIORITY_BELOW_NORMAL、THREAD_PRIORITY_LOWEST、THREAD_PRIORITY_IDLEの7つです。「相対」と呼んでいる理由は、前回説明したようにプロセスの優先度も加味されて、そこから最終的な優先度が決定されるからです。
 SetThreadPriority関数は、重要なスレッドの優先度を高めて実行する頻度を増やす目的と、重要でないスレッドの優先度を低くして実行頻度を減らす目的で使用されます。33回で示したサンプルでは、main関数が実行される機会を増やすために使用していますが、この様な方法はあまりお勧めできません。続く...

テーマ : プログラミング
ジャンル : コンピュータ

Win32並行処理プログラミング入門34

 この記事は、Win32並行処理プログラミング入門33の続きです。前回はスレッドスケジューリングの解説のためのサンプルを掲載しました。今回はスレッドスケジューリングの解説をします。
 Windowsのスレッドは、優先度に従ってスケジューリングされます。PC上で実行できるスレッドは複数ありますが、CPUが同時に実行できるプログラムは論理コア数と同じだけです。ですからOSは実行するスレッドを管理せねばなりません。それをスレッドスケジューリングと言います。スレッドスケジューリングは一見簡単そうに見えますが実際は複雑です。
 生成された順にスレッドを実行するだけならば、スレッドスケジューリングは簡単なのですが、そうしてしまうと色々な不都合があります。例えば、PC上で音楽を聴く時、音を奏でるプログラムがスレッドの生成順に実行されてしまうと、音楽が終わるまで(音楽に関するプログラムの終了まで)他の処理が出来なくなります。それを防ぐために、スレッドスケジューリングは色々な工夫がなされています。
 第1にスレッドは無制限に実行されません各スレッドにはタイムスライスと呼ばれる時間が割り当てらており、タイムスライスを使いきるとスレッドはいったん中断され、OSは他のスレッドが実行できるようにします。これにより、複数のアプリケーションを使用する事が出来ます。しかしこの機能だけでは、緊急を要するプログラムの実行が遅れてしまいます。そこで、スレッドには優先度が決められています。
 Windowsにおけるスレッドの優先順位決定法も複雑です。プロセスにも優先度が定められており、スレッドの優先度と併せて最終的な優先度が決定されます。優先度の対応については、OSの互換性と将来のバージョンを加味して非決定的となっています。最終的な優先度は、Windowsのバージョンが異なれば違う可能性があります。最終的な優先度に依存するプログラムを組んではなりません。続く...
 

テーマ : プログラミング
ジャンル : コンピュータ

Win32並行処理プログラミング入門33

 この記事は、Win32並行処理プログラミング入門32の続きです。前回はcoutの様なグローバルオブジェクトを、実務的な並行プログラミングで使用してはならない理由を詳細に解説しました。今回は、スレッドのスケジューリングを解説します。
早速ですが、論よりサンプルプログラムです。

#include <iostream>
#include <windows.h>
#include <tchar.h>
#include <process.h>
using namespace std;

//鳥の発言
unsigned __stdcall PiyoFunc( void* pvParam ) 
{
    for ( ; ; ) {
        cout << " ピヨ " ;
        SwitchToThread();
    }
    return 0;
}

//猫の発言
unsigned __stdcall NyaFunc( void* pvParam ) 
{
    for ( ; ; ) {
        cout << " にゃ~ "; 
        SwitchToThread();
    }
    return 0;
}

//にゃんぴよ会議開催
int _tmain( int, _TCHAR* )
{
    const int threadCount = 2;
    HANDLE hThreads[ threadCount ];

    //ピヨスレッドを作成する
    hThreads[ 0 ] = reinterpret_cast< HANDLE >( 
        _beginthreadex ( 
            __nullptr,
            0U,
            PiyoFunc,
            reinterpret_cast< void * >( ( INT_PTR ) 0 ) ,
            CREATE_SUSPENDED, //直ぐには実行しない
            __nullptr ) );

    //にゃ~スレッドを作成する
    hThreads[ 1 ] = reinterpret_cast< HANDLE >( 
        _beginthreadex ( 
            __nullptr,
            0U,
            NyaFunc,
            reinterpret_cast< void * >( ( INT_PTR ) 0 ) ,
            CREATE_SUSPENDED, //直ぐには実行しない
            __nullptr ) );
    
    //強制的に終わらせるためmain関数の優先順位を高くする
    SetThreadPriority( 
        GetCurrentThread(), 
        THREAD_PRIORITY_TIME_CRITICAL );

    //作成したスレッドを実行
    for ( int i = 0; i < threadCount; i++) {
        ResumeThread( hThreads[ i ] );
    }

    //タイムアウト時間をミリ単位で指定して待機
    //※指定した時間は正確ではありません
    DWORD result = WaitForMultipleObjects( 
        threadCount, hThreads, TRUE, 200 );

    //WaitForSingleObject関数が終了した原因を表示
    unsigned int max = ( WAIT_OBJECT_0 + threadCount - 1 );
    if ( result == WAIT_FAILED ) { 
        //エラーを表示する
        DWORD error = GetLastError();
        LPVOID title = _T( "エラー" );
        LPVOID msg;
        FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
                FORMAT_MESSAGE_FROM_SYSTEM |
                FORMAT_MESSAGE_IGNORE_INSERTS,
            __nullptr,
            GetLastError(),
            MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), // 既定の言語
            reinterpret_cast< LPTSTR >( &msg ),
            0,
            __nullptr
        );
        MessageBox( __nullptr, 
            reinterpret_cast< LPCTSTR >( msg ) , 
            reinterpret_cast< LPCTSTR >( title ), 
            MB_OK | MB_ICONERROR );
        LocalFree( msg );
        return -1;
    } 
    if ( result == WAIT_TIMEOUT ) {
        cout << "【時間切れワン。本日の会議は終了するワン。】" << endl;
    } else if ( ( result >= WAIT_OBJECT_0 ) & ( result <= max ) ) {
        cout << "全スレッドの処理が終わりました。" << endl;
    } 
    cout << endl;

    //ハンドルを閉じる
    for ( int i = 0; i < threadCount; i++ ) {
        CloseHandle( hThreads[ i ] );
        hThreads[ i ] = __nullptr;
    }
    cout << endl;
    return 0;
}

このサンプルは、猫と鳥が会議している様子を表しています。スレッドのスケジューリングに関する関数を使用しています。ちなみに、進行役は犬が勤めています。次回このサンプルを用いて、Windowsにおけるスレッドのスケジューリングおよび、スレッドスケジューリングに関するWin32APIを解説します。続く...

テーマ : プログラミング
ジャンル : コンピュータ

プロフィール

インドリ

Author:インドリ
みなさん、はじめまして、
コンニチハ。

ボクは、無限の夢(infinity dream)を持つネタ好きな虹色の鳥インドリ(in dre)です。
色々な情報処理技術を啄ばむから楽しみにしてね。

http://twitter.com/indori
は別人による嫌がらせ行為です。
私とは関係ないので注意して下さい。
次はなりすましブログなどをするかもしれませんが、ここ以外でブログをするつもりがないので、ここ以外にインドリのブログがあったとしても無視してください。


何度言っても分からない人がいるので、ここにコメント欄へ書き込むときの注意事項を書きます。


一、社会人としてのマナーをわきまえましょう。
一、妄想に基づく書き込みを止めてください。
一、暴言の類は書かないで下さい。
一、某誹謗中傷サイトの書き込みは彼らの妄想に基づく書き込みですから無視して、ここへ書き込まないで下さい。
一、コメント書く前に他のコメントよく読んでから行って下さい。
一、言いがかかり等の行為を禁止します。
一、その他常識的に考えて迷惑なコメントはしないで下さい。


以上のルールを守れない人のコメントは削除します。



利用上の注意
ここに紹介してある文章およびプログラムコードは正確であるように心がけておりますが、内容を保証するものではありません。当サイトの内容によって生じた損害については、一切の責任を負いませんので御了承ください。


執筆したCodeZineの記事


【VB.NETで仮想CPUを作ろう】

  1. VB.NETで仮想CPUを作ろう
  2. レジスタの実装
  3. 仮想CPUのGUI化
  4. テストドライバの改良
  5. CPUの基礎動作の実装
  6. MOV命令の実装
  7. ADD命令実装
  8. SUB命令実装
  9. INC命令&DEC命令の実装と命令長
  10. MLU命令の実装とModR/Mについて
  11. DIV命令の実装とイベント設計について
  12. 機械語駆動式 関数電卓を作ろう!
  13. 機械語駆動式 関数電卓を作ろう! 解答編(前半)
  14. 機械語駆動式 関数電卓を作ろう! 解答編(後半)


【仮想ネットワーク実装でTCP/IPを学ぼう】
  1. TCP/IPの基礎と勘所
  2. ネットワークアクセス層の勘所
  3. インターネット層の勘所
  4. トランスポート層の勘所
  5. アプリケーション層の勘所
  6. セキュリティの基礎と仮想ネットワークの仕様
  7. GDI+と独自プロトコルの定義



【並列化】
インテル Parallel Studioを使って並列化プログラミングを試してみた
並列プログラミングの効率的なデバッグを実現する「Parallel Inspector」


【TBBシリーズ】
  1. インテル スレッディング・ビルディング・ブロックの概要
  2. インテルTBBから学ぶループの並列化
  3. スレッドセーフとインテルTBBのコンテナ
  4. インテルTBBのスレッドクラス


【OpenMPシリーズ】
  1. OpenMPの基礎構文
  2. OpenMPの実行時ライブラリと並列ループ
  3. OpenMPのメモリモデルとfork- joinモデル

最近の記事
最近のコメント
月別アーカイブ
カテゴリ
Ada (9)
COBOL (5)
C (9)
C++ (11)
C# (370)
D (25)
Java (8)
Perl (1)
Ruby (14)
PHP (2)
Boo (2)
Cobra (2)
LISP (6)
F# (33)
HTML (0)
XHTML (0)
CSS (0)
XML (0)
XSLT (0)
Scala (4)
WPF (0)
WF (2)
WCF (0)
LINQ (4)
MONO (5)
Linux (0)
MySQL (0)
ブログ内検索
リンク
最近のトラックバック
RSSフィード
ブロとも申請フォーム

この人とブロともになる

QRコード
FC2カウンター