10.マルチタスク TOPへ 12.Bluetooth通信

11.SemaphoreとMutex


先のマルチタスクのプログラムにより、様々な動作をロボットに行わせることができました。 しかし、マルチタスクにしたが故に起こる問題もあります。 最も代表的な例がタスクが他のタスクの実行を妨害することです。 これを解決するためにセマフォとミューテックスです。 ここではこれらの動作とそれを扱うプログラムを学びます。


1.セマフォがない場合の問題

まず、セマフォがない場合にどのようなことが起こってしまうかを、その動作で確認してみましょう。
次のプログラムを作成してください。ここでは semaphore1.c とします。
semaphore1.c
#include "nxtlib.h"

#define TURN_TIME 450
#define MOVE_TIME 1500

task touch();

task main(){
	SetSensorTouch(S1);

	nSchedulePriority = kHighPriority;
	StartTask(touch);
	nSchedulePriority = kDefaultTaskPriority;

	while(true){
		motor[motorB] = 50;
		motor[motorC] = 50;
		wait1Msec(MOVE_TIME);

		motor[motorB] = 50;
		motor[motorC] = -50;
		wait1Msec(TURN_TIME);
	}
}

task touch(){
	nSchedulePriority = 10;

	while(true){
		while(SensorValue(S1) != 1){
			wait1Msec(1);
		}

		motor[motorB] = -50;
		motor[motorC] = -50;
		wait1Msec(MOVE_TIME);

		motor[motorB] = -50;
		motor[motorC] = 50;
		wait1Msec(TURN_TIME);
	}
}

このプログラムでは2つのタスクは以下のような動作をするようにされています。
また、タスクの優先度はtouchのタスクの方が大きいように設定されているため、touchタスクが優先されると予想されます。 つまり、ロボットは四角の走行中にタッチセンサが押されたら後退・右折を行ってくれるはずです。

それではこのプログラムを動作させてください。 実際にタッチセンサを押すと、後退が中断され左折したり、右折が中途半端に行われる、または右折が行われないことがあると思います。 つまり、予想していた動きとは異なった動きをしてしまっているのです。

なぜ、このような動作をしてしまったのかを解説します。
task main(){
	 〜中略〜
	while(true){
		motor[motorB] = 50;
		motor[motorC] = 50;
		wait1Msec(MOVE_TIME);	…@

		motor[motorB] = 50;
		motor[motorC] = -50;
		wait1Msec(TURN_TIME);	…A
	}
}

task touch(){
	 〜中略〜
	while(true){
		while(SensorValue(S1) != 1){
			wait1Msec(1);
		}
	
		motor[motorB] = -50;
		motor[motorC] = -50;
		wait1Msec(MOVE_TIME);	…B

		motor[motorB] = -50;
		motor[motorC] = 50;
		wait1Msec(TURN_TIME);	…C
	}
}

ここで注目すべきは、両タスクに記述されている@〜Cのwait関数です。

main関数の赤字@のwait1Msecが働いている間にタッチセンサが押されると、touchタスク中のwhileループを抜けて処理が移ります。
しかし、後退中にオレンジ色Bのwait1Msec関数によりtouchタスクも処理を待機してしまいます。
これにより、この状態では両方のタスクが処理を待機する状態になっているので、優先度に依らず先に待機が解除されたタスクが処理されます。

そして、先にmainタスクに処理が移ってしまい、右折の動作を行います。
さらに、AとCも同様にして『wait関数により処理がまた移り…』を繰り返してしまいます。

このようにして、mainとtouchのタスク間でモータの実行権を奪い合っている状況となっているのです。

このように、マルチタスクでは、タスク間でモータなどのハードウェア資源の実行権を奪い合っている状態が起こりうるのです。
この問題を解決するためには、どのタスクがモータを使用しているかを示す変数を用いることが有効です。 つまり、その変数によってあるタスクがモータ使用中には、他のタスクがモータを使用できなくなるという操作があれば解決します。
これを行うのが「セマフォ」や「ミューテックス」であるのです。


2.セマフォを用いる

それでは先ほどのプログラムを改良します。ここではセマフォを用いたプログラムを作成しましょう。
セマフォとは、元々は「腕木信号(右図)」や「手旗信号」を由来としています。 腕木信号は、腕木が上がっているときに列車は進行し、下がっているときに停止するようになっています。
これと同じように、セマフォ変数の値が0以外ならタスクを処理、0ならばタスクを待機することで、タスクの排他処理を行います。
セマフォ変数を用いる場合は次の手順で行います。
  1. セマフォ変数に任意の正の整数値を初期値としてセットする
  2. セマフォ変数を要求し、セマフォ変数が0以外になるまで待機する
  3. 0以外になったらセマフォ変数を取得したら、セマフォ変数を1減算する
  4. タスクを処理する
  5. セマフォ変数を1加算して、セマフォ変数を元に戻す
このようにしてタスクの排他処理を行います。
udeki-signal.jpg(5113 byte)

それでは次のプログラムを作成してください。ここでは semaphore2.c とします。
semaphore2.c
#include "nxtlib.h"

#define TURN_TIME 450
#define MOVE_TIME 1500

TSem sMotor;

task touch();

task main(){
	SetSensorTouch(S1);
	InitSem(sMotor, 1);

	nSchedulePriority = kHighPriority;
	StartTask(touch);
	nSchedulePriority = kDefaultTaskPriority;

	while(true){
		AcquireSem(sMotor);
		motor[motorB] = 50;
		motor[motorC] = 50;
		ReleaseSem(sMotor);
		wait1Msec(MOVE_TIME);

		AcquireSem(sMotor);
		motor[motorB] = 50;
		motor[motorC] = -50;
		ReleaseSem(sMotor);
		wait1Msec(TURN_TIME);
	}
}

task touch(){
	nSchedulePriority = 10;

	while(true){
		while(SensorValue(S1) != 1){
			wait1Msec(1);
		}

		AcquireSem(sMotor);
		motor[motorB] = -50;
		motor[motorC] = -50;
		wait1Msec(MOVE_TIME);

		motor[motorB] = -50;
		motor[motorC] = 50;
		wait1Msec(TURN_TIME);
		ReleaseSem(sMotor);
	}
}

それではプログラムを動かしてみてください。 先ほどのように、動作中にタッチセンサを押すと、後退・右折までを一貫して行うようになったと思います。
それでは今回新たに用いたAPIなどについて解説します。

InitSem(sMotor, 1)
セマフォ変数に初期値をセットするAPIです。セマフォ変数は複数のタスクで取り扱うため、外部変数として宣言する必要があります。
第1引数は初期化したいセマフォ変数を記述します。
第2引数は初期値を記述します。初期値は正の整数値で指定します。 今回の場合は、モータを処理するタスクは1つだけである必要があるため、初期値は1とセットしました。
このAPIは nxtlib.h に記述されています。

AcquireSem(sMotor)
セマフォの要求・待機・取得を行うAPIです。 セマフォ変数が0であった場合は待機し、0以外ならセマフォ変数を1減算して後に記述されている処理を実行します。
このAPIは nxtlib.h に記述されています。

ReleaseSem(sMotor)
セマフォの開放を行うAPIです。 セマフォ変数を1増分します。セマフォを取得した場合は、必ずこのAPIでセマフォの開放を行ってください。
このAPIは nxtlib.h に記述されています。

今回は、タッチセンサ反応後の処理を一括して行いたいので、反応後の処理前にセマフォを取得し、処理全てを終えたらセマフォを開放します。 一方、通常の四角に走行する処理は一括である必要がないため、モータのパワー設定の前後でセマフォの取得と開放を行います。 なぜなら、四角走行中の間はwait関数が働いている間でこの間にタッチセンサの入力を受け付けるようにしたいので、 このwait関数はセマフォを取得してはいけないのです。
このようにして、複数の動作の内「どれを一括して行いたいか」を考えた上でセマフォのプログラムを記述しましょう。


3.ミューテックスを用いる

次にミューテックスを用いたプログラムを作成しましょう。
ミューテックスとは、基本的にはセマフォ変数の初期値が1の場合と同じ動作となります。 また、それ以外に優先度継承が行われるという特徴があります。 RobotCの場合では、ミューテックスを取得している間、タスクの優先度が最高になるという処理によって優先度継承が行われます。

それでは次のプログラムを作成してください。ここでは semaphore3.c とします。
semaphore3.c
#include "nxtlib.h"

#define TURN_TIME 450
#define MOVE_TIME 1500

TMutex mMotor;

task touch();

task main(){
	SetSensorTouch(S1);
	InitMutex(mMotor);

	nSchedulePriority = kHighPriority;
	StartTask(touch);
	nSchedulePriority = kDefaultTaskPriority;

	while(true){
		AcquireMutex(mMotor);
		motor[motorB] = 50;
		motor[motorC] = 50;
		ReleaseMutex(mMotor);
		wait1Msec(MOVE_TIME);

		AcquireMutex(mMotor);
		motor[motorB] = 50;
		motor[motorC] = -50;
		ReleaseMutex(mMotor);
		wait1Msec(TURN_TIME);
	}
}

task touch(){
	nSchedulePriority = 10;

	while(true){
		while(SensorValue(S1) != 1){
			wait1Msec(1);
		}

		AcquireMutex(mMotor);
		motor[motorB] = -50;
		motor[motorC] = -50;
		wait1Msec(MOVE_TIME);

		motor[motorB] = -50;
		motor[motorC] = 50;
		wait1Msec(TURN_TIME);
		ReleaseMutex(mMotor);
	}
}

では、プログラムを動作させてください。この場合もセマフォを使用した場合と同じように、後退・右折が中断されずに行われたと思います。
LEGOの場合では、基本的にモータなどを取り扱うタスクは1つだけが望ましいため、ミューテックスを使うのも良いでしょう。
今回新たに用いたAPIなどについて解説します。

InitMutex(mMotor)
ミューテックスを初期化します。このAPIはnxtlib.hに記述されています。

AcquireMutex(mMotor)
ミューテックスの要求・待機・取得を行います。このAPIはnxtlib.hに記述されています。

ReleaseMutex(mMotor)
ミューテックスの開放を行います。このAPIはnxtlib.hに記述されています。

このようにして、ミューテックスを用いた場合でも望んだ動作を実現させることができました。
セマフォとミューテックスの違いはLEGOを用いるプログラムではあまり差異がありませんが、その違いを覚えておくようにしましょう。


4.課題

前回の課題を改良し、ライントレース中に超音波センサによって障害物を回避するロボットを作成せよ。



10.マルチタスク TOPへ 12.Bluetooth通信