前回の記事では、EventQueueについてThe EventQueue API Tutorialでメモリの分断まで読んでみました。今回は、スタティックなEventQueueについて考えます。
題材について
この記事で題材として取り上げたのは、The EventQueue APIというTutorialです。
静的なEventQueue(Static EventQueue)
The EventQueue API provides a mechanism for creating a static queue, a queue that doesn't use any dynamic memory allocation and accepts only user-allocated events.
EventQueueのAPIでは、静的なキューを作成する仕組みを提供しています。静的なキューは、動的なメモリ割り当てを使わず、ユーザが割り当てたイベントのみを使用します。
After you create a static queue (by passing zero as
sizeto its constructor), you can postUserAllocatedEventto it.
コンストラクタの引数
sizeにゼロを与えて静的なキューを作成した後、UserAllocatedEventをキューに入れることができます。
Using static EventQueue combined with UserAllocatedEvent ensures no dynamic memory allocation will take place during queue creation and events posting and dispatching.
UserAllocatedEventと静的なEventQueueを組み合わせることで、キューの作成とイベントの投入および実行に動的なメモリ割り当てを使用しないことが確約されます。
You can also declare queues and events as static objects (static in the C++ sense), and then memory for them will be reserved at compile time:
また、キューとイベントを静的なオブジェクト(C++でいうところのstatic)として宣言することもできるので、それらのメモリはコンパイル時に確保されます。
前回の記事までで使用してきたEventQueueでは、実行時にコンストラクタに引数を与えて、必要なメモリを確保して動作する方式をとっていました。ここで紹介されている静的EventQueueは、コンパイル時にメモリを確保してしまう方式です。
ややこしいことは、いいから、コードを見てみましょう。少し、原文から変更を加えています。
/*
* Copyright (c) 2019 ARM Limited. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
* Licensed under the Apache License, Version 2.0 (the License); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
# include "mbed.h"
いちおう、クレジットも入れておきます。
// Creates static event queue
static EventQueue queue(0);
これまで、EventQueueインスタンスを作成するときには、引数にメモリ領域の大きさを指定していました。静的EventQueueでは、実行時にメモリ割り当てを行わせないようにゼロを与えています。
void handler(int count);
// Creates events for later bound
auto event1 = make_user_allocated_event(handler, 1);
auto event2 = make_user_allocated_event(handler, 2);
// Creates event bound to the specified event queue
auto event3 = queue.make_user_allocated_event(handler, 3);
auto event4 = queue.make_user_allocated_event(handler, 4);
これまでは、実行時に動的にイベントを作成していましたが、この例ではコンパイル時にイベントを作成します。make_user_allocated_event()関数は、EventQueueを選ばないUserAllocatedEventオブジェクトを作成します。一方、make_user_allocated_event()メソッドは、特定のEventQueueに紐づいたUserAllocatedEventオブジェクトを作成します。
void handler(int count) {
printf("UserAllocatedEvent = %d at %lld\n", count, Kernel::get_ms_count());
return;
}
それぞれのイベントで実行される関数handlerでは、引数の値を表示して、何番目のイベントが呼び出されたかを示します。ちょっと改造して、時刻情報の表示を追加しました。
void post_events(void)
{
// Single instance of user allocated event can be posted only once.
// Event can be posted again if the previous dispatch has finished or event has been canceled.
// bind & post
event1.call_on(&queue);
// event cannot be posted again until dispatched or canceled
bool post_succeed = event1.try_call();
assert(!post_succeed);
queue.cancel(&event1);
// try to post
post_succeed = event1.try_call();
assert(post_succeed);
// bind & post
post_succeed = event2.try_call_on(&queue);
assert(post_succeed);
// post
event3.call();
// post
event4();
}
イベントをキューに入れる方法が示されています。特定のキューを指定しないイベントevent1では、event1.call_on()メソッドで投入すべきキューを引数で指定しています。
一度キューに投入したイベントは、実行またはキャンセルされるまで再投入ができません。event1.try_call()メソッドでは、event1のキューへの投入を試し、投入できたかどうかを返り値で示します。event1.call_on()メソッドでは、この返り値は内部で処理されて外には出てきません。ここでは、event1.call_on()メソッドで投入した直後であるため、投入に失敗することを期待しています。
このままでは、イベントevent1の投入ができないので、いったんキューに入っているイベントをqueue.cancel(&event1)でキャンセルします。すると、こんどはevent1.try_call()でイベントを投入できるようになります。
try_call()メソッドには、キューを指定することができるevent2.try_call_on(&queue)メソッドもあります。
イベントの生成時にqueue.make_user_allocated_event()メソッドを使うと、イベントの投入時にキューの指定をevent3.call()のように省略できます。メソッドそのものを省略してevent4()とすることもできます。
int main()
{
printf("*** start ***\n");
Thread event_thread;
// The event can be manually configured for special timing requirements
// specified in milliseconds
// Starting delay - 100 msec
// Delay between each event - 200msec
event1.delay(100);
event1.period(200);
event2.delay(100);
event2.period(200);
event3.delay(100);
event3.period(200);
event4.delay(100);
event4.period(200);
event_thread.start(callback(post_events));
// Posted events are dispatched in the context of the queue's dispatch function
queue.dispatch(600); // Dispatch time - 600msec
// 600 msec - 3 set of events will be dispatched as period is 200 msec
event_thread.join();
}
四つのイベントには、それぞれdelay()メソッドとperiod()メソッドで実行時期と実行周期を設定しています。こうすると、イベントをキューに入れてから100ms、300ms、500ms、というタイミングでイベントが実行されます。
イベントをキューに入れるための関数post_eventsは、event_threadという独立したスレッドで実行されます。EventQueueインスタンスは、queue.dispatch(600)により600msだけイベントの実行に使われるので、それぞれのイベントは3回ずつ実行されるはずです。はたして実行結果は、こうなりました。
*** start ***
UserAllocatedEvent = 1 at 100
UserAllocatedEvent = 2 at 100
UserAllocatedEvent = 3 at 100
UserAllocatedEvent = 4 at 100
UserAllocatedEvent = 2 at 300
UserAllocatedEvent = 3 at 300
UserAllocatedEvent = 4 at 300
UserAllocatedEvent = 2 at 500
UserAllocatedEvent = 3 at 500
UserAllocatedEvent = 4 at 500
event1だけが繰り返し実行されていません。
四つのイベントの中でevent1だけはqueue.cancel(&event1)でキューから取り除かれて、再度キューに入れられています。試しにqueue.cancel(&event1);からassert(post_succeed);までをコメントアウトしてみたら、event1も3回実行されるようになりました。どうやら、queue.cancel(&event1)でperiodの値がリセットされてしまったようです。
そこで、queue.cancel(&event1)の後、periodを再設定してみました。
queue.cancel(&event1);
event1.period(180);
振る舞いの違いが分かるように180ms周期にしてみました。すると、event1も3回実行されるようになりました。
*** start ***
UserAllocatedEvent = 1 at 100
UserAllocatedEvent = 2 at 100
UserAllocatedEvent = 3 at 100
UserAllocatedEvent = 4 at 100
UserAllocatedEvent = 1 at 280
UserAllocatedEvent = 2 at 300
UserAllocatedEvent = 3 at 300
UserAllocatedEvent = 4 at 300
UserAllocatedEvent = 1 at 460
UserAllocatedEvent = 2 at 500
UserAllocatedEvent = 3 at 500
UserAllocatedEvent = 4 at 500
delayの値は再設定していないのですが、どうやら、periodはリセットされるのに、delayはリセットされていない様子です。
以上で、Tutorialは、おしまいです。
追記 (2020-02-08)
このコード例での「event1のperiodがcancelによってリセットされる」問題もgithubのIssueに提起したところ、「コード例に問題はない。バグを見つけた。」という反応がありました。
つまり、ライブラリに問題があるため、思った通りの動作にならなかったという事ですか?
関連サイト
Mbed OSのページ
EventQueueのTutorial
Mbed対応Cypress製品のページ
関連記事
EventQueueを使ってみたよ (1)
EventQueueを使ってみたよ (2)
EventQueueを使ってみたよ (3)
EventQueueを使ってみたよ (4)
EventQueueを使ってみたよ (5)
EventQueueを使ってみたよ (6)
EventQueueを使ってみたよ (7)
EventQueueを使ってみたよ (8)
EventQueueを使ってみたよ (9)