昨日はみなさんおつかれさまでした。
私は二日目の Community Panel Discussion で日本Scalaユーザーズグループ代表*1ということで登壇しました。ありがとうございました!
*1:本当の代表は水島代表(仮)です
commons-daemonを使ったら、NettyServerをちゃんとLinuxのデーモン化できた。
https://gist.github.com/2156447
今回もJavaのスレッドに関連する記事です。前回はメモリモデルに関連す話題でしたが、今回J2SE 5.0から使えるConcurrency Utilitiesについて解説してみました。店頭で見つけたら手にとって見てくださし。
そして次回の締め切りが、、迫り来る!
12月23日発売のWEB+DB PRESS Vol.66で連載4回目の記事を書きました。
タイトルは「再考するJava【第4回】並行処理におけるスレッドセーフの心得」です。
マルチスレッドは難しく広範囲な話題なので書くのが非常に難しいのですが、今回はスレッドセーフに絞って書いてみました。*1スレッドというとスレッドの起動や停止、スレッドプールとかに目がいきがちなのですが、それよりもスレッドセーフを実現するために何を気をつけるべきか、そういう根本的な概念に目を向けることも重要だと思いこの記事を書きました。
この記事を糸口にしていただき、並行処理に関する良書や文献などを読む機会が増えるとよいと思います。
ということで、よろしくお願いします。
http://atnd.org/events/22899
@yusukeyに絡まれたので、しょうがなく我が家秘伝のJIRA黒魔術をざっくり公開します。。。
JIRAはワークフローを柔軟に扱えることが一つの魅力です。
たとえば、ストーリチケットを作成するときに、ストーリポイントは必須だよね。ストーリチケットだからね。でも、ストーリポイントを必須項目にするとストーリポイントが決まっていない状況では起票できないし、必須でなくしてしまうと未入力のままの可能性がある。
思いついたときに起票して、ストーリポイントも入力漏れを防ぐにはどうしたらよいか。
簡単です。チケットが開始状態になるまでは未入力だけど、開始するときには必須ってワークフローを書けばいいのです。
ワークフローを超ざっくり説明すると、、、ワークフローには、オープン、開始、処理中、解決、クローズとかの"状態"があります。それと各状態から状態に移動するための"遷移"と概念があります。この時点でだいぶ難しいw 絵にしてみた。こんな感じ。
その"遷移"を可能にする"条件"や遷移時に"検証"を行うことができます。"条件"はその名の通りその条件が有効でない時は、その先の状態に遷移できません。"検証"はその先の状態に遷移する際に呼び出され、検証に失敗した場合は警告表示と次の状態に遷移しません。
今回は、チケット作成直後の状態(オープン)から開始状態に遷移するStart Progressという"遷移"の"検証"に独自の条件を追加するのがよいです。
このような拡張を追加する際にはJIRAのプラグインを自作するというのもあるのですが、今回はもっと手軽にワークフローを制御できるScript Runnerを紹介します。
いきなりコードから説明。
ストーリチケットはIDが7番です。*1
ストーリポイントってカスタムフィールドなんでそれもカスタムフィールドの編集画面からパラメータを取得して、フィールドを取得します。フィールドのオブジェクトまでとれれば値の取得は簡単です。このルールの場合は、null以外と0以上を両方をチェックしています*2。"条件"も"検証"も戻り値としてはbooleanを返すだけでよいです。
import com.atlassian.jira.issue.CustomFieldManager import com.atlassian.jira.ComponentManager import com.atlassian.jira.issue.fields.CustomField CustomFieldManager customFieldManager = ComponentManager.getInstance().getCustomFieldManager() if (issue.getIssueTypeObject().getId() == "7"){ CustomField customField = customFieldManager.getCustomFieldObject(10003) BigDecimal sp = customField.getValue(issue) return (sp != null && sp.floatValue() > 0) } true
また、ストーリチケット以外でかつサブタスクがないチケットは、開始する前に初期見積を必ず入れておきたい場合は以下のようなバリデータを書きます。意外と簡単に掛けてしまいます。これで初期見積も入力漏れのまま開始されることはないわけです。
if (issue.getIssueTypeObject().getId() != "7" && issue.getSubTaskObjects().size() == 0){ Long orgEst = issue.getOriginalEstimate() return (orgEst != null && orgEst.intValue() > 0) } true
どうやってデバッグするの?Eclipseとか用意するの?って疑問あると思うんですが、以下のような画面を辿っていくとConditionTesterという画面があるのでそこでチケットIDとGroovyスクリプトと入力してテストすることができます。
試しにストーリポイントが未入力なチケットを開始状態にしてみる。
なんということでしょう。ストーリポイントが未入力または0の場合はバリデーションエラーが起きました。思った通りのバリデータを書けるではありませんか。これでいろいろ幸せでになります はぁと!
インストールしたデフォルトだと次のような状態になっています。
最初のサブタスクの方は、In ProgressなどからResolvedに遷移するResolve Issueの条件に追加すれば、サブタスクが解決しないと親タスクも解決できないにようになります。これは簡単ですね。
二つ目のブロックされる課題リンクがあるはちょっと複雑です。
JIRA Workflow Toolboxを手動でインストール。atlassian-jira/WEB-INF/lib にこのプラグインのJARファイルをインストールするタイプのものです。
これもResolve Issueの条件に追加すればよいです。
条件はこんな感じ。ブロックする課題が解決もしくはクローズになっていないとチケットが解決にできないように設定します。
明日は@tsuyoshikawaです!
Scala Advent Calendar jp 2011 6日目 いきます。
STMの話にしようと思ったのですが、いろいろまだ調査中なんでまた後日ということで、今回はパラレルコレクションでいきます。すでにあちこちのブログで扱っているネタなので目新しさはないですが...
パラレルコレクションは2.9から使える新機能です。
早速 使い方。通常のコレクションの要素を2倍する処理は次のように記述します。
List(1,2,3).map(_ * 2)
一方、パラレルコレクションではparメソッドを使います。
List(1,2,3).par.map(_ * 2)
scala.collection.immutable.List#parはParSeq[A]型の戻り値を返します。ParSeq#mapを呼び出すだけでmapを並行に処理できるわけです*1。本来並行処理を実装する場合は、スレッドの起動や待機、スレッドプールとタスクの管理など複雑な制御が伴いますが、パラレルコレクションの場合はparメソッドを呼ぶだけ並行処理を記述できます。
実際のテストプログラム。Benchのmainメソッドがエントリポイントです。引数に応じて通常のコレクションでの処理と、パラレルコレクションでの処理を呼び分けます。
package parallel import scala.collection.immutable.NumericRange // プログラム本体 object Bench { // nまでの階乗を計算するメソッド def fac(n: BigInt) = NumericRange(BigInt(1), n, BigInt(1)). foldLeft(BigInt(1)) { (cur, next) => cur * next } def main(args: Array[String]): Unit = { import parallel.BenchUtil._ args match { case Array("N") => bench(50, "normal") { (1 to 2000).map { x => fac(x) } } case Array("P") => bench(50, "parallel") { (1 to 2000).par.map { x => fac(x) } } } } }
BenchUtil#benchメソッドは計測した結果をソートして、前後20%ずつ削除した値だけを利用して平均値や標準偏差、最大最小値を計算。
package parallel import scala.compat.Platform // ベンチマーク用ユーティリティクラス object BenchUtil { private def avg(xs: List[BigDecimal]): BigDecimal = xs.sum / xs.size private def std(xs: List[BigDecimal]): BigDecimal = { val a = avg(xs) Math.sqrt((xs.foldLeft(BigDecimal(0))((s, c) => s + (c - a) * (c + a)) / xs.size).toDouble) } private def median(xs: List[BigDecimal]) = xs.toSet.toList.sortWith(_ < _) match { case n :: Nil => n case xs if xs.size % 2 != 0 => xs(xs.size / 2) case xs if xs.size % 2 == 0 => { val a = xs(xs.size / 2 - 1) val b = xs(xs.size / 2) (a + b) / 2 } case _ => throw new RuntimeException } private def mode(xs: List[BigDecimal]): BigDecimal = xs.foldLeft(Map[BigDecimal, Int]().withDefaultValue(0)) { (map, key) => map + (key -> (map(key) + 1)) } maxBy (_._2) _1 def bench(n: Int, msg:String)(f: => Unit) { val times = for (i <- List.range(1, n + 1, 1)) yield { Platform.collectGarbage val start = System.nanoTime f val stop = System.nanoTime BigDecimal(stop - start) / 1000 / 1000 } val truncate = n / 5 val result = times.sortWith(_ < _).view(truncate, n - truncate).toList if (result.size > 0) { println("%s, threadId = %d, n = %d, avg = %11.2f, std = %11.2f, median = %11.2f, mode = %11.2f, min = %11.2f, max = %11.2f". format(msg, Thread.currentThread.getId, result.size, avg(result), std(result), median(result), mode(result), result.min, result.max)) } } }
結果は次のとおり。私の環境ではパラレルコレクションを使った処理の方が3倍ぐらい高速になりました。もっとコアが多いマシンでテストしたいところだけど、個人ではこれが限界。
動作環境: MacBook Pro 15インチ Corei7 2GHz(物理コアは4つ。仮想コアとして8つ) 単位はmsec normal , threadId = 1, n = 30, avg = 3177.07, std = 10.39, median = 3179.97, mode = 3184.39, min = 3155.66, max = 3191.19 parallel, threadId = 1, n = 30, avg = 1110.68, std = 27.62, median = 1113.52, mode = 1081.60, min = 1065.08, max = 1157.97
CPU負荷は具体的な数値はとってませんが、パラレルの方はちゃんと全部使っている感じ。
通常のコレクション
パラレルコレクション
使い込んでみないと具体的にどういうところで使えるかわかりませんが、動画のエンコードとか面白そうかなと思ったりしています。
あわせて読みたい
Scala 並列コレクション メモ(Hishidama's Scala parallel collections Memo)
scala2.9のparallel collection の benchmark をしてみた - scalaとか・・・
Scalaの並列コレクションで実際に並列化されているメソッドを調べてみた - chimerastのエレガント指向プログラミング日記
*1:当然mapに渡す関数に副作用がないことが前提
10月22日発売のWEB+DB PRESS Vol.65で連載3回目の記事を書きました。
タイトルは「再考するJava【第3回】Java SE 7 新機能のポイント 〜言語仕様の小さな拡張&NIO.2 ファイルシステムインターフェイス〜」です。Java SE 7の新機能*1を紹介する記事を書きました。
try-with-resources構文は、一度使い始めると便利すぎて、後戻りできないですねw ダイヤモンドオペレータも、例外のマルチキャッチも便利です!
それと、水島さん、櫻庭さん、なぎせさんに、レビューをしていただきました。物書きってのは本当に難しいですね。おかげで勉強になりました。本当にありがとうございました!
書店に立ち寄ったらぜひ見てみてください。Java SE 7を知ってもらうよい機会になればと思っています。
あと、特集の「インフラの基礎知識」はみんな読むべきだなと思いました。
ということで、よろしくお願いします。
追記:
Eclipse 3.7.1でJava SE 7に正式に対応しています。
http://download.eclipse.org/eclipse/downloads/drops/R-3.7.1-201109091335/index.php