提供: Minecraft Modding Wiki
2012年6月24日 (日) 03:22時点におけるUdonya (トーク | 投稿記録)による版 (Thread Safe API Methods)
移動先: 案内検索

スケジューラの解説

この解説では、Bukkitのスケジューラの利用方法を説明しています。

主な注意事項

主な注意事項は次の2点です。

  • 他のスレッドからAPIのメソッドを(一部例外あり)を呼び出さない
  • メインのスレッドをスリープ状態にしてはならない

解説

スケジューラは次の3種類のタスクをオプションとして提供しています。

  • 遅延タスク(Delayed Task)
  • 連続タスク(Repeating Task)
  • 戻り値を返すタスク

これらのタスクは2種類のスレッドとして実行する事ができます。

  • メインサーバスレッド
  • 専用スレッド

スレッドの種類

スケジューラにどのスレッドタイプを利用すれば良いかが、最も重要な問題です。
間違ったスレッドタイプを利用すると、検出が困難な障害を断続的に発生させる原因となります。
また、そのような障害は試験サーバでは発生せず、頻繁に連続稼動しているサーバでのみ発生する場合がある等、
再現性が低い障害となる場合もあるため、原因の特定を更に困難にします。

Minecraftサーバはマルチスレッドであり、
一つのメインサーバーのスレッドと、他のヘルパースレッドが存在します。

多くのデータ構造は、サーバのメインスレッドからのみアクセスされます。
別のスレッドからこれらのデータを変更しようとすると、
2つのスレッドが同時にデータにアクセスする事になるため、
データ破損を引き起こす可能性があります。

メインスレッドは、サーバーのCPU時間の配分を処理します。
このスレッドは、他のタスクへCPU時間を配分するために、秒間20回のスリープを実行します。

少数のBukkitAPIはスレッドセーフです。
あなたのプラグインが使っているAPIがスレッドセーフなものである事を確認していない限り、
基本的にBukkitAPIの命令はスレッドセーフではないものとして開発して下さい

同期タスク

サーバのメインスレッドが実行するタスクです。

重要: このスレッドが停止すると、サーバがフリーズします。


メインスレッドは下記の用途に使えます

  • スケジューラメソッドの呼び出し
    • スケジューラの戻り値を取得する際は注意を要します(後方の説明に記載)
  • 停止・待機処理を行わないタスク


メインスレッドは下記の用途に使わなくてはいけません

  • Bukkit APIのメソッドを呼び出すタスク(スレッドセーフな方法を除く)


メインスレッドは下記の用途に使ってはいけません

  • 自スレッドをスリープ状態にするタスク
    • 停止・待機処理を含むスレッド。(例:ネットワーク通信の受信タスク)


メインスレッド上で同期タスクを実行する事自体は良いのですが、
待機や停止の時間がほぼゼロであるものに限ります。

訳者注: 1つの処理ステップが長時間を費やす処理は、サーバ自体をその間待機状態に置いてしまうため、メインスレッド上で行うべきではない事を説明しています。

非同期タスク

非同期タスクは、サーバのメインスレッド以外のスレッドによって実行されます。専用のタスクとして実行されるため、同期タスクで説明したような問題を引き起こすことなく、自タスクを停止・スリープ状態にする事ができます。この特性は、同期タスクの真逆に相当します。


非同期タスクは下記の用途に利用できます

  • スケジューラメソッドの呼び出し
  • プラグインAPIを利用しないタスク


非同期タスクは下記の用途に利用しなければなりません

  • 自スレッドをスリープ状態にするタスク
    • 停止・待機処理を含むスレッド。(例:ネットワーク通信の受信タスク)


非同期タスクは下記の用途に利用してはいけません

  • Bukkit APIのメソッドを呼び出すタスク(スレッドセーフな方法を除いては、下記参照)


タスクの種類

タスクには、連続タスク, 遅延タスク, 戻り値を返すタスク, の3種類があります。

APIのインタフェースの仕様については[こちら]を参照して下さい。

遅延タスク

一定の遅延を置いてから実行されるタスクです。遅延時間にはゼロを指定可能です。

具体例

This submits a task to the main thread to be executed after 3 seconds.

myPlugin.getServer().getScheduler().scheduleSyncDelayedTask(myPlugin, new Runnable() {

   public void run() {
       getServer().broadcastMessage("This message is broadcast by the main thread");
   }
}, 60L);

The time delay is 60. This counts in server ticks. There are 20 ticks per second, so the delay is 60/20 = 3.

This code could be executed by another thread. If the code

getServer().broadcastMessage("This message is broadcast by an async thread");

was executed by another thread, it could cause problems since the broadcastMessage method is being executed by another thread.

繰り返しタスク

繰り返し実行されるタスクです。遅延時間も設定可能です。

具体例

This submits a task to an async thread to be executed after 3 seconds with a period of 10 seconds.

myPlugin.getServer().getScheduler().scheduleAsyncRepeatingTask(myPlugin, new Runnable() {

   public void run() {
       System.out.println("This message is printed by an async thread");
   }
}, 60L, 200L);

Both times count in server ticks. The initial delay is 60/20 = 3 seconds and the period is 200/20 = 10 seconds.

The above code will print "This message is printed by an async thread" every 10 seconds.

戻り値を返すタスク

スケジューラから戻り値を返す事ができるタスクです。メインスレッドから実行可能なタスクです。

この仕組みの目的は、他タスクから呼ばれたAPIのメソッドに戻り値を返させる事です。

スケジューラは、戻り値として返されるであろうオブジェクトを取得可能にします。

具体例

Future<String> returnFuture = myPlugin.getServer().getScheduler().callSyncMethod(myPlugin, new Callable<String>() {

   public String call() {
       return "This is the string to return";
   }

});

try {
    // This will block the current thread 
    String returnValue = returnFuture.get();
    System.out.println(returnValue);
} catch (InterruptedException e) {
    System.out.println("Interrupt triggered which waiting on callable to return");
} catch (ExecutionException e) {
    System.out.println("Callable task threw an exception");
    ee.getCause().printStackTrace();
}

This will submit the callable and then use the Future.get() method. This method sleeps the current thread until the Callable has returned a value and gets the result.

メインスレッドからの利用

この方法は非推奨です。 この仕組みは、メインスレッドから利用される事を想定したものではありません。

.get()メソッドを利用している場合、メインスレッドがこのタスクを完了するまでの間、現行スレッド(=メインスレッド)をスリープ状態にします。

どうしてもメインスレッド上で使用する必要がある場合は、返されるであろうスケジューラの戻り値に対する.get()メソッドを呼び出す前に、.isDone()メソッドをチェックしなければなりません。さもなくばメインスレッドは、メインスレッド自身がタスクを完了させるまでスリープ状態になります。

Thread Safe API Methods

Bukkit API methods which are thread safe are: