mojavy.com

pthreadの取り消しポイント(cancellation point)についてのメモ

March 18, 2014 at 09:41 PM | categories: unix, programming |

cancellation pointsとは、スレッドのキャンセル種別がdeferredのときに、そこに到達したときにはじめて実際にそのスレッドのキャンセル要求が処理されるような関数のこと。

POSIX.1では、基本的にはブロックするような関数がcancellation pointsであることが要求されている。

参考



スレッドプールの実装方法について

March 03, 2014 at 08:58 PM | categories: unix, programming |

スレッドプール(thread pool)を実装するには、暇なときはthreadを寝かせておいて必要なときに起こす、というイベント通知の仕組みが必要になる。 UnixでC/C++で実装するときはpthreadの条件変数を使うのが普通だと思われるが、適当なファイルディスクリプタをopenしておいてread等でブロックさせる方法でも実装できそう。

どのようなやり方が一般的なのか、いくつか有名どころのOSSの実装を調べてみた。

libuvの場合

https://github.com/joyent/libuv

単純にpthread_cond_waitをつかっている 1

static void worker(void* arg) {
  struct uv__work* w;
  QUEUE* q;

  (void) arg;

  for (;;) {
    uv_mutex_lock(&mutex);

    while (QUEUE_EMPTY(&wq))
      uv_cond_wait(&cond, &mutex);

    q = QUEUE_HEAD(&wq);

    if (q == &exit_message)
      uv_cond_signal(&cond);
    else {
      QUEUE_REMOVE(q);
      QUEUE_INIT(q);  /* Signal uv_cancel() that the work req is
                             executing. */
    }

    uv_mutex_unlock(&mutex);

    if (q == &exit_message)
      break;

    w = QUEUE_DATA(q, struct uv__work, wq);
    w->work(w);

    uv_mutex_lock(&w->loop->wq_mutex);
    w->work = NULL;  /* Signal uv_cancel() that the work req is done
                        executing. */
    QUEUE_INSERT_TAIL(&w->loop->wq, &w->wq);
    uv_async_send(&w->loop->wq_async);
    uv_mutex_unlock(&w->loop->wq_mutex);
  }
}

Boost.Asioの場合

http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio.html

Boost.Asioにスレッドプールそのものは提供されてないが以下のようにして簡単に実装することができる

#include <thread>
#include <functional>
#include <boost/asio.hpp>

int main ( int argc, char* argv[] ) {
    asio::io_service io_service;
    asio::io_service::work work(io_service);

    std::vector<std::thread> threadPool;

    for(size_t t = 0; t < std::thread::hardware_concurrency(); t++){
        threadPool.push_back(thread(std::bind(&asio::io_service::run, &io_service)));
    }

    io_service.post(std::bind(an_expensive_calculation, 42));
    io_service.post(std::bind(a_long_running_task, 123));

    //Do some things with the main thread

    io_service.stop();
    for(std::thread& t : threadPool) {
        t.join();
    }
}

http://stackoverflow.com/questions/14265676/using-boostasio-thread-pool-for-general-purpose-tasks

長くなるのでコードは省略するが、io_service::postするとunixの場合は最終的にはtask_io_service::wake_one_idle_thread_and_unlockからpthread_cond_signalが呼ばれる。

memcachedの場合

https://github.com/memcached/memcached

libeventのイベント通知機能を利用して実装している。それぞれのthread初期化の際にpipeをつくって、そのfdをlibeventに渡す。 2 libevent内部でそのfdをepollなりkqueueなりでブロックして待つ。

//
// memcached.c
//
typedef struct {
    pthread_t thread_id;        /* unique ID of this thread */
    struct event_base *base;    /* libevent handle this thread uses */
    struct event notify_event;  /* listen event for notify pipe */
    int notify_receive_fd;      /* receiving end of notify pipe */
    int notify_send_fd;         /* sending end of notify pipe */
    struct thread_stats stats;  /* Stats generated by this thread */
    struct conn_queue *new_conn_queue; /* queue of new connections to handle */
    cache_t *suffix_cache;      /* suffix cache */
    uint8_t item_lock_type;     /* use fine-grained or global item lock */
} LIBEVENT_THREAD;

//
// thread.c
//
void thread_init(int nthreads, struct event_base *main_base) {
//
// 中略
//
    threads = calloc(nthreads, sizeof(LIBEVENT_THREAD));
//
// さらに中略
//
    for (i = 0; i < nthreads; i++) {
        int fds[2];
        if (pipe(fds)) {
            perror("Can't create notify pipe");
            exit(1);
        }

        threads[i].notify_receive_fd = fds[0];
        threads[i].notify_send_fd = fds[1];

        setup_thread(&threads[i]);
        /* Reserve three fds for the libevent base, and two for the pipe */
        stats.reserved_fds += 5;
    }

    /* Create threads after we've done all the libevent setup. */
    for (i = 0; i < nthreads; i++) {
        create_worker(worker_libevent, &threads[i]);
    }

    /* Wait for all the threads to set themselves up before returning. */
    pthread_mutex_lock(&init_lock);
    wait_for_thread_registration(nthreads);
    pthread_mutex_unlock(&init_lock);
}

pthread_cond_waitの実装

脱線するが、pthread_cond_waitがどのようにsleepにはいってるのか気になったので調べた。

https://sourceware.org/git/?p=glibc.git;a=tree;f=nptl;hb=HEAD

pthread_cond_waitのソースコードはglibcnptl以下にある。 __pthread_cond_waitlll_futex_waitを呼んでおり、これは以下のように実装されている。(以下はx86_64のもの)

#define lll_futex_wait(futex, val, private) \
  lll_futex_timed_wait(futex, val, NULL, private)

#define lll_futex_timed_wait(futex, val, timeout, private) \
  ({                                         \
    register const struct timespec *__to __asm ("r10") = timeout;          \
    int __status;                                \
    register __typeof (val) _val __asm ("edx") = (val);                \
    __asm __volatile ("syscall"                            \
             : "=a" (__status)                         \
             : "0" (SYS_futex), "D" (futex),                 \
           "S" (__lll_private_flag (FUTEX_WAIT, private)),         \
           "d" (_val), "r" (__to)                    \
             : "memory", "cc", "r11", "cx");                 \
    __status;                                    \
  })

上記アセンブラは大体以下のような意味3

futex(futex, FUTEX_WAIT, val, timeout, NULL, 0);  // 便宜上、上記コードの引数の変数名をそのままつかっているが、
                                                  // 1つめのfutexはシステムコールのfutexで、
                                                  // 2つめは引数のpthread_cond_tの__futexメンバ変数のアドレス

futex() システムコールは、 指定したアドレスの値が変更されるのをプログラムが待つ手段や 特定のアドレスに対して待機中のプロセスを wake (起床) させる手段を提供する

futex(2) http://linuxjm.sourceforge.jp/html/LDP_man-pages/man2/futex.2.html

とのこと。

まとめ

  • pthread_cond_waitをつかったもののほうが普通は高速なはず
  • memcachedのようなやりかただとユーザプロセス側でスレッドプール管理のための排他制御がほとんど不要になるので多少実装が簡単か

  1. pthread_cond_waitはunixの場合。windowsの場合はpSleepConditionVariableCS、これが使えない場合は疑似的に同様の動作をするようなラッパを定義している。 

  2. memcachedでは使用してないが、libeventはシグナルを通知する際もfdをつかう。Boost.Asioもシグナル通知はpipeを経由する。 

  3. 厳密には違う。 



シェルスクリプトでプロセスの多重起動を防止する簡単で安全な方法

February 16, 2014 at 06:35 PM | categories: unix, shell |

flock(1)を使うのが一番安全かつ簡単

LOCKFILE=/tmp/my.lockfile

(
    flock -n 200 || exit 1

    # do something
) 200>$LOCKFILE

タイムアウトを設定したければ-wオプションをつかえばよい。

リードライトロックとしてつかえるので、更新系のスクリプトは1つしか起動したくないけど参照系は並列実行を許す、みたいなことも比較的簡単にできる。



Mach-Oバイナリのライブラリロードパスをカスタマイズする方法

May 17, 2013 at 07:46 PM | categories: osx, unix, elf, mach-o |

matrix

ライブラリの単体テストをするときとかに、実行プログラムがロードする共有ライブラリのパスを任意のディレクトリで上書きしたいときがある。

例えば以下のようなディレクトリ構成で、project/t/mytestというバイナリをビルドするときにproject/src/libmy.soをリンクするようにしておけば作業しやすい。

└── project
    ├── src
    │   ├── libmy.a
    │   ├── libmy.so -> libmy.so.1
    │   ├── libmy.so.1
    │   ├── Makefile
    │   ├── mylib.c
    │   ├── mylib.h
    │   └── mylib.o
    └── t
        ├── Makefile
        ├── mytest
        ├── mytest.c
        └── mytest.o

こういうときは、mytestをビルドするときに以下のようにしてrpathを相対パスで追加していた。

$ cc -I../src -L../src -Wl,-rpath=../src *.c -lmy -o mytest
$ readelf -d mytest
Dynamic section at offset 0xe30 contains 22 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libmy.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000f (RPATH)              Library rpath: [../src]
 0x000000000000000c (INIT)               0x4004c8
 0x000000000000000d (FINI)               0x4006f8
 0x000000006ffffef5 (GNU_HASH)           0x400298
 0x0000000000000005 (STRTAB)             0x4003c0
 0x0000000000000006 (SYMTAB)             0x4002d0
 0x000000000000000a (STRSZ)              134 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x600fe8
 0x0000000000000002 (PLTRELSZ)           48 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x400498
 0x0000000000000007 (RELA)               0x400480
 0x0000000000000008 (RELASZ)             24 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x000000006ffffffe (VERNEED)            0x400460
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x400446
 0x0000000000000000 (NULL)               0x0

しかし、OSXの場合は上記のように単に実行バイナリ側にrpathを追加しただけだと、ローダがrpathを設定するコマンドより先にライブラリをロードするコマンドを実行しようとして該当ファイルがみつけられなくて以下のようなエラーになってしまう.

dyld: Library not loaded: libmy.1.dylib
  Referenced from: /Users/path/to/project/t/./mytest
  Reason: image not found
zsh: trace trap  ./mytest

otool -l <executable file>でロードコマンドの詳細をみると以下のようなエントリがみつかるが、ここのnameの値はライブラリ側のinstall_pathが設定される。

-- 中略
Load command 11
          cmd LC_LOAD_DYLIB
      cmdsize 40
         name libmy.1.dylib (offset 24)
   time stamp 2 Thu Jan  1 09:00:02 1970
      current version 1.0.0
compatibility version 0.0.0

install_pathsonameのかわりのようなもので、なにも指定しなければ出力ファイル名になる。 ここに@executable_path@rpathをつかうことによって、このダイナミックライブラリをリンクする側のバイナリに応じて挙動をかえることができる。 これらの変数(?)の詳細は参考リンクに解説がある。

要は、ELFのrpathとおなじような挙動にしたければ、 -Wl,-install_name,@rpath/libmy.1.dylib というようなオプション付きでライブラリをビルドすればよい。

また、-Wl,-install_name,@executable_path/../src/libmy.1.dylibのようにすると実行ファイルからの相対パスにすることができる。

この情報はリンクされる側のライブラリに埋めこまれている点に注意。

ELFに比べると柔軟にロードパスを制御することができると思われるが、うまく活用するのはちょっと難しそう。

参考リンク



おやつの時間をお知らせしてくれるUnixコマンド:at teatime (他...)

January 08, 2013 at 08:30 PM | categories: unix, tips, linux |

banana

Favorite Linux Commands(http://clippy.in/b/YJLM9W) で紹介されてたコマンドのうち知らなかったものをメモ。

at

入力されたコマンドを指定されたタイミングで実行するようにスケジュールする。 cronに登録するほどでもない単発のコマンドを実行したいときとかにつかう。 時間の指定には現在の時刻からの経過時間や絶対時間の他にteatimeやmidnightといった文字列もつかえる。 ちなみにteatimeは午後4時。 出力先を指定しなければコマンドの出力はcronと同じようにメールにとぶ。$TTYにリダイレクトしてやればコマンドを実行した端末に表示させることもできる。

echo "echo おやつの時間です > $TTY"| at teatime
echo "echo はろー > $TTY" | at now + 3 minute

mtr

tracerouteとpingをあわせたようなもの。tracerouteより表示がみやすい。 ネットワークのどこで時間がかかってるのか一目でわかる。

mtr mojavy.com

column

入力テキストをいい感じのカラム表示にフォーマットしてくれる。 縦に長い出力するコマンドとか、そのままだと出力がみづらいときとかにつかう。

gem list | column
mount | column -t
grep -v '^#' /etc/apt/sources.list | column -t

reset

端末をリセットする。 うっかりバイナリファイルをcatとかしてしまって端末が壊れてしまった場合に端末を閉じずに復帰できる。

sshfs

ssh経由でリモートのファイルシステムをマウントできる。

sshfs name@server:/path/to/dir /path/to/mount/point
fusermount -u /path/to/mount/point  # unmount

その他

以下はコマンド自体の機能ではないけど覚えておくと便利かもしれないもの

curl ifconfig.me

ifconfig.meというサイトがある。自分のグローバルIPがわかる。

dig +short txt .wp.dg.cx

dnsクエリでwikipediaのエントリーがみれる。

$ dig +short txt banana.wp.dg.cx
"Banana is the common name for herbaceous plants of the genus Musa and for the fruit they produce. It is one of the oldest cultivated plants. They are native to tropical South and Southeast Asia, and are likely to have been first domesticated in Papua New " "Guinea. Today, they are cultivated throughout the tropics. They are grown in at least 107 countries, primarily for their... http://en.wikipedia.org/wiki/Banana"


About Me

pic
mojavy

Recent posts






Categories



Badges