PHPのis_a()が非推奨エラーメッセージ吐く件

PHPXML_RPCを使ってRSSフィードの更新ping打つ処理を書いたりすると、XML_RPCが内部で多用しているis_a()メソッドが非推奨だとエラーメッセージをゴリゴリ吐きます。

こういった問題にはerror_reporting()のメッセージ出力を一時的に無効にして対応するようです。

<?php
require_once('XML/RPC.php');

class HogeHogeHttpUtil {
	
	function rss_ping($title, $top_url) {

		// 一時的にStrict Standards エラーメッセージ抑止
		$er = error_reporting();
		if( ($er & E_STRICT) == E_STRICT ) {
			error_reporting($er ^ E_STRICT);
		}

		// 以降、Strict Standards エラーの出る処理
		$servers = array(
			array( 'server' => 'http://ping.bloggers.jp',
				'path'   => '/rpc/', ),
			array( 'server' => 'http://rpc.reader.livedoor.com',
				'path'   => '/ping', ),
			array( 'server' => 'http://blogsearch.google.com',
				'path'   => '/ping/RPC2', ),
		);
		$param = array(
			new XML_RPC_Value($title,'string'),
			new XML_RPC_Value($top_url,'string')
		);
		$msg = new XML_RPC_Message('weblogUpdates.ping',$param);
		foreach( $servers as $k => $v ) {
			$client = new XML_RPC_Client($v['path'],$v['server'],80);
			if( ! $client->send($msg) ) {
				print 'XML RPC send ERROR : '.$v['server']."\n";
			}
		}

		// 処理終わって、エラーメッセージ抑止モードからの戻し
		error_reporting($er);
	}

}
?>

確認した環境は以下です。*1

%php -version
PHP 5.2.9 (cli) (built: May  8 2009 17:43:26)
Copyright (c) 1997-2009 The PHP Group
Zend Engine v2.2.0, Copyright (c) 1998-2009 Zend Technologies
%pear list | grep XML_RPC
XML_RPC              1.5.1   stable

*1:さくらサーバ・スタンダードプラン

checkinstall インストール手順

tar ball解凍して入れる手順です。ディストリビューションCentOSですが、yumよりこっちの方が早いのでいつもこうしている・・

#!/bin/sh
wget http://asic-linux.com.mx/%7Eizto/checkinstall/files/slackware/checkinstall-1.6.1-i386-1.tgz
tar xvfzp checkinstall-1.6.1-i386-1.tgz
rm -f checkinstall-1.6.1-i386-1.tgz
cp -pr ./usr/* /usr/.
rm -rf ./usr

マルチスレッドにおけるMapオブジェクト

身近でjava.util.HashMapのインスタンスがスレッドセーフでない事に起因したトラブルの話があったので、実際にサンプルを作って試してみました。

まず各スレッドで動作するRunnableなworkerクラス。

import java.util.Map;

public class Worker implements Runnable {

	private Map<String, String> targetMap;

	public void setMap(Map<String, String> targetMap) {
		this.targetMap = targetMap;
	}

	public void run() {
		String threadId = String.valueOf(Thread.currentThread().getId());
		for (int i = 0; i < 100; i++) {
			try {
				targetMap.put(threadId, threadId);
				Thread.sleep(3);
				targetMap.remove(threadId);
			} catch (InterruptedException ignore) {
			}
		}
	}
}

次々にスレッドを起動するmainメソッドのクラス。

import java.util.HashMap;
import java.util.Map;

public class SampleMain {

	public static void main(String[] args) throws InterruptedException {
		Map<String, String> targetMap = new HashMap<String, String>();
		for (int i = 0; i < 100; i++) {
			Worker worker = new Worker();
			worker.setMap(targetMap);
			Thread t = new Thread(worker);
			t.start();
			Thread.sleep(8);
		}
		Thread.sleep(10000);
		System.out.println(targetMap.size());
	}

}

メインから100スレッドをスタートさせ、各スレッドの中では、共有するMapオブジェクトに対して自分のスレッドIDの文字列をキーにした要素をputしたりremoveしたりを繰り返しているだけのサンプルです。

論理的には最後の出力は必ず「0」になるはずです。実際、これを1CPUの環境で実行すると必ず結果は「0」となります。

しかし、2CPUの環境で数回実行してみると、結果は以下の通りバラバラ。

スレッド数や各スレッドの中の処理回数を増やせば、この値はより大きくなります。

0
0
-4
2
2
1
-3
-9
0
-3


では、マルチスレッド環境でMapオブジェクトを共有しなければならない場合にどうするかというと、以下の実装が考えられます。


まずは触るときに必ずsynchronizeするやり方ですが、これは破綻が目に見えているので難しいと思います。

// Worker.java
synchronized (targetMap) {
	targetMap.put(threadId, threadId);
}
Thread.sleep(3);
synchronized (targetMap) {
	targetMap.remove(threadId);
}


Javaのバージョンを問わない対策としては、Collections.synchronizedMapを使う方法があります。このメソッドから返されたオブジェクトはアクセスが同期化されます。
http://java.sun.com/j2se/1.3/ja/docs/ja/api/java/util/Collections.html#synchronizedMap(java.util.Map)

// SampleMain.java
Map<String, String> targetMap = Collections.synchronizedMap(new HashMap<String, String>());


Java5以降であれば、HashMapクラスでインスタンス化するのはやめてConcurrentHashMapクラスを使うことができます。
http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/java/util/concurrent/ConcurrentHashMap.html

// SampleMain.java
Map<String, String> targetMap = new ConcurrentHashMap<String, String>();


ConcurrentHashMapクラスがどうやって高速な同期を実現しているかについての説明が以下のURLにありました。
http://www.itarchitect.jp/technology_and_programming/-/24161.html

このエントリの主題からは逸れますが、特にvolatile変数に関する説明のくだりはとても参考になりました。

JSR 133以前のメモリ・モデル、すなわちJava言語仕様第2版の17章に規定されているメモリ・モデルでは、volatile変数に関しては順序関係が必ず反映されるが、非volatile変数に関しては反映されるとは限らなかった。上の例で言えば、スレッドT1がvolatile変数に対する書き込み処理を行う前に実行したことでも、それが非volatile変数に対して行われた更新操作であった場合は、必ずしもスレッドT2に反映されるとは限らない。

  JSR 133ではより制限を強め、作業メモリとメイン・メモリの同期に関して、volatile変数とロックが同じように動作するように定めている。すなわち、 volatile変数に書き込むと作業メモリの内容をメイン・メモリに書き出し、volatile変数を読み出すと作業メモリの内容を無効にする。したがって、JSR 133により、ロックを利用しなくとも、共通のvolatile変数を持っていれば、2つのスレッド間に明確な順序づけを行うことが可能になった。これにより、volatile変数に書き込みを行うスレッドで、その書き込み処理以前に行ったすべての処理(非volatile変数を含む)が、 volatile変数に読み出しを行うスレッドに反映されることになる。

 volatile変数による順序づけは、ロックによる順序づけよりもコストがかからない。クラスConcurrentHashMapでは、これを利用して、以下のような処理を行っている。

○書き込みメソッドでは、すべての処理の終了後にvolatile変数に対する書き込み処理を行う
○読み出しメソッドでは、いちばん先にvolatile変数の読み出し処理を行う

http://www.itarchitect.jp/technology_and_programming/-/24161-4.html

BioRubyのseqがNoMethodError

私は全く門外漢ですが、友人がバイオインフォマティクスの研究などしていて「biorubyを使ってみたいけどよくわからない」という話になったので、入手してみて試してみようとしたところ

http://bioruby.org/archive/doc/Japanese/tutorial.html

上記の日本語版チュートリアルにある最初の例を実行してみるといきなりNoMethodError。

bioruby> dna = seq("atgcatgcaaaa")
NoMethodError: undefined method `seq' for main:Object
        from (irb):1
bioruby>

調べてみると以下のMLにある通り、メソッド名の仕様変更にドキュメントが追随できていないとのことで、seq、ent、objは全て頭にgetをつけるようにすれば問題ないようです。

http://lists.open-bio.org/pipermail/bioruby-ja/2009-February/000155.html

bioruby> dna = getseq("atgcatgcaaaa")
  ==> #<Bio::Sequence:0xb7b8f224 @moltype=Bio::Sequence::NA, @seq="atgcatgcaaaa">
bioruby>

全く分からない分野なので、内容を説明してもらいながら実行してみたりしてなかなか面白かったです。

bioruby> dna = getseq("atgc" * 10).randomize
  ==> "acatgagctgaggctacacaggttagttaccccatcggtt"
bioruby> doublehelix dna
     at
    c--g
   a---t
  t----a
 g----c
a---t
g--c
 cg
 at
c--g
t---a
 c----g
  c----g
   g---c
    a--t
     ta
     cg
    a--t
   c---g
  a----t
 g----c
g---c
t--a
 ta
 ta
c--g
a---t
 a----t
  t----a
   g---c
    g--c
     gc
  ==> "catcggtt"
bioruby>

UTF-8における全角マイナスと全角チルダの問題(=いわゆる「波ダッシュ問題」)

以下の通り、同じ文字を表現しているにも関わらず、文字コードが異なるケースが発生します。

[webmaster@localhost work]$ nkf -w sjis.txt > utf8.nkf.txt
[webmaster@localhost work]$ od -tx1 utf8.nkf.txt
0000000 e2 88 92 e3 80 9c
0000006
[webmaster@localhost work]$ vi utf8.term.txt
[webmaster@localhost work]$ cat utf8.term.txt
−〜
[webmaster@localhost work]$
[webmaster@localhost work]$
[webmaster@localhost work]$ od -tx1 utf8.term.txt
0000000 ef bc 8d ef bd 9e 0a
0000007
[webmaster@localhost work]$
[webmaster@localhost zenkaku_minus]$ diff utf8.term.txt utf8.nkf.txt
1c1
< −〜
---
> "0
\ No newline at end of file
[webmaster@localhost zenkaku_minus]$

上記の貼り付けはこの文章をWindows PC上で書いているのでnkf -wで変換したものが文字化けしていますが、実際は以下のSSの通り、見えています。どちらのコードも文字が割り当てられている為、文字化けせずみえてしまうあたりが微妙なところ。



「−(全角マイナス)」を例にとると、Linux上でnfk -wで変換したものはUnicodeの仕様通り「e2 88 92」で構成されていますが、Windows PCからのPuttyなどのターミナル経由での入力はLinuxコマンドライン上で入力したものであっても、Windowsの独自仕様で「ef bc 8d」による表現になります。

LinuxMacWindowsなど複数のOSのクライアントからの入力がある場合は、どれか(≒Windows)を基準にして他をはじくか、統一する為に変換するなどの対応が必要で、他にどれだけこういう文字があるのかという体系立てた調査結果を探しましたが、当方ではWeb上で見つけられませんでした。*1


追記(6/19):

コメントをいただいた事をきっかけに、Unicode仕様とWindows実装(JIS X 208等への準拠)との間での不整合についていくらか経緯・事情を理解しました。


私が書いた内容は「波ダッシュ問題」としてさんざん既知の問題で、Wikipediaをみると以下のように項目を設けて解説があります。

http://ja.wikipedia.org/wiki/Unicode#.E6.97.A5.E6.9C.AC.E8.AA.9E.E7.92.B0.E5.A2.83.E3.81.A7.E3.81.AEUnicode.E3.81.AE.E8.AB.B8.E5.95.8F.E9.A1.8C
http://ja.wikipedia.org/wiki/%E6%B3%A2%E3%83%80%E3%83%83%E3%82%B7%E3%83%A5#Unicode.E3.81.AB.E9.96.A2.E9.80.A3.E3.81.99.E3.82.8B.E5.95.8F.E9.A1.8C


おそらく有名なエピソードかと思われますが、上記の「波ダッシュ」項の以下の解説が興味深かったです。「興味深かった」とか悠長にいえる他人事の話ではないのですが、「仕様を決めるのは(当たり前ですが)人間なんだな」という感じがしたというか。

このような間違いが発生した理由は、Unicodeの例示字形を検討するグループにいたメンバーの日本語に対する知識が不十分だったために、縦書きの例示字形「」を90度回転すればいいと誤って判断してしまったためである。


以下のウェブサイトではJcode.pmにおけるこの問題への対応について解説がありました。

http://www.fiberbit.net/user/hobbit-t/html/jcode.html


網羅的に対処するとなると、Unicode Consortiumが提供しているJIS規格とUnicodeのコード変換表を元に対応という事になるようです。

ftp://ftp.unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/JIS0208.TXT


追記(6/20):

このエントリで挙げた例ではShift_JISのファイルをnkf -wでUTF-8に変換して問題が発生しています。

コメントにてid:nurse様に教えていただいたのですが、これについては以下のようにオプションを明示する事でWindows互換での変換となるので、実行する環境を問わずこのように統一する方がよいようです。

[webmaster@localhost work]$ nkf --ic=CP932 --oc=UTF-8 sjis.txt > utf8.nkf.txt
[webmaster@localhost work]$ od -tx1 utf8.nkf.txt
0000000 ef bc 8d ef bd 9e
0000006
[webmaster@localhost work]$

nkf --helpをみると以下のように説明があります。

Long name options
 --ic=<input codeset>  --oc=<output codeset>
                   Specify the input or output codeset

*1:一般的に入力の可能性が高いのはこの二つのようですが

IEでテキストボックス内でエスケープキー連続押下するとform.reset発行

ハタさんのブログ : IE は Form内で [ESC] 2回でform.reset
http://blog.xole.net/article.php?id=489

上記に全て書いてありますが、IEの独自仕様ですね。

IE6、7、8の各バージョンで確認しましたが、全て同様にリセットが呼ばれました。