Use a semaphore to ensure that the robot only attempts to paint one part at a time.
In Exercise 10, the main program only sent a new paint request to the robot when the previous part had been painted. This was because the submitPaintRequest methods invoked from the main Java thread used the postEventAndWait method. The submitPaintRequest method did not return until the part had received a reply to indicate that the paint process had been completed. If this was guaranteed to be the only way that paint requests were sent to the robot, no conflict would arise. However, if any other paint requests were sent from the JACK thread, the robot could attempt to paint more than one part at the same time.
An alternative technique to ensure that the robot only attempts to paint one part at a time is to use a semaphore. A semaphore is a synchronisation resource which can be used to establish mutual exclusion regions of processing in JACK plans and threads. A semaphore is a binary resource that plans and threads may wait for and signal on when they have completed. Waiting entities queue on the semaphore and acquire the semaphore in FIFO order.
The semaphore has a single constructor:
Semaphore()
Methods are provided to grab and release the semaphore. signal() is used to release the semaphore. The semaphore is grabbed initially by the constructing thread (or plan) and must thus be released by a call to signal(). To grab the semaphore from within a plan, use planWait(). The planWait() method returns a special JACK type, the Cursor.
The cursor concept originates from relational databases, where a query can return multiple tuples in the form of a result set. Access to the elements in this set is then provided through a cursor. In JACK, these concepts have been extended to provide cursors which not only operate in the conventional manner but also operate on the temporal evolution of a query. The latter type of cursor is typically used in JACK applications to determine when a particular condition becomes true. Cursors which provide this additional capability within JACK are implemented as triggered cursors. Triggered cursors are not checked using a busy-wait loop – rather, they are only tested when the agent performs a modification action that impacts on the cursor. The cursor returned by the planWait() method is triggered when the semaphore is grabbed by the plan.
This means that a plan can use the @waitFor reasoning statement to wait until the plan has been able to grab the semaphore before it begins painting the part.
In this exercise we introduce a new ProcessPaintRequest plan. This plan can only paint a part when it has the semaphore. If it does not have the semaphore, it must wait until it gets the semaphore before it paints the part.
1. Create the named data of type Semaphore that is to be used to prevent the robot from attempting to paint more than one part at a time.
2. Open the Painting_DEP design diagram and
§ A uses link from the Painting capability to the ProcessPaintRequest plan.
§ A handles link from the StartPainting event to the Painting capability.
§ A posts link from the Painting capability to the StartPainting event.
§ A #private link from the Painting capability to the mutex data.
Figure 13: The Painting_DEP design diagram with the ProcessPaintRequest plan, StartPainting event and mutex named data
3. Edit the StartPainting event so that it:
startPainting(String c, String f)
{
colour = c;
from = f;
}
If editing the file as a JACK file, save and close the file before continuing.
4. As the semaphore is grabbed initially by the constructing thread, it must also be released by a call to signal(). The #private data Semaphore mutex(); declaration constructs the semaphore in the capability. Painting capability. To release the semaphore after it has been constructed, edit the capability and override its autorun method as follows:
protected void autorun()
{
mutex.signal();
}
5. Edit the ProcessPaintRequest plan as follows:
#handles event Paint pev;
#posts event StartPainting spev;
#sends event Finished fev;
@waitFor(mutex.planWait()); // Wait for semaphore (mutex).
try {
@subtask(spev.startPainting(pev.colour, pev.from));
@send(pev.from, fev.finished(pev.colour));
}
finally {
mutex.signal(); // Release semaphore (mutex).
}
If editing the file as a JACK file, save and close the file before continuing.
6. Use the browser to remove the @reply statements from the three 'painting' plans.
7. Remove the @waitFor reply and associated statements from the Part agent's SendPaintCommand plan.
8. Open the PaintRequesting_DEP design diagram and
§ A uses link from the PaintRequesting capability to the DisplayFinished plan.
§ A handles link from the Finished event to the PaintRequesting capability.
Figure 14: The PaintReqesting_DEP design diagram with the DisplayFinished plan and Finished event
9. Edit the DisplayFinished plan and
System.out.println(self.name()+" has been painted "
+fev.colour);
where fev is the reference to the Finished event handled by the plan.
If editing the file as a JACK file, save and close the file before continuing.
10. Save the project.
11. Compile and run the project.
运行结果:
(1) test with red
(2) test with no specified colour
(3) Painting part1@%portal the new colour requested - red
(4) Painting part1@%portal the new colour requested - red 2nd coat
(5) test with green
(6) test with green
(7) No specified colour. Painting part2@%portal red
(8) part1@%portal received finished notification: colour red
(9) Painting part3@%portal the new colour requested - green
(10) Painting part3@%portal the new colour requested - green 2nd coat
(11) part2@%portal received finished notification: colour null
(12) Painting part4@%portal green
(13) part3@%portal received finished notification: colour green
(14) part4@%portal received finished notification: colour green
运行结果分析:
由于将Part Agent的SendCommandPlan中的waitFor移除,不再等待Robot Agent的reply,而Robot Agent中每个Plan中都会@sleep(5),所以应该会产生异步现象,即Robot Agent会同时处理多个Part Agent的绘制请求。
但从结果来看,虽然main线程不再同步,但是Robot Agent还是按照先来先处理的方式,处理完成后再处理下一个Part Agent的绘制请求。原因在于在Robot Agent使用信号semaphore,标识当前Robot Agent是否正在处理绘制请求进行控制,增添ProcessPaintRequest规划,在该规划中通过waitFor读取信号的状态,若当前信号占用,则一直等待,信号空闲,则通过planWait()获取该信号,开始处理当前的绘制请求,处理完成后通过signal()释放信号资源。