渋谷 克智:Linux と pthreads によるマルチスレッドプログラミング入門

作成日 : 2020-03-16
最終更新日 :

概要

帯の惹句は「レガシーな技術を今あえて学ぶ!」。C 言語でマルチスレッドを実装する方法を pthread を用いて学ぶ

感想

マルチスレッドは Java 言語で有名になったが、ほかの言語でも使える。 C 言語でも前から実装はされていたが、そのときは標準化がされていなかった。 POSIX による pthread になってからは(いつのころだろう?)、 安定してマルチスレッドプログラミングができるようになった。 私が見る限り、適切な記載がされているように思える。

せっかくなので、私の環境でも試してみた。 ただし、Ubuntu でも Windows Subsystem for Linux (Windows 10 Home Edition 上)の Ubuntu 16.04.6 である。以降、キーボード入力個所は下線をつけて表示する。

まず、Makefile で躓いた。本書 pp.15-16 のコード 2-2 firstThread 用 Makefile でコンパイルすると次の結果となる。

$ make
gcc  -pthread firstThread.c -o firstThread.o
gcc  -pthread  firstThread.o   -o firstThread
firstThread.o: In function `_fini':
(.fini+0x0): multiple definition of `_fini'
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o:(.fini+0x0): first defined here
firstThread.o: In function `data_start':
(.data+0x0): multiple definition of `__data_start'
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o:(.data+0x0): first defined here
firstThread.o: In function `data_start':
(.data+0x8): multiple definition of `__dso_handle'
/usr/lib/gcc/x86_64-linux-gnu/5/crtbegin.o:(.data+0x0): first defined here
firstThread.o:(.rodata+0x0): multiple definition of `_IO_stdin_used'
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o:(.rodata.cst4+0x0): first defined here
firstThread.o: In function `_start':
(.text+0x0): multiple definition of `_start'
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o:(.text+0x0): first defined here
firstThread.o: In function `_init':
(.init+0x0): multiple definition of `_init'
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crti.o:(.init+0x0): first defined here
/usr/lib/gcc/x86_64-linux-gnu/5/crtend.o:(.tm_clone_table+0x0): multiple definition of `__TMC_END__'
firstThread.o:(.data+0x10): first defined here
/usr/bin/ld: error in firstThread.o(.eh_frame); no .eh_frame_hdr table will be created.
collect2: error: ld returned 1 exit status
<builtin>: recipe for target 'firstThread' failed
make: *** [firstThread] Error 1

ここまで出るとへこむ。しかし、めげてはいられない。multiple definition of ... というのが頻出しているので、 何かのライブラリが二重に定義されているに違いない。 そしておそらくは pthread に関係しそうなものだろう。では、オブジェクトファイルを作らずに直接実行ファイルを作ることはできないか。 make を使わずに直接コマンドラインを打ってみる。

$ gcc  -pthread firstThread.c -o firstThread
$

エラーは出ない。ということは、オブジェクトファイルを作るときの過程に問題があったようだ。 次に手打ちで次のようにしてみよう。

$ gcc  -c firstThread.c 
$
$ gcc  -pthread firstThread.o -o firstThread

こちらも問題ない。逆はどうか。

$ gcc  -pthread -c firstThread.c
$ 
$ gcc  firstThread.o -o firstThread
firstThread.o: In function `main':
firstThread.c:(.text+0x72): undefined reference to `pthread_create'
collect2: error: ld returned 1 exit status

`pthread_create' が参照できない、ということだ。だから、-pthread はローダ・リンカーだけに作用すればいいようだ。

ということで、私は、コード 2-2 で、
CFLAGS := $(CFLAGS) -pthread
となっている行をコメントアウトしている。

キャスト

本書の主題であるマルチスレッドでは正しい主張がなされていると思う(正直にいうと、 わたしの能力を超えているので主張の是非の判断ができない)。 それ以外のところで気になった個所を述べる。 まずキャストについて、p.24 では次のように書かれている。

コード2-6のプログラムでは、pthread_create関数の説明の中で簡単に触れていた、 スレッド実行関数への引数を使っています。 (pthread_createの定義に従うため引数をvoid *型に強引にキャストしています。 お使いのコンパイラの設定などによっては、キャストに対する注意が出来るかもしれませんが、無視してください。)

実際、私の環境で試したところ、次のように注意(ウォーニング)が出た:

$ make
cc -c -o firstThread3.o firstThread3.c
firstThread3.c: In function ‘threadFunc’:
firstThread3.c:7:13: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
     int n = (int)arg;
             ^
firstThread3.c: In function ‘main’:
firstThread3.c:25:51: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
     if (pthread_create(&thread, NULL, threadFunc, (void *)n ) != 0) {
                                                   ^
cc  -pthread firstThread3.o -o firstThread3
$

これを防ぐだけならば、p.23 のコードを次のようにすればよい:

なお、pthread_create の定義に従うためとあるのは、pthread_create の関数プロトタイプが次のようになっていることを指す。 特に第4引数に着目してほしい。

    #include <pthread.h>

    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

100 万回に 1 回

pp.90-91 では、稀にしか発生しない事象への対応の心構えについて次のように述べられている:

プログラミング初心者はしばしば「そんな数メートル先から針の穴を通すような事象が本当に起こるんだろうか?」 と考えてしまいがちですが、100 万回に 1 回しか起きない事象でも 100 万回繰り返して実行すればきっと発生するのです。 たいていの実用的なプログラムは何日も動かし続けても誤計算をしてはいけません。

稀にしか起こらない事象に対しても備えをしなければいけないという趣旨であり、これは正しい。 しかし、口うるさいようだが、「100 万回に 1 回しか起きない事象でも 100 万回繰り返して実行すればきっと発生する」 は厳密には正しくない。 というのは、この事象を確率的に考えれば、1 回の事象で誤計算が起こる確率を p としたとき、 1 回の事象は前後の事象とは関係なく独立に起こると考えられるので、 100 万回繰り返しても 1 回必ずその事象が起こるとは限らないからだ。 100 万本のクジがあり、そのなかに誤計算のクジが1本、正常計算のクジが 999,999 本あるとすると、 プログラムを動かし続けるというのは、合わせて 100 万本のクジから、①1本を選んで、②結果が正常計算か誤計算かを判断して、 ③それを戻す、という①から③までの繰り返しであるということだ。 だから、うまくすれば 100 万回クジを引いても、1 本も誤計算のクジをひかない確率もある。 ただ、そのような 1 本も誤計算のクジをひかない確率 p はどれだけかというと、(1 - 1/n)n (n = 1,000,000) を計算すればよい。まじめに計算すると、0.367879 …… という値となる。 これは、1 / e (e は自然対数の底) に近い。 あれ、けっこう発生しないのではないか、と思われるかもしれないが、1回発生したら、 それは誤計算なのである。著者は数学的な厳密さを捨てて、「きっと」ということばに戒めを込めたのだ。

この話に関連して、本田技研工業の創始者である本田宗一郎のことばを次のページ Honda | 語り継ぎたいこと | 『120%の良品を目指せ!』。 お客さま第一主義の意味深い言葉 / 1953 (www.honda.co.jp) から紹介する。 評者である私自身は、世の中一般の過剰品質に疑問を抱いているが、 それでもこのことばは私には耳が痛い。

100%を目指したんじゃあ、人間のすることだから、1%やそこいらのミスをする。 その1%を買ったお客さんには、Hondaは、100%の不良品をお売りしたことになってしまう。 だからミスをなくすために120%を目指さなければならないんだ

#define の使用について

p.52 のサンプル以降、コードのいくつかで #define ディレクティブが用いられている。 現代の C では、const int を使用するのがよいと思われる。著者はもちろん const int のことは知っていて、 簡明さのために #define ディレクティブを採用したのだろう。

粒度の語感

プログラミング全般で「粒度」ということばがよく出てくる。もちろん、元は砂や土などの細かさ、という意味で、 コンピュータの分野では全体を細分化するときの細分化の高さを表すときに使われている。 さて、p.124 で、デッドロックの原因の一つである相互ロックの発生について、この発生を回避する方法が複数述べられている。 最初の方法は「複数のミューテックスを同時にロックしない」という題が掲げられていて、 その説明として「できるだけ排他処理をする粒度をあげ、 一つのミューテックスで複数の資源獲得の排他を行なうようにし、複数のミューテックスを同時にロックしなくてよいようにします。」 とある。この<粒度をあげ>という表現は、<粒度を粗くして>とするのが適当なのではないだろうか。 一つのミューテックスで複数の資源の面倒を見る、というのは上げ/下げの関係より粗い/細かいの関係が適切だと思うからだ。 ただ、程度を表す名詞とその程度を示す形容詞の関係というのは、日本語のなかで悩ましい問題である。

なお、デッドロックの問題はこのマルチスレッドのみならず、データベースでは避けられない問題であり、 むしろデータベース関係での現象と対応が多く報告されていることを付記する。

soloFly プログラム

pp.138-pp.145 にあるプログラムを実行したら、本来 Destination? の文字列が画面の下側に表示されるべきところが、 まったく表示されず、カーソルのみが左上部に存在する。キーボードを入力しても文字が表示されないが、 おそらく文字は受け付けていると思われる。 実際、表示されなくとも数字とスペースと Enter キーを入力すると、 それらしい個所に Fly が移動している。 どうしてこのような現象になるのかはわからないが、Windows のターミナル(いわゆる DOS 窓)や、 Windows PowerShell の画面で行なっていることが関係しているに違いない。 ネイティブの Linux でどうなるか後で試してみたい。

誤植

p.127 「まれにアンロックも読み出しロックの解除と書き込みロックの解除に別れることがありますが、」 ⇒「~解除に分かれることがありますが、」

p.237 「たしかに、第3章で述べた……」⇒「たしかに、第5章で述べた……」

p.238 「……同じ第3章の」⇒「……同じ第5章の」

スタイルについて

引数のない main 関数については、 int main() のスタイルと int main(void) のスタイルが混在している。int main(void) に統一するのがよいと思う。

書誌情報

書名Linux と pthreads によるマルチスレッドプログラミング入門
著者渋谷 克智
発行日978-4-7980-5372-1
発行元秀和システム
定価2400 円(本体)
サイズ判 ページ
ISBN

まりんきょ学問所コンピュータの部屋コンピュータの本プログラミング > 渋谷 克智:Linux と pthreads によるマルチスレッドプログラミング入門


MARUYAMA Satosi