トップ «前の日記(2010/05/20(Thu)) 最新 次の日記(2010/05/27(Thu))»
【ソース+水=麦茶色の何か】

半期 四半期 全カテゴリ

今日の一言


2010/05/21(Fri) ベームベーム [長年日記] 2:00現在 17℃

_ [Linux][Ubuntu][Debian][FC][C++][研究関係][雑記] Linux上で、C言語(C++)を使ってマルチスレッドプログラミングする話(pthread,mutex)

自分がプログラミングに使うのは、基本的にLinuxだけなので、pthreadを使うのが主。

manを引用すると、

POSIX.1 は、一般に POSIX スレッドや Pthreads として知られるスレッド・プログラミングのインタフェース群 (関数、ヘッダファイル) を規定している。一つのプロセスは複数のスレッドを持つことができ、全てのスレッドは同じプログラムを実行する。これらのスレッドは同じ大域メモリ (データとヒープ領域) を共有するが、各スレッドは自分専用のスタック (自動変数) を持つ。 POSIX.1 はスレッド間でどのような属性を共有するかについても定めている (つまり、これらの属性はスレッド単位ではなくプロセス全体で共通である):

とのこと。

久々にマルチスレッドプログラミングにチャレンジしたので、実際にどう使うのかを簡単に書いておく。最低限、これだけ出来ればある程度対応できるはず。

*コンパイルには「-lpthread」が必要。

 #include <stdio.h>
 #include <stdlib.h>
 #include <pthread.h>

 typedef struct {
     char *c;
     int  i;
 }   St;

 void *thread_func1()
 {
     printf("0\n");
     return NULL;
 }
 void *thread_func2(void *arg)
 {
     int i = (int)arg;
     printf("%d\n",i);
     return NULL;
 }
 void* thread_func3(void *arg)
 {
      St *st = (St*)arg;
      printf("%d %s\n", st->i, st->c);
 }

 int main(void)
 {
     int i=1;
     St st;
     st.c = "struct";
     st.i = 100;

     pthread_t thread1;
     pthread_t thread2;
     pthread_t thread3;

     if (pthread_create(&thread1, NULL, thread_func1, (void *)NULL) != 0)
     { return EXIT_FAILURE; }
     if (pthread_create(&thread2, NULL, thread_func2, (void *)i) != 0)
     { return EXIT_FAILURE; }
     if (pthread_create(&thread3, NULL, thread_func3, (void *)&st) != 0)
     { return EXIT_FAILURE; }

     if (pthread_join(thread1, NULL) != 0)
     { return EXIT_FAILURE; }
     if (pthread_join(thread2, NULL) != 0)
     { return EXIT_FAILURE; }
     if (pthread_join(thread3, NULL) != 0)
     { return EXIT_FAILURE; }

     return EXIT_SUCCESS;
 }

上の例だと問題ないが、マルチスレッドでは、同じ変数に同時にアクセスするということが起こり得る。

そこで、「mutex」という考え方を導入する。

簡単に言うと、あるスレッドでロックした「mutex」に対し,別のスレッドがロックしようとすると、そのmutexを開放するまで待機させられるという仕組み。 Wikipediaとかにも載っているので、詳細は省く。

具体的には、こんな感じで使う。

 #include <stdio.h>
 #include <stdlib.h>
 #include <pthread.h>
 #include <unistd.h>

 volatile int cnt;
 static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

 void *add1()
 {

     int i;
     for(i=0; i<10; i++)
     {
       pthread_mutex_lock(&mutex);      	/* mutex ロック */
         cnt = cnt +1;
         printf("locked:1 ",cnt);
       pthread_mutex_unlock(&mutex);   	/* mutex アンロック */
       printf("%d\n",cnt);
       usleep(400);
      }
     return NULL;
 }
 void *add2()
 {
     int i=0;
     for(i=0; i<10; i++)
     {
       pthread_mutex_lock(&mutex);      	/* mutex ロック */
         cnt = cnt +2;
         printf("locked:2 ",cnt);
       pthread_mutex_unlock(&mutex);   	/* mutex アンロック */
       printf("%d\n",cnt);
       usleep(300);
     }
     return NULL;
 }

 int main(void)
 {
     pthread_t thread1;
     pthread_t thread2;
     pthread_mutex_init( &mutex, NULL ); //mutexを初期化

     if (pthread_create(&thread1, NULL, add1, (void *)NULL) != 0)
     { return EXIT_FAILURE; }
     if (pthread_create(&thread2, NULL, add2, (void *)NULL) != 0)
     { return EXIT_FAILURE; }

     if (pthread_join(thread1, NULL) != 0)
     { return EXIT_FAILURE; }
     if (pthread_join(thread2, NULL) != 0)
     { return EXIT_FAILURE; }

     return EXIT_SUCCESS;
 }

あと、グローバル変数等を使っている場合、コンパイラによって最適化されてしまう可能性があるので、「volatile」をつけるほうがいいと思われ。

かなり乱暴な説明だが、とりあえずこんな感じで。

もっと知りたかったら、このへん(http://www.cs.tsukuba.ac.jp/~yas/sie/pdsoft-2000/2001-01-18/)とかを見ればいいかもね。

_ [Linux][Ubuntu][Debian][FC][C++][研究関係][雑記] Linux上で、C++を使ってマルチスレッドプログラミングする話2(boost)

先ほど知ったのだが、「pthread」では、メンバ関数をスレッド化出来ない模様。

正確には、staticメンバ関数であれば、pthread_createの引数に出来るのだが、それはなんか気持ち悪い。

そこで、「boost」に含まれる「boost::thread」を試してみた。

まずはインストール。

 # aptitude install libboost-dev
 # ldconfig

で、こんな感じに書く。

*コンパイルには「-lboost_thread」が必要

 #include <iostream>
 #include <boost/thread.hpp>
 #include <boost/bind.hpp>
 using namespace std;
 using namespace boost;

 // メンバ関数をマルチスレッドで実行

 class bThread
 {
 private:
   int cnt;
   mutex mt;
 public:
   bThread(){cnt=0;}
   ~bThread(){}
   void *add1()
   {
       for(int i=0; i<10; i++)
       {
         usleep(400);
       mutex::scoped_lock look(mt);      	/* mutex ロック */
         cnt = cnt +1;
         printf("locked:1 ",cnt);
         printf("%d\n",cnt);
        }
       return NULL;
   }
   void *add2()
   {
       for(int i=0; i<10; i++)
       {
         usleep(300);
       mutex::scoped_lock look(mt);      	/* mutex ロック */
         cnt = cnt +2;
         printf("locked:2 ",cnt);
         printf("%d\n",cnt);
       }
       return NULL;
   }
 };

 int main(int argc, char **argv)
 {
   bThread thr;

   thread test1( bind(&bThread::add1, &thr) );
   thread test2( bind(&bThread::add2, &thr) );

   test1.join();
   test2.join();
   return 0;
 }

threadには引数は渡せず、bindというのを使って、変数をやりとりするらしい。

また、mutex::scoped_lockってのでmutexをロックできるのだが、アンロックが見当たらない。というのも、デストラクタでアンロックするらしく、スコープを抜けるとロックが解ける仕様らしい。

・・・なんか、これも気持ち悪いなぁ。

まあ、自分が使いこなせていないだけというのもあるが。

とりあえず、これだけメモっておこう。

*参考にしたサイト

http://www.yukun.info/blog/2008/07/boost-thread-mutex-condition.html

_ [Linux][Ubuntu][Debian][FC][OpenCV][C++][研究関係][雑記] Linux上で、C++を使ってマルチスレッドプログラミングする話3(pthreadでstaticでないメンバ関数をスレッド化する方法) + OpenCVのビデオIOをクラス化して動かす

参考にさせていただいたページ:

http://b-51.hp.infoseek.co.jp/programing.html

http://d.hatena.ne.jp/kasei_san/20070612/p1

ざっくりいうと、staticなメンバ関数を間に噛ませることで、非スタティックな関数をスレッドとして動くようにしようというのが本手法。

絶対、自分ではこんなの思いつかないや。もっと精進しよう。

で、自分でも使ってみたのが次のコード。

OpenCVを使っていたりして、サンプルとしてはかなりひどい。

が、めんどうなので、とりあえずこれでいく。

 #include <cv.h>
 #include <highgui.h>
 #include <ctype.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include "fbiad.h"
 #include <fstream>
 #include <string>
 #include <iomanip>
 #include <pthread.h>
 #include <iostream>

 extern "C"
 {
     typedef void* (*ThreadFunc_t)(void*);
 }

 static pthread_mutex_t mutex;  //同期用にmutexを確保

 using namespace std;

 /* IEEE1394Camera用クラス */
 class IEEE1394Camera
 {
 private:
   CvCapture *capture;
   IplImage *frame;
   CvVideoWriter *vw;
   double w, h;
   int c, num;
   CvFont font;
   char str[64];

 public:
   /* コンストラクタ */
   IEEE1394Camera(int index)
   {
     w = 320, h = 240;
     num = 0;
     capture = cvCaptureFromCAM (index);
   }
   /* デストラクタ */
   ~IEEE1394Camera()
   {
     cvReleaseCapture (&capture);
   }
   /* スレッド起動用静的メンバ関数 */
   static void* video_thread(void *arg)
   {
     int v_index = (int)arg;
     IEEE1394Camera Ccam(v_index);
     Ccam.video_io();
     return NULL;
   }
   /* 実際にスレッドで動かしたい関数 */
   void* video_io ()
   {
     //キャプチャサイズを設定
     cvSetCaptureProperty (capture, CV_CAP_PROP_FRAME_WIDTH, w);
     cvSetCaptureProperty (capture, CV_CAP_PROP_FRAME_HEIGHT, h);
     //ビデオライタ構造体を作成
     vw = cvCreateVideoWriter ("cap.avi", CV_FOURCC ('X', 'V', 'I', 'D'), 15, cvSize ((int) w, (int) h));
     cvInitFont (&font, CV_FONT_HERSHEY_COMPLEX, 0.7, 0.7);
     cvNamedWindow ("Capture", CV_WINDOW_AUTOSIZE);
     printf("video_get_ready...\n");
   pthread_mutex_lock( &mutex ); //mainでロックされているので、開放されるまで待機
   pthread_mutex_unlock( &mutex );
     //カメラから画像をキャプチャし,ファイルに出力
     while (1) {
       frame = cvQueryFrame (capture);
       snprintf (str, 64, "%03d[frame]", num);
       cvPutText (frame, str, cvPoint (10, 20), &font, CV_RGB (0, 255, 100));
       cvWriteFrame (vw, frame);
       cvShowImage ("Capture", frame);
       num++;
       c = cvWaitKey (10);
       if (c >= 0)
         break;
     }
     //書き込みを終了し,構造体を解放
     cvReleaseVideoWriter (&vw);
     cvDestroyWindow ("Capture");
     return 0;
   }
 };

 /* メイン関数 */
 int main(int argc, char **argv)
 {
   int cam_index;
   if (argc == 1 || (argc == 2 && strlen (argv[1]) == 1 && isdigit (argv[1][0])))
     cam_index = (argc == 2 ? argv[1][0] - '0' : 0);

 pthread_mutex_init( &mutex, NULL ); //mutexを初期化
 pthread_mutex_lock( &mutex ); //mutexにlockをかける
   pthread_t video_t;
   pthread_create( &video_t, NULL, (ThreadFunc_t)IEEE1394Camera::video_thread, (void *)cam_index );
   getchar();  //キー入力を待つ
 pthread_mutex_unlock( &mutex ); //lockを解除
   pthread_join( video_t, NULL );
 pthread_mutex_destroy(&mutex);          /*  ミューテックス破棄  今のLinuxでは意味はないが念のため */
   return 0;
 }

ここで重要なのは、

 extern "C"
 {
     typedef void* (*ThreadFunc_t)(void*);
 }

   /* スレッド起動用静的メンバ関数 */
   static void* video_thread(void *arg)
   {
     int v_index = (int)arg;
     IEEE1394Camera Ccam(v_index);
     Ccam.video_io();
     return NULL;
   }
   /* 実際にスレッドで動かしたい関数 */
   void* video_io ()

   pthread_create( &video_t, NULL, (ThreadFunc_t)IEEE1394Camera::video_thread, (void *)cam_index );

の部分。

*こちら(http://b-51.hp.infoseek.co.jp/programing.html)の手法をそのまま使わせてもらいました。

まず、staticメンバ変数であれば、pthread_createでスレッドとして起動できるので、「スレッド起動用静的メンバ関数」を準備。

が、UNIXの場合pthread_create(POSIX準拠のスレッドライブラリでスレッドを構築する関数)は型として「extern "C" void*(*)(void*)」を要求するため、型が違うというエラーが出る。

なので、C言語形式にリンケージ指定する必要があるが、メンバ関数にはextern "C"が使えない。そこで、

こちら(http://b-51.hp.infoseek.co.jp/programing.html

で紹介されている、typedefを使った回避方法を使わせていただいた。

以下引用。

以下のようにextern "C"付きでtypedefするとそのtypedefされたシンボルはC言語リンケージ指定までを含みます。

extern "C"
{
  typedef void* (*ThreadFunc_t)(void*);
}
    ・
    ・
    ・
  sts = pthread_create(&tid,NULL,(ThreadFunc_t)CTest::ThreadEntry,this);

これはThreadFunc_tという型名に「extern "C" void*(*)(void*)」(戻り値がvoid*で引数がvoid*一つのCリンケージ指定関数へのポインタ)という意味を持たせます。 ですので、最後の行のようにstaticメンバ関数をこの型名でキャストして渡してあげればワーニングも出ることなく、スレッドのエントリポイントにメンバ関数が使えます。

あとは「スレッド起動用静的メンバ関数」に実際にスレッドの実効処理を行うのは別のメンバ関数を呼び出す処理を書けばOK!

うーむ、なんというか、凄いな。

意外と綺麗にまとまってる。自分としては、これが一番スマートな気がする。

_ [Linux][Ubuntu][Debian][FC][OpenCV][C++][研究関係][雑記] Linux上で、C++を使ってマルチスレッドプログラミングする話3改(略)

引数としてクラスのポインタを渡して実行させるようにした。

汎用性を考えると、こっちのほうがいいと思われ。

 #include <cv.h>
 #include <highgui.h>
 #include <ctype.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include "fbiad.h"
 #include <fstream>
 #include <string>
 #include <iomanip>
 #include <pthread.h>
 #include <iostream>

 extern "C"
 {
     typedef void* (*ThreadFunc_t)(void*);
 }

 static pthread_mutex_t mutex;  //同期用にmutexを確保

 using namespace std;

 /* IEEE1394Camera用クラス */
 class IEEE1394Camera
 {
 private:
   CvCapture *capture;
   IplImage *frame;
   CvVideoWriter *vw;
   double w, h;
   int c, num;
   CvFont font;
   char str[64];

 public:
   /* コンストラクタ */
   IEEE1394Camera(int index)
   {
     w = 320, h = 240;
     num = 0;
     capture = cvCaptureFromCAM (index);
   }
   /* デストラクタ */
   ~IEEE1394Camera()
   {
     cvReleaseCapture (&capture);
   }
   /* スレッド起動用静的メンバ関数 */
   static void* video_thread(void *arg)
   {
     IEEE1394Camera* Ccam = (IEEE1394Camera*)arg;
     Ccam->video_io();
     return NULL;
   }
   /* 実際にスレッドで動かしたい関数 */
   void* video_io ()
   {
     //キャプチャサイズを設定
     cvSetCaptureProperty (capture, CV_CAP_PROP_FRAME_WIDTH, w);
     cvSetCaptureProperty (capture, CV_CAP_PROP_FRAME_HEIGHT, h);
     //ビデオライタ構造体を作成
     vw = cvCreateVideoWriter ("cap.avi", CV_FOURCC ('X', 'V', 'I', 'D'), 15, cvSize ((int) w, (int) h));
     cvInitFont (&font, CV_FONT_HERSHEY_COMPLEX, 0.7, 0.7);
     cvNamedWindow ("Capture", CV_WINDOW_AUTOSIZE);
     printf("video_get_ready...\n");
   pthread_mutex_lock( &mutex ); //mainでロックされているので、開放されるまで待機
   pthread_mutex_unlock( &mutex );
     //カメラから画像をキャプチャし,ファイルに出力
     while (1) {
       frame = cvQueryFrame (capture);
       snprintf (str, 64, "%03d[frame]", num);
       cvPutText (frame, str, cvPoint (10, 20), &font, CV_RGB (0, 255, 100));
       cvWriteFrame (vw, frame);
       cvShowImage ("Capture", frame);
       num++;
       c = cvWaitKey (10);
       if (c >= 0)
         break;
     }
     //書き込みを終了し,構造体を解放
     cvReleaseVideoWriter (&vw);
     cvDestroyWindow ("Capture");
     return 0;
   }
 };

 /* メイン関数 */
 int main(int argc, char **argv)
 {
   int cam_index;
   if (argc == 1 || (argc == 2 && strlen (argv[1]) == 1 && isdigit (argv[1][0])))
     cam_index = (argc == 2 ? argv[1][0] - '0' : 0);
   IEEE1394Camera Ccam(cam_index);

 pthread_mutex_init( &mutex, NULL ); //mutexを初期化
 pthread_mutex_lock( &mutex ); //mutexにlockをかける
   pthread_t video_t;
   pthread_create( &video_t, NULL, (ThreadFunc_t)IEEE1394Camera::video_thread, (void *)&Ccam );
   getchar();  //キー入力を待つ
 pthread_mutex_unlock( &mutex ); //lockを解除
   pthread_join( video_t, NULL );
 pthread_mutex_destroy(&mutex);          /*  ミューテックス破棄  今のLinuxでは意味はないが念のため */
   return 0;
 }

コンパイルはこんな感じで。

 $ g++ `pkg-config --cflags opencv` `pkg-config --libs opencv` pthread_video_io_2.cpp
本日のツッコミ(全1件) [ツッコミを入れる]
_ メロン (2010/06/18(Fri) 17:39)

はじめまして
いつも黙って拝見させて頂いてますm(_ _)m
私も今、IEEE1394カメラからDIVXで録画するのをやってましてOpenCVを使ってるんですが
ココにあるようにcvWriteFrame()を時間制御しながらフレームを追加していくことは出来たんですが
これに同期してマイク入力から録音をしないといけないんですが
そんな方法ってないんですかね・・・