プログラミング言語を比較してみる(1)

4月 3rd, 2011

主流を占める手続き型のプログラミング言語には、共通の機能があります。C、C++、Java、PHP、JavaScript、Pythonを例にまとめてみることにします。まずはプログラムの「流れ」を決める制御構造。

手続き型のプログラムは基本的にを順次実行することによって構成されます。C、C++、Java、PHP、JavaScriptではstatementは;で終わり、Pythonは改行で終わります。

まずは条件分岐から。C、C++、Java、PHP、JavaScriptは以下のように書きます。

if( cond ){
  statement;
}
else if( cond2 ){
  statement;
}
else{
  statement;
}

Pythonでは

if cond :
  statement
elif cond :
  statement
else
  statement

switch~case文ってのもあって、C、C++、Java、PHP、JavaScriptでは以下のとおり。

switch( cond ){
  case value:
    statement;
    break;
  case value2:
    statement;
    break;
  default:
    statement;
}

Pythonにはswitch~case文はありません。

続いて、繰り返し。C、C++、Java、PHP、JavaScriptでは以下の通り。

// カウンタループ
for( 初期条件; 継続条件; カウンタ変数の更新 ){
  statement;
}

// 前判定型ループ
while( 継続条件 ){
  statement;
}

// 後判定型ループ
do{
  statement;
while( 継続条件 );

Pythonでは、

# 前判定型ループ
while 継続条件:
  statement
else
  statement

# Pythonにはカウンタループと、後判定型のループはありません。

どの言語も、ループからの早期脱出にはbreak文が、次ループへのスキップにはcontinue文が使えます。Pythonのwhile~elseブロックのstatementは継続条件が偽(break脱出でない)時に実行されます。

C、C++、PHPにはgoto文があり、深いループから一気に脱出する際などに使われます。

goto label;

label:
statement;

Java、JavaScript、Pythonにはgoto文はありません。

Pythonにはカウンタループがないと書きましたが、もちろん同等の機能を実現することはできます。例えば、0~9までのループはこのようになります。

for i in range(10):
  print i
else:
 print "the last value was %d" % i

Pythonのforループはもともとイテレーションを念頭に作られているのです。例えばリストを内部イテレータとして、次のような反復が美しく書けます。

list = [0,1,1,2,3,5,8,13,21,34]

for i in list:
  print i

同じような使い方はPHPでも可能です。PHPではforeachを使います。

#!/usr/local/bin/php
<?php
$list = array(0,1,1,2,3,5,8,13,21,34);

foreach($list as $i)
 print( $i."\n" );
?>

Cにはイテレータの仕組みは用意されていませんが、C++はSTLで外部イテレータが実装されています。

#include <iostream>
#include <vector>

int
main(void)
{
  using namespace std;

  vector<int> array;

  array.push_back(0);
  array.push_back(1);
  array.push_back(1);
  array.push_back(2);
  array.push_back(3);
  array.push_back(5);
  array.push_back(8);
  array.push_back(13);
  array.push_back(21);
  array.push_back(34);

  vector<int>::iterator i;

  for(i=array.begin(); i!=array.end(); i++)
    cout << *i << endl;
}

Javaにも似た仕組みがあります。

import java.util.*;

public class ILoop{
  public static void
  main(String[] args){
    LinkedList<Integer> l = new LinkedList<Integer>();

    l.add(0);
    l.add(1);
    l.add(1);
    l.add(2);
    l.add(3);
    l.add(5);
    l.add(8);
    l.add(13);
    l.add(21);
    l.add(34);
    for(Iterator i=l.iterator(); i.hasNext(); ){
      System.out.println( i.next() );
    }
  }
}

C++には例外的な事象を捕まえるのに便利なtry~catchがあります。

#include <iostream>

int
main(void)
{
  using namespace std;

  try{
    for(int i=0; i<100; i++){
      cout << i << endl;
      if(i==10)
        throw( "bye" );
    }
  }
  catch( const char* message ){
    cout << message << " is thrown!" << endl;
  }
}

Java、JavaScriptも同様ですが、catchに加えてfinallyを付け加えることができ、例外の発生に関わらず実行されます。PHPにはfinallyがありません。Pythonも例外の処理機構を持っています。

try:
  statement
except Exception1:
  statement
except Exception2:
  statement
except: # その他の例外
  statement
else: # 例外なし
  statement
finally: # 必ず実行される
 statement

Pythonではシステム定義の例外は他言語でのthrow動作なしにcatchされます。しかし、raiseを使えば強制的に例外を発生させることもできます。またExeptionクラスから導出することで新しい例外を定義することもできます。

未分類

SQL BASIC

3月 30th, 2011

SQL(MySQL)の基本文法をざっとおさらいしてみます。(推敲要ですがとりあえず公開)
まずはテーブルを定義するCREATE TABLE。

ここでは外部キー制約をひとつつけてみます。そのために、データベースエンジンとして外部キーをサポートするもの(ここではInnoDB)を指定する必要があります。また外部キーにはインデックスが定義されている必要があります。ちなみにPRIMARY KEYを指定した列には自動的にインデックスがつきます。

mysql> CREATE TABLE companies (company varchar(32)
 NOT NULL PRIMARY KEY, address varchar(128)) engine=InnoDB;
mysql> CREATE TABLE citizen (name varchar(32) NOT NULL PRIMARY KEY, age int
 NOT NULL, birthday date NOT NULL, company varchar(32), index (company),
 FOREIGN KEY (company) REFERENCES companies (company)) engine=InnoDB;

データの挿入は、UPDATE。

mysql> INSERT INTO companies (company, address)
VALUES ('nokia', 'helsinki');
mysql> INSERT INTO citizen (name, age, birthday, company)
VALUES ('John', 45, '1965-10-01', 'nokia');
mysql> INSERT INTO citizen (name, age, birthday, company)
 VALUES ('John', 45, '1965-10-01', 'microsoft');

ちなみに、companiesに登録されていないcompanyを使ってcitizenを登録しようとすると、こんな風に怒られます。

mysql> INSERT INTO citizen (name, age, birthday, company)
 VALUES ('Tom', 45, '1965-10-01', 'microsoft');
ERROR 1452 (23000): Cannot add or update a child row:
a foreign key constraint fails
 (`test`.`citizen`, CONSTRAINT `citizen_ibfk_1` FOREIGN KEY (`company`)
 REFERENCES `companies` (`company`))

データの削除はDELETE。

mysql> DELETE FROM citizen WHERE company='nokia';

データの更新はUPDATE。

mysql> UPDATE citizen SET company='google' WHERE company='nokia';

データの照会はSELECT。

mysql> SELECT name,age FROM citizen WHERE birthday < '2000-01-01'
 ORDER BY name DESC;

サブクエリを用いたSELECT。

mysql> SELECT name,age,company FROM citizen WHERE company
 IN (SELECT company FROM companies WHERE address LIKE '%view');

相関サブクエリを用いたSELECT。

mysql> select name,age,company from citizen AS a
WHERE age = (SELECT MAX(age) FROM citizen AS b WHERE a.company = b.company);

表結合
FROMですべての表をリストし、WHEREで指定する方法
INNER JOIN : キーが両方にある行だけ表示される
RIGHT JOIN : キーが右の表にあるものはすべて表示される (左の表だけにあるカラムはNULL)
LEFT JOIN : キーが左の表にあるものはすべて表示される

mysql> select * from citizen inner join companies on citizen.company=companies.company;
+-------+-----+------------+---------+---------+---------------+
| name  | age | birthday   | company | company | address       |
+-------+-----+------------+---------+---------+---------------+
| John  |  45 | 1965-10-01 | google  | google  | mountain view |
| Marco |  30 | 1980-10-01 | nokia   | nokia   | helsinki      |
| Wim   |  10 | 2001-10-01 | nokia   | nokia   | helsinki      |
+-------+-----+------------+---------+---------+---------------+
3 rows in set (0.00 sec)

mysql> select * from citizen, companies where citizen.company=companies.company;
+-------+-----+------------+---------+---------+---------------+
| name  | age | birthday   | company | company | address       |
+-------+-----+------------+---------+---------+---------------+
| John  |  45 | 1965-10-01 | google  | google  | mountain view |
| Marco |  30 | 1980-10-01 | nokia   | nokia   | helsinki      |
| Wim   |  10 | 2001-10-01 | nokia   | nokia   | helsinki      |
+-------+-----+------------+---------+---------+---------------+
3 rows in set (0.00 sec)

mysql> select * from citizen right join companies on citizen.company=companies.company;
+-------+------+------------+---------+-----------+---------------+
| name  | age  | birthday   | company | company   | address       |
+-------+------+------------+---------+-----------+---------------+
| John  |   45 | 1965-10-01 | google  | google    | mountain view |
| NULL  | NULL | NULL       | NULL    | microsoft | seatle        |
| Marco |   30 | 1980-10-01 | nokia   | nokia     | helsinki      |
| Wim   |   10 | 2001-10-01 | nokia   | nokia     | helsinki      |
+-------+------+------------+---------+-----------+---------------+
4 rows in set (0.00 sec)

集約関数を使ったクエリ。

mysql> SELECT name, avg(age), company FROM citizen GROUP BY company;

HAVINGを使ったGROUP結果の絞り込み。

mysql> SELECT name, avg(age), company FROM citizen WHERE company!='microsoft'
GROUP BY company HAVING avg(age) > 30;

トランザクションはこんな感じ。

mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into companies (company, address) values ('intel', 'seatle'); Query OK, 1 row affected (0.00 sec)

mysql> select * from companies;
+-----------+---------------+
| company   | address       |
+-----------+---------------+
| google    | mountain view |
| intel     | seatle        |
| microsoft | seatle        |
| nokia     | helsinki      |
+-----------+---------------+
4 rows in set (0.01 sec)

mysql> rollback;
Query OK, 0 rows affected (0.42 sec)

mysql> select * from companies;
+-----------+---------------+
| company   | address       |
+-----------+---------------+
| google    | mountain view |
| microsoft | seatle        |
| nokia     | helsinki      |
+-----------+---------------+
3 rows in set (0.00 sec)

mysql> insert into companies (company, address) values ('intel', 'seatle');
Query OK, 1 row affected (0.13 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

カーソル


カーソルの宣言
DECLARE [カーソル名] CURSOR FOR [SELECT文];

カーソルを開く
OPEN [カーソル名]

1行ごとにデータを取り出す
FETCH NEXT FROM [カーソル名] INTO [変数リスト];
WHILE (@@FETCH_STATUS = 0)
BEGIN
  -- ここで処理を行う
  FETCH NEXT FROM [カーソル名] INTO [変数リスト];
END;

FETCHで位置付けた行を更新するには、UPDATE文でWHERE CURRENT OF カーソル名を指定する。
FETCHで位置付けた行を削除するには、DELETE文でWHERE CURRENT OF カーソル名を指定する。

カーソルを閉じ、リソースを解放する
CLOSE [カーソル名]
DEALLOCATE [カーソル名]

ビューはSELECTにつけた別名。

mysql> CREATE VIEW names AS SELECT name FROM citizen;
Query OK, 0 rows affected (0.21 sec)

mysql> SELECT * FROM names;
+-------+
| name  |
+-------+
| John  |
| Marco |
| Wim   |
+-------+
3 rows in set (0.00 sec)

ユニオンは複数の問い合わせの結果をけっこう強引に合成出力。

mysql> SELECT name,age FROM citizen UNION select company,address from companies;
+-----------+---------------+
| name      | age           |
+-----------+---------------+
| John      | 45            |
| Marco     | 30            |
| Wim       | 10            |
| google    | mountain view |
| intel     | seatle        |
| microsoft | seatle        |
| nokia     | helsinki      |
+-----------+---------------+
7 rows in set (0.00 sec)

ストアドプロシージャ (stored procedure) とは、データベースに対する一連の処理をまとめた手続きにして、リレーショナルデータベース管理システムに保存(永続化)したもの。永続格納モジュール(Persistent Storage Module)とも呼ばれる。ストアドプロシージャは実際にはデータベースのデータ辞書に格納されている。(from WikiPedia)

programming

GTKによるGUIプログラミング(3)

3月 27th, 2011

GTKはGUIを作るためのライブラリです。もっと言えば、ボタンやメニューなどのGUI部品(ウィジット)のコレクションです。

実際のGTKのプログラムではウィジットをウィンドウにレイアウトして表示します。基本的には水平方向に部品を並べるhboxと垂直方向に部品を並べるvhoxを組み合わせて、レイアウトを構成します。hboxとvboxのなかにウィジットを詰め込んでいけばいいのです。hboxやvboxをhboxやvboxに(入れ子に)配置することもできます。

レイアウトの微調整のためにさまざまなパラメータが用意されていますが、このあたりは必要に応じてAPIリファレンスを参照すれば良いでしょう。

以下は、いくつかのウィジットを配置するサンプルのプログラムです。内容説明はいらないかと思います。

#include <stdio.h>
#include <stdlib.h>
#include "gtk/gtk.h"

static gboolean
delete_event(GtkWidget *widget, GdkEvent  *event, gpointer data)
{
  gtk_main_quit ();
  return FALSE;
}

static void
t_button_signal_handler(GtkWidget *widget, gpointer  data)
{
  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
    gtk_button_set_label((GtkButton*) widget, "ON" );
  else
    gtk_button_set_label((GtkButton*) widget, "OFF" );
}

static void
check_signal_handler(GtkWidget *widget, gpointer  data)
{
  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
    g_print( "check activated\n" );
  else
    g_print( "check deactivated\n" );
}

static void
radio_signal_handler(GtkWidget *widget, gpointer  data)
{
  if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)))
    g_print( "%s activated\n", gtk_button_get_label((GtkButton*)widget));
  else
    g_print( "%s deactivated\n", gtk_button_get_label((GtkButton*)widget));
}

static void
hscale_signal_handler(GtkAdjustment *widget, gpointer  data)
{
  g_print("value changed %d\n", (gint)widget->value );
}

int
main(int argc, char* argv[])
{
  GtkWidget *window;
  GSList *group;
  GtkObject *adj;
  GtkWidget *button, *hscale;
  GtkWidget *vbox, *hbox;
  GtkWidget *separator;
  GtkWidget *label;
  GtkWidget *quitbox;

  gtk_init( &argc, &argv );

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

  g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (delete_event), NULL);
  gtk_container_set_border_width (GTK_CONTAINER (window), 10);

  vbox = gtk_vbox_new (FALSE, 0);

  // label
  label = gtk_label_new ("horizontal box in vertical box");
  gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
  gtk_widget_show (label);

  hbox = gtk_hbox_new (FALSE, 0);

  // toggle button
  button = gtk_toggle_button_new_with_label ("OFF");
  g_signal_connect (G_OBJECT (button), "toggled", G_CALLBACK (t_button_signal_handler), NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  gtk_widget_show (button);

  // check button
  button = gtk_check_button_new_with_label ("check");
  g_signal_connect (G_OBJECT (button), "toggled", G_CALLBACK (check_signal_handler), NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  gtk_widget_show (button);

  // radio button
  button = gtk_radio_button_new_with_label( NULL, "radio 0" );
  g_signal_connect (G_OBJECT (button), "toggled", G_CALLBACK (radio_signal_handler), NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  gtk_widget_show (button);
  group = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button));
  button = gtk_radio_button_new_with_label( group, "radio 1");
  g_signal_connect (G_OBJECT (button), "toggled", G_CALLBACK (radio_signal_handler), NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  gtk_widget_show (button);

  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  separator = gtk_hseparator_new ();
  gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, TRUE, 5);
  gtk_widget_show (separator);

  // hscale
  adj = gtk_adjustment_new (0.0, 0.0, 50.0, 0.1, 0, 0);
  hscale = gtk_hscale_new(GTK_ADJUSTMENT(adj));
  g_signal_connect(G_OBJECT (adj), "value_changed", G_CALLBACK (hscale_signal_handler), NULL );
  gtk_box_pack_start (GTK_BOX (vbox), hscale, FALSE, FALSE, 0);
  gtk_widget_show (hscale);

  separator = gtk_hseparator_new ();
  gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, TRUE, 5);
  gtk_widget_show (separator);

  quitbox = gtk_hbox_new (FALSE, 0);
  button = gtk_button_new_with_label ("Quit");
  g_signal_connect_swapped (G_OBJECT (button), "clicked",
  G_CALLBACK (gtk_main_quit),
  G_OBJECT (window));
  gtk_box_pack_start (GTK_BOX (quitbox), button, TRUE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), quitbox, FALSE, FALSE, 0);

  gtk_container_add (GTK_CONTAINER (window), vbox);

  gtk_widget_show (button);
  gtk_widget_show (quitbox);

  gtk_widget_show (vbox);
  gtk_widget_show (window);

  gtk_main ();

  return 0;
}

programming

GTKによるGUIプログラミング (2)

3月 27th, 2011

GTKはイベント駆動型のプログラムであるという説明をしました。GTKのプログラムではイベントの他にシグナルという似たような機能が使われます。ここで少し整理しておきましょう。

おおまかに言うとイベントはXサーバが発信するものです。シグナルはGTK(GLib)の仕組みが発信しているものです。Xサーバが発信したイベントに基づいて、さらにシグナルが発信されることもあります。イベントの後ろには”button_press_event”のように、”event”がつきます。一方、シグナルには”clicked”のように”event”はつきません。

ちなみにこのシグナルはいわゆるUnixのシグナルとは無関係です。(使われ方は似ていますが)

イベントとシグナルは同様の考え方で使うことができますが、気をつけなければならないのはイベントやシグナルが発生した時にコールされる、ハンドラ(コールバック)関数の登録に若干の違いがあることです。

マウスのボタンがクリックされた時に発生する、button_press_event(イベント)とclicked(シグナル)についてハンドラの登録を見てみましょう。

#include <gtk/gtk.h>

static void
signal_handler(GtkWidget *widget, gpointer  data)
{
  g_print ("Hello World (signal)\n");
}

static gboolean
event_handler(GtkWidget *widget, GdkEventButton *event, gpointer data)
{
  g_print("Hello World (event) %d\n", event->button);
  return FALSE;
}

static void
destroy(GtkWidget *widget, gpointer data)
{
  gtk_main_quit ();
}

int
main(int argc, char *argv[])
{
  GtkWidget *window;
  GtkWidget *button;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy), NULL);
  gtk_container_set_border_width(GTK_CONTAINER(window), 10);
  button = gtk_button_new_with_label("Hello World");

  g_signal_connect(G_OBJECT(button), "clicked",  G_CALLBACK(signal_handler), NULL);
  g_signal_connect(G_OBJECT(button), "button_press_event",  G_CALLBACK(event_handler), NULL);

  gtk_container_add(GTK_CONTAINER(window), button);
  gtk_widget_show(button);

  gtk_widget_show(window);
  gtk_main();

  return 0;
}

ボタンウィジットを作ってwindowに割り当てています。さらにg_signal_connect()でボタンに発生するbutton_press_eventにイベントハンドラーを割り当て、左ボタンが押された時に発生するclickedシグナルにシグナルハンドラーを割り当てています。ハンドラーの定義の仕方がそれぞれ違うことに注意しましょう。

実際にプログラムを走らせてみるとわかることですが、button_press_eventはボタンウィジットの上でどのマウスボタンを押しても発生します。これに対してclickedシグナルが発生するのは左ボタンを押した時だけです。イベントハンドラの返り値をTRUEに変更すると、clickedシグナルは発生しなくなります。Xサーバが発信したbutton_press_eventはまず登録されたイベントハンドラによって処理され、それが左ボタンのクリックだった場合にはGlibがclickedシグナルを発生するものと思われます。ただし、イベントハンドラがTRUEの返り値を持つとclickedシグナルは発生しません。

これはウィンドウのXボタンを押した時に発生するdelete_event(イベント)とdestroy(シグナル)の関係でも同じです。

programming

GTKによるGUIプログラミング (1)

3月 20th, 2011

GTKはGraphical User Interfaceを作るためのライブラリで、特にLinuxベースのオペレーティングシステムでは広く使われています。(Windowsでも使えます。使ったことはないけど)

CのAPIが標準で用意されていますが、さまざまな言語向けのバインディングが用意されています。GTKのプログラミングをCとPythonのバインディングを使ってそれぞれを比較しながら、概観(復習)してみることにします。

GTKのチュートリアルから拝借して、もっとも簡単なGTKのプログラムを作ってみます。

#include <gtk/gtk.h>

static gboolean
delete_event(GtkWidget *widget, GdkEvent  *event, gpointer data)
{
  g_print ("delete event occurred\n");
  return TRUE;
}

int
main(int argc, char *argv[])
{
  GtkWidget *window;

  gtk_init(&argc, &argv);

  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  g_signal_connect(G_OBJECT (window), "delete_event",  G_CALLBACK (delete_event), NULL);
  gtk_widget_show (window);

  gtk_main ();

  return 0;
}

コンパイルは

$ gcc simple01.c `pkg-config –cflags –libs gtk+-2.0`

のようにします。なんにもないウィンドウが開くだけなのですが、このプログラムにはGTKプログラミングの基本となる内容が含まれています。まず、gtk_init()。これはGTKの環境整備をしてくれる関数でGTKを使う時には最初に呼ぶ必要があります。

そしてgtk_main()。gtk_main()が呼ばれるとプログラムはスリープ状態になり、イベント待ちとなります。GTK(に限らずGUIプログラム)はイベント駆動型で、GUIの描画後にスリープ状態になり、発生したイベントに応じてイベントハンドラを呼ぶことで動作するのです。

g_signal_connect()はdeleteイベントに対するイベントハンドラ(delete_event())を登録しています。deleteイベントはWindowのXボタンをクリックすると発生させることができます。

同じことをpythonで書くと、

#!/usr/bin/env python

import pygtk,gtk

class GTK_Main:
    def __init__(self):
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.connect("delete_event", self.delete_event)
        self.window.show()

    def delete_event(self, widget, event, data=None):
        print "delete event occurred"
        return True

    def main(self):
        gtk.main()

base = GTK_Main()
base.main()

動作させるには、

$ python sample01.py

のようにします。クラスを作らなくても書けますが、クラスを作って実装するのがお作法でしょう。gtk_init()に相当する作業は、import pygtk,gtkで実行されます。

programming

Linuxスレッドプログラミング

3月 8th, 2011

Linuxプログラミングが出版された頃にはサポートが万全でなく説明が割愛されていたスレッドですが、その後POSIXスレッドの実装が進み今では幅広く使われています。fork()で子プロセスを起動するのに比べてスレッドはオーバーヘッドが少ないことがあげられます。スレッドがプロセスともっとも違うことはメモリ空間を共有していることでしょう。

各スレッドはそれぞれ別のスタックを持ちますがグローバル変数は共有しています。そのため複数のスレッドでグローバル変数を操作する際にはその同期に留意する必要があります。まずは簡単な例から。pthread_create()でスレッドの生成、pthread_join()でスレッドの終了待ち(ブロック)、pthread_mutex_lock()およびpthread_mutex_unlock()でクリティカルセクション(グローバル変数の変更)の保護を行います。

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

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int  counter = 0;

void *
countup()
{
  pthread_mutex_lock( &mutex );
  counter++;
  printf("Counter value: %d\n",counter);
  pthread_mutex_unlock( &mutex );
}

int
main(void)
{
  pthread_t thread1, thread2;

  if( pthread_create( &thread1, NULL, &countup, NULL) )
    printf("Thread creation failed\n" );
  if( pthread_create( &thread2, NULL, &countup, NULL) )
    printf("Thread creation failed\n" );

  pthread_join( thread1, NULL);
  pthread_join( thread2, NULL);

  exit(0);
}

pthread_cond_init(), pthread_cond_wait(), pthread_key_create(), pthread_setspecific(), pthread_getspecific(), pthread_key_delete()などスレッドには他にも関数が用意されています。いずれ機会があったらまた使ってみます。

conditional mutexについては少し説明を。この関数は通常のmutexと組で使います。

threadA (cc==zzzの時の処理)
pthread_mutex_lock( mutex );

pthread_cond_wait( condmutex, mutex );
mutexをunlockし、condmutexをwait。
condemutexの解除で、mutexをwait。mutexの解除でlockしてreturn。
この動作はrace conditionを生む可能性あり。
threadBのインスタンスが複数あると、
mutexを解除した時点でmutexを取ってccを変更するかも。

pthread_mutex_unlock( mutex );

threadB (cc==zzz以外の処理)
pthread_mutex_lock( mutex );
if(cc==zzz)
  pthread_cond_signal( condmutex );
else
 zzz++;
pthread_mutex_unlock( mutex );

programming

再読Linuxプログラミング(7)

3月 7th, 2011

最後の章となる第13章はソケット通信についてです。ネットワークソケットとUnixソケットについてプログラムを作って復習します。コンパイル時に以下の用に-DUNIXをつけるとUnixソケットを、つけなければネットワークソケットを使用します。

$ cc server.c -DUNIX -o server
$ cc client.c -DUNIX -o client

クライアントの基本動作はsocket()とconnect()。サーバの基本動作はsocekt()、bind()、listen()、accept()です。accept()はそのまま実行するとブロックしますが、select()を使ってサーバソケット(ファイルディスクリプタ)を監視し、接続があった時にだけaccept()するようにするとブロックを回避し、同時にクライアントからの通信を処理することができます。setsockopt()でSO_REUSEADDRを設定しているのは、ポートがTIME_WAITになっていてもbind()を成功させるためです。(TIME_WAITはclient側がクリーンにコネクションをクローズしていれば発生しない)

プログラムの実行はサーバをまず開始し、クライアントを(複数)同時に実行します。

client.c

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/un.h>

#include <unistd.h>
#include <string.h>

int
main( void )
{
  int sockfd;
  int i, len;
#ifdef UNIX
  struct sockaddr_un address;
#else
  struct sockaddr_in address;
#endif
  char buf[1024];

#ifdef UNIX
  printf( "creating a unix socket...\n" );
  sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
  address.sun_family = AF_UNIX;
  strcpy(address.sun_path, "server_socket" );
#else
  printf( "creating a network socket...\n" );
  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  address.sin_family = AF_INET;
  address.sin_addr.s_addr =inet_addr("127.0.0.1");
  address.sin_port = htons( 1234 );
#endif

  len = sizeof(address);
  if(connect(sockfd, (struct sockaddr *)&address,len)==-1){
    perror("connect");
    exit(1);
  }

  for(i=0;i<5;i++){
    strcpy( buf, "hello, world" );
    write(sockfd, buf, strlen(buf)+1);
    memset(buf,'\0',sizeof(buf));
    read(sockfd,buf,1024);
    sleep(1);
    printf( "server returned(%d): %s\n", getpid(), buf );
  }
  close(sockfd);
  exit(0);
}

server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/un.h>
#include <netdb.h>
#include <sys/ioctl.h>

#define bigger(a,b) a>b?a:b

int
main( int argc, char* argv[])
{
  int srv_sock_fd, clnt_sock_fd;
  socklen_t srv_len, clnt_len;
#ifdef UNIX
  struct sockaddr_un srv_address, clnt_address;
#else
  struct sockaddr_in srv_address, clnt_address;
#endif
  fd_set readfds, chkfds;
  char buf[1024], msg[128];;
  int fd, maxfd=2;
  int option=1;

#ifdef UNIX
  printf( "creating a unix socket...\n" );
  unlink( "server_socket" );
  srv_sock_fd = socket(AF_UNIX, SOCK_STREAM, 0);
  srv_address.sun_family = AF_UNIX;
  strcpy(srv_address.sun_path, "server_socket" );
#else
  printf( "creating a network socket...\n" );
  srv_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
  srv_address.sin_family = AF_INET;
  srv_address.sin_addr.s_addr = htonl(INADDR_ANY);
  srv_address.sin_port = htons( 1234 );
#endif

  srv_len = sizeof(srv_address);
  if((setsockopt(srv_sock_fd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option) ))<0)
    perror("setsockopt");
  if((bind(srv_sock_fd, (struct sockaddr*)&srv_address, srv_len))<0)
    perror("bind");
  if((listen(srv_sock_fd, 64))<0)
    perror("listen");

  maxfd = bigger(maxfd, srv_sock_fd);

  FD_ZERO(&readfds);
  FD_SET(srv_sock_fd, &readfds);

  while(1){
    chkfds = readfds;

    if(select(maxfd+1, &chkfds, (fd_set *)0, (fd_set *)0, NULL)<1){
      perror("select");
      exit(1);
    }

    for(fd=0;fd<=maxfd;fd++){
      if(FD_ISSET(fd,&chkfds)){
        if(fd==srv_sock_fd){
          clnt_len = sizeof(clnt_address);
          clnt_sock_fd = accept(srv_sock_fd, (struct sockaddr *)&clnt_address,  &clnt_len);
          FD_SET(clnt_sock_fd, &readfds);
          maxfd = bigger(maxfd, clnt_sock_fd);
          printf("Client connected.\n");
          continue;
        }
        else{
          int nread;

          ioctl(fd, FIONREAD, &nread);
          if(nread==0){
            FD_CLR(fd, &readfds);
            close(fd);
            printf("Client closed.\n");
          }
          else{
            memset(buf,'\0',sizeof(buf));
            read(fd,buf,nread);
            fprintf(stderr, "FD %d, %s\n", fd, buf );
            strcpy( buf, "HELLO, WORLD" );
            write(fd,buf,strlen(buf)+1 );
          }
        }
      }
    }
  }
}

programming

再読Linuxプログラミング(6)

3月 6th, 2011

IPCの最後はメッセージキューです。メッセージキューはパイプと似ていますが、いくつか違いがあります。

  • メッセージには送信側が指定した優先度が付いている
  • メッセージの書き込みに受信側がメッセージの到着を待っていることが要求されない
  • メッセージキューの名前の削除はいつでも可能だが、キューの削除は参照カウントがゼロになるまで行われない。(mq_unlink()によって削除され、mq_close()されて参照カウントがゼロに達するまで、キューはその内容を維持して存在を続ける)
  • 空のメッセージキューにメッセージが到着した時に、シグナルかスレッドコールの非同期アクションを起動できる

以下のサンプルはメッセージキューにメッセージが到着した時にシグナルハンドラを起動するものです。実は何か所がハマりました。最後まで気がつかなかったミスはシグナルがあがるのは「空」のメッセージキューにメッセージが到着したときだけと言うこと。最初のメッセージの到着までの時間を稼ぐために、送信側にsleep()を入れています。送信側のプログラムを起動したあとに受信側を同時に起動します。

#include <mqueue.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int
main()
{
  mqd_t mqd;
  void *ptr;
  unsigned int prio;
  char *msg;
  int i;

  msg = "hello world!";
  prio = 10;

  mq_unlink("/mq");
  mqd = mq_open("/mq", O_RDWR|O_CREAT, S_IRWXU|S_IRWXO, NULL );

  ptr = calloc(strlen(msg) + 1,sizeof(char));
  strcpy( ptr, msg );
  for( i=0; i<5; i++ ){
    sleep(3);
    printf( "write(%d) %ld bytes\n", strlen(ptr) );
    if( mq_send(mqd,ptr,strlen(ptr),prio) != 0 )
      perror( "mq_send" );
  }
  mq_close(mqd);
  mq_unlink("/mq");

  exit( 0 );
}

受信側のプログラムにはいくつか注意点があります。まずsignal()の代わりに、sigaction()を使っています。(互換性の問題からそれが推奨されているようです)それとキューにたまったデータを全部読み取ってリターンするためにノンブロッキング読み込みをしています。

加えてsigprocmask()とsigsuspend()を使って取りこぼしなくグローバル変数mqflagのアップデートを保証しています。sigprocmask()でSIGUSR1をブロックしたあとに、sigsuspend()でシグナルの受け付けを開始してwaitします(アトミック動作)。sigsuspend()はsignalを受け付けるとシグナルハンドラを実行しシグナルのマスクを以前の状態に戻してリターンします。(つまりSIGUSR1は再びブロックされる)このことでSIGUSR1のイベントハンドラを使った処理を確実に行うことができます。(A)の位置にsleep()を入れて試すと、sigsuspend()がシグナルのblock中に起きたシグナルをきちんと処理できることが確認できます。

ちなみに互換性を維持しながら安全に行うことが可能なシグナルハンドラ内での動作は、sig_atomic_tの変更のみとか。今回はこれにしたがって作ってみました。実際にプログラムを作るときは確認事項が多そうです。

- 参考(引用) -
http://book.chinaunix.net/special/ebook/addisonWesley/APUE2/0201433079/ch10lev1sec16.html

We have seen how we can change the signal mask for a process to block and unblock selected signals. We can use this technique to protect critical regions of code that we don’t want interrupted by a signal. What if we want to unblock a signal and then pause, waiting for the previously blocked signal to occur? Assuming that the signal is SIGINT, the incorrect way to do this is

      sigset_t     newmask, oldmask;

      sigemptyset(&newmask);
      sigaddset(&newmask, SIGINT);

      /* block SIGINT and save current signal mask */
      if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
          err_sys("SIG_BLOCK error");

      /* critical region of code */

      /* reset signal mask, which unblocks SIGINT */
      if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
          err_sys("SIG_SETMASK error");

      /* window is open */
      pause();  /* wait for signal to occur */

      /* continue processing */

If the signal is sent to the process while it is blocked, the signal delivery will be deferred until the signal is unblocked. To the application, this can look as if the signal occurs between the unblocking and the pause (depending on how the kernel implements signals). If this happens, or if the signal does occur between the unblocking and the pause, we have a problem. Any occurrence of the signal in this window of time is lost in the sense that we might not see the signal again, in which case the pause will block indefinitely. This is another problem with the earlier unreliable signals.

To correct this problem, we need a way to both reset the signal mask and put the process to sleep in a single atomic operation. This feature is provided by the sigsuspend function.

- 引用おわり -

#include <mqueue.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/stat.h>

volatile sig_atomic_t mqflag;

static void
sig_usr1(int signo)
{
  mqflag = 1;
  return;
}

int
main( void )
{
  mqd_t mqd;
  void *buff;
  ssize_t n;
  sigset_t zeromask, newmask, oldmask;
  struct mq_attr attr;
  struct sigevent sigev;
  struct sigaction sigact;
  int i;

  mqd = mq_open("/mq", O_RDONLY|O_NONBLOCK );

  mq_getattr(mqd, &attr);
  buff = malloc(attr.mq_msgsize);

  sigemptyset(&zeromask);
  sigemptyset(&newmask);
  sigemptyset(&oldmask);
  sigaddset(&newmask, SIGUSR1);

  // signal(SIGUSR1, sig_usr1 );
  memset(&sigact, 0, sizeof(sigact));
  sigact.sa_handler = sig_usr1;
  sigact.sa_flags = 0;  

  sigaction(SIGUSR1, &sigact, NULL);  

  sigev.sigev_notify = SIGEV_SIGNAL;
  sigev.sigev_signo = SIGUSR1;
  mq_notify(mqd, &sigev);
  sigprocmask(SIG_BLOCK, &newmask, &oldmask);

  for (i=0;i<5;i++) {
    while (mqflag == 0)
      sigsuspend(&zeromask);

    mqflag = 0;
    mq_notify(mqd, &sigev);
    while ( (n = mq_receive(mqd, buff, attr.mq_msgsize, NULL)) >= 0)
      printf("read(%d) %ld bytes : %s\n", i, (long)n, buff );
    if (errno != EAGAIN)
    perror("mq_receive");
  // sleep( 10 );  // (A)
  }

  mq_close(mqd);
  exit( 0 );
}

programming

再読Linuxプログラミング(5)

3月 6th, 2011

続いては共有メモリです。共有メモリを用いたプロセス間通信はオーバーヘッドが少なく高速なことが特徴です。

POSIX IPCの名前付き共有メモリを作成し、書き込みと読み出しを行います。作成した共有メモリにftruncate()でサイズを指定し、mmap()して読み書きを行います。この例では同一プロセスから読み書きを行っていますが、別のプロセスから行う場合も同等です。ただし読み書きのタイミングについてはセマフォーなどを用いて管理する必要があります。

#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

#define SHMSIZE 128

void
write_shm()
{
  int i, fd;
  unsigned char *ptr;

  fd = shm_open("/shm", O_RDWR, S_IRWXU|S_IRWXO );
  ftruncate( fd, SHMSIZE );
  ptr = mmap(NULL, SHMSIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

  for (i = 0; i < SHMSIZE; i++)
    *ptr++ = i % 16;

  close(fd);
}

void
read_shm()
{
  int i, fd;
  unsigned char c, *ptr;

  fd = shm_open("/shm", O_RDONLY, S_IRWXU|S_IRWXO );
  ptr = mmap(NULL, SHMSIZE, PROT_READ, MAP_SHARED, fd, 0);

  for (i = 0; i < SHMSIZE; i++)
    printf("shm[%d] = %d\n", i, *(ptr++) );

  close(fd);
}

int
main(void)
{
  int fd;

  fd = shm_open("/shm", O_RDWR|O_CREAT, S_IRWXU|S_IRWXO );
  close(fd);

  write_shm();
  read_shm();
  shm_unlink( "shm" );

  return 0;
}

共有メモリに対するmmap()の使い方は通常のファイルに対する場合とほとんど同じです。せっかくなので上述のセマフォを利用したメモリアクセス管理を通常ファイルに対するmmap()を使って実現してみます。

#include <semaphore.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/mman.h>

int
main(void)
{
  int fd, i, zero = 0;
  int *ptr;
  sem_t *sem;

  fd = open("/tmp/mmap", O_RDWR|O_CREAT, S_IRWXU|S_IRWXO);
  write(fd, &zero, sizeof(int));
  ptr = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

  sem = sem_open("/sem",  O_CREAT|O_RDWR, S_IRWXU|S_IRWXO, 1 );
  /*
    The semaphore name is removed immediately.
    The semaphore  is  destroyed  once all other processes
    that have the semaphore open close it.
  */
  sem_unlink( "/sem" );

  close(fd);
  setbuf(stdout, NULL); /* no buffering */

  if (fork() == 0) {
    for (i = 0; i < 10; i++){
      sem_wait(sem);
      (*ptr)++;
      printf("child: %d\n", *ptr);
      sem_post(sem);
    }
  }
  else{
    for (i = 0; i < 10; i++){
      sem_wait(sem);
      (*ptr)++;
      printf("parent: %d\n", *ptr);
      sem_post(sem);
    }
  }
  exit(0);
}

programming

再読Linuxプログラミング(4)

3月 6th, 2011

第12章はセマフォ、メッセージキュー、共有メモリについて。ここで説明されているのはSystem V IPCと呼ばれる実装についての解説で、後に同等の機能を持つものがPOSIX IPCとして実装されています。

System V IPCを使って作られたアプリケーションは数多く存在し、今後も無くなることはないと思われますが、POSIX IPCはSystem V IPCを研究して作られたものですから、それなりのアドバンテージがあります。せっかくなのでPOSIX IPCについてサンプルコードを作ってみることにします。

System V IPCには共有オブジェクトを操作する一連のコマンドとして、ipcmk、ipcrm、ipcsが用意されています。一方、POSIXのIPCの名前付きオブジェクトについては、ファイルシステム上のオブジェクトとして実装されるために特別のコマンドはなく、rmやlsで操作できます。ファイルの場所はシステム依存のようですが、Fedora14では/dev/shmです。

今回はセマフォについて復習してみます。セマフォは数の限られているリソースの競合を調停する仕組みで、リソースの数を保持するカウンタを増減し、ゼロならばリソース要求したプロセスを待たせる仕組みです。カウンタを減らす操作をwaitあるいはP操作といい、カウンタを増やす操作をpostあるいはV操作と呼びます。カウンタがゼロの時にwait操作を試みたプロセスはその値が1になるまで待たなくてはいけません。

一見簡単そうに思えるカウンター操作ですが、セマフォのカウンタ操作はアトミックに行われることが保証されなくてはいけません。カウンタの中身を確認し、内容を変更して、書き戻すという一連の動作を排他的に行う必要があるのです。逆に言えば、セマフォはそれをやってくれるシステムコールだと言うこともできます。

セマフォには名前なしセマフォと名前付きセマフォがありますが、ここでは名前付きのセマフォを使います。二つのプロセスが単にP動作とV動作を行うサンプルプログラムです。(コンパイルには-lpthreadオプションが必要です)

#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>

int
main( void )
{
  sem_t *semid;
  int i;

/*  sem_unlink( "/sem" ); // in case it exists */
  semid = sem_open( "/sem", O_CREAT|O_RDWR, S_IRWXU|S_IRWXO, 1 );
  for(i=0;i<5;i++){
    printf( "wait (%d)...\n", i );
    sem_wait( semid );
    printf( "granted\n" );
  }
  /*  sem_close( semid ); */
  sem_unlink( "/sem" );
  return( 0 );
}
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>

int
main( void )
{
  sem_t *semid;
  int i;

  semid = sem_open( "/sem", O_RDWR );
  for(i=0;i<5;i++){
    sleep( 1 );
    printf( "post (%d)\n", i );
    sem_post( semid );
  }

  return( 0 );
}
$ ./sem_wait & sleep 1; ./sem_post
wait (0)...
granted
wait (1)...
post (0)
granted
wait (2)...
post (1)
granted
wait (3)...
post (2)
granted
wait (4)...
post (3)
granted
post (4)

programming