
Verse言語で排他制御を教えて!
こういった悩みにお答えします.
本記事の信頼性
- リアルタイムシステムの研究歴12年.
- 東大教員の時に,英語でOS(Linuxカーネル)の授業.
- 2012年9月~2013年8月にアメリカのノースカロライナ大学チャペルヒル校(UNC)コンピュータサイエンス学部で客員研究員として勤務.C言語でリアルタイムLinuxの研究開発.
- プログラミング歴15年以上,習得している言語: C/C++,Python,Solidity/Vyper,Java,Ruby,Go,Rust,D,HTML/CSS/JS/PHP,MATLAB,Verse(UEFN), Assembler (x64,aarch64).
- 東大教員の時に,C++言語で開発した「LLVMコンパイラの拡張」,C言語で開発した独自のリアルタイムOS「Mcube Kernel」をGitHubにオープンソースとして公開.
- 2020年1月~現在はアメリカのノースカロライナ州チャペルヒルにあるGuarantee Happiness LLCのCTOとしてECサイト開発やWeb/SNSマーケティングの業務.2022年6月~現在はアメリカのノースカロライナ州チャペルヒルにあるJapanese Tar Heel, Inc.のCEO兼CTO.
- 最近は自然言語処理AIとイーサリアムに関する有益な情報発信や,Unreal Editor for Fortnite(UEFN)でゲーム開発に従事.
- (AI全般を含む)自然言語処理AIの論文の日本語訳や,AIチャットボット(ChatGPT,Auto-GPT,Gemini(旧Bard)など)の記事を50本以上執筆.アメリカのサンフランシスコ(広義のシリコンバレー)の会社でChatGPT/Geminiを訓練するプロンプトエンジニア・マネージャー・Quality Assurance(QA)の業務委託の経験あり.
- (スマートコントラクトのプログラミングを含む)イーサリアムや仮想通貨全般の記事を200本以上執筆.イギリスのロンドンの会社で仮想通貨の英語の記事を日本語に翻訳する業務委託の経験あり.
- UEFNで10本以上のゲームを開発し,フォートナイト上で公開(Fortnite,Fortnite.GG).
こういった私から学べます.
今回のテーマはVerse言語の排他制御(Mutual Exclusion)です.
複数のNPCが同じ場所を通ろうとすると,互いにぶつかったり,移動できなくなったりすることがあります.
このような問題を防ぐために使われるのが排他制御です.
C言語では,ミューテックス(Mutex)やセマフォ(Semaphore)などを利用して排他制御を実現します.
一方,Verse言語では,C言語とは異なる仕組みで排他制御を実現できます.
今回は,C言語とVerse言語の違いを比較しながら,実際に開発したAI Runnerのソースコードを用いて解説していきます.
AI Runnerは以下のゲームで遊べます.
-

-
【UEFN】Ninja Random Switch Deathrun 300+【島コード:3505-9013-4036】
Ninja Random Switch Deathrun 300+とは Ninja Random Switch Deathrun 300+は,フォートナイトで遊べる忍者テーマのデスランです. 最大の特 ...
続きを見る
排他制御とは
排他制御とは,複数の処理が同じ資源を同時に使用しないように制御する仕組みです.
例えば,2体のNPCが1体しか通れない狭い通路に進入する場合を考えます.
両方のNPCが同時に通路へ進入すると,衝突して移動できなくなる可能性があります.
そこで,片方のNPCが通路を使用している間,もう片方のNPCを待機させます.
これが排他制御の基本的な考え方です.
なお,競合状態(Race Condition)や同期などの基本的な概念については,以下の記事で詳しく解説しています.
-

-
【C言語】プロセス間通信とは【共有メモリ,セマフォ,ミューテックス,Read-Write Lock,名前付きパイプ,ソケット通信,メッセージキュー,RPC】
こういった悩みにお答えします. こういった私から学べます. プロセス間通信 プロセス間通信(IPC:InterProcess Communication)とは,コンピュータの動作において,複数プロセス ...
続きを見る
-

-
【第8回】元東大教員から学ぶLinuxカーネル「同期」
こういった私から学べます. 前回を読んでいない方はこちらからどうぞ. Linuxカーネルの記事一覧はこちらからどうぞ. 今回のテーマは同期です. 同期を理解すると,カーネルの本質的な課題が深く理解でき ...
続きを見る
本記事では,Verse言語でどのように排他制御を実装するのかを紹介します.
C言語とVerse言語の違い
まず,C言語とVerse言語では,処理の実行方法が異なります.
C言語で複数のスレッドを実行する場合,あるスレッドの処理中に,別のスレッドへ実行が切り替わる可能性があります.
そのため,共有データを安全に更新するには,ミューテックスなどを使用して保護する必要があります.
一方,Verse言語では,中断を伴わない同期処理は,途中でほかのタスクに切り替わりません.
そのため,共有データの確認と更新を中断のない処理として記述することで,競合を防ぐことができます.
ただし,Sleepなどで処理を中断すると,ほかのタスクが実行されるため注意が必要です.
ミューテックス
ミューテックスとは,1つの処理だけが共有資源を使用できるようにする仕組みです.
C言語のミューテックス
C言語では,pthread_mutex_lock関数とpthread_mutex_unlock関数を使用します.
|
1 2 3 4 5 |
pthread_mutex_lock(&mutex); /* Access shared data */ pthread_mutex_unlock(&mutex); |
pthread_mutex_lock関数でロックを取得し,pthread_mutex_unlock関数で解放します.
ロックされている間,ほかのスレッドは同じミューテックスを取得できません.
詳しくは以下の記事をご覧下さい.
-

-
【C言語】ミューテックスとは
こういった悩みにお答えします. こういった私から学べます. 本記事ではスレッドとプロセスを理解している前提で説明します. スレッドとプロセスを学びたいあなたはこちらからどうぞ. ミューテックス ミュー ...
続きを見る
Verse言語の排他制御
Verse言語では,C言語のミューテックスを使用する代わりに,中断のない同期処理を利用できます.
AI Runnerでは,複数のNPCが同じ通路を使用しないように,通路の予約情報を管理しています.
以下は,TryClaimAIRunnerLaneLease関数の一部です.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
LaneBlocked := HasAIRunnerLaneOccupancyFromState( State, Agent, RoomKey, GroupKey, LaneIndex, NowSeconds ) if(LaneBlocked?): return false if(set State.LaneLeaseRoomKeyByAgent[Agent] = RoomKey) {} if(set State.LaneLeaseGroupKeyByAgent[Agent] = GroupKey) {} if(set State.LaneLeaseIndexByAgent[Agent] = LaneIndex) {} if(set State.LaneLeaseUntilByAgent[Agent] = LeaseUntilSeconds) {} return true |
この処理では,まずHasAIRunnerLaneOccupancyFromState関数で,ほかのNPCが対象のLaneを予約しているかどうかを確認します.
すでにほかのNPCに予約されている場合,falseを返します.
予約されていない場合,NPCの予約情報を更新してtrueを返します.
ここで重要なのは,Laneの予約状態の確認から更新までにSleepなどの中断処理がないことです.
そのため,確認と予約の間にほかのタスクが割り込むことを防げます.
この仕組みは,C言語のpthread_mutex_trylock関数のように,資源を取得できるか試す処理に近いものです.
ただし,Verse言語の予約処理はOSのミューテックスそのものではありません.
セマフォ
セマフォとは,同時に使用できる資源の数を制限する仕組みです.
例えば,1つの通路を同時に1体のNPCしか使用できないようにする場合が挙げられます.
C言語のセマフォ
C言語では,sem_wait関数とsem_post関数を使用します.
|
1 2 3 4 5 |
sem_wait(&sem); /* Use shared resource */ sem_post(&sem); |
sem_wait関数で資源を取得し,sem_post関数で解放します.
資源が使用中の場合,sem_wait関数は利用可能になるまで待機します.
詳しくは以下の記事をご覧下さい.
-

-
【C言語】セマフォとは
こういった悩みにお答えします. こういった私から学べます. 本記事ではスレッドとプロセスを理解している前提で説明します. スレッドとプロセスを学びたいあなたはこちらからどうぞ. セマフォ セマフォとは ...
続きを見る
Verse言語のセマフォ相当の実装
AI Runnerでは,通路をLaneとして管理しています.
そして,1つのLaneを1体のNPCだけが予約できるようにしています.
これは,容量1のセマフォと同じ考え方です.
AI Runnerでは,TryClaimBestAIRunnerPendingLaneLease関数などを使用して,Laneの予約を試みます.
以下は,実際のAI Runnerで予約を繰り返す処理の一部です.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
loop: NowSeconds := GetSimulationElapsedTime() LeaseUntil := NowSeconds + SafeLeaseSeconds ai_runner_speed_runtime.ArmAIRunnerLaneDemand( CurrentAgent, RoomKey, GroupKey, PreferredLane, SafeTargetLaneCount, false, LeaseUntil ) PendingLane := ai_runner_speed_runtime.TryClaimBestAIRunnerPendingLaneLease( CurrentAgent, RoomKey, GroupKey, SafeTargetLaneCount, PreferredLane, false, NowSeconds, LeaseUntil ) if(PendingLane >= 0): # Clean up reservation-related state. EndAIRunnerIntentionalWaitIfStarted(Owner, WaitStarted) ai_runner_speed_runtime.RelaxAIRunnerLaneDemandIfMatches( CurrentAgent, RoomKey, GroupKey ) ai_runner_speed_runtime.ReleaseAIRunnerLaneChoiceLock( CurrentAgent, RoomKey, GroupKey ) return true # Wait before retrying. Sleep(0.10) |
※実装から予約の再試行に関する部分を抜粋・簡略化しています.実際にはNPCの状態確認やWatchdogの管理も行っています.
まず,ArmAIRunnerLaneDemand関数でLaneの利用要求を登録します.
次に,TryClaimBestAIRunnerPendingLaneLease関数でLaneの予約を試みます.
PendingLaneが0以上の場合は予約成功です.関連する待機状態を解除して,trueを返します.
一方,予約できなかった場合は,Sleep(0.10)で約0.1秒間待機します.
その後,ループで再度予約を試みます.
このように,AI Runnerでは予約の試行とSleepによる待機を組み合わせて排他制御を実現しています.
C言語のsem_wait関数とは異なり,資源が解放されたことを直接通知するのではなく,定期的に予約状態を確認する方式です.
Leaseによる予約の管理
AI Runnerでは,Laneの予約にLease(期限付き予約)を使用しています.
例えば,NPCが通路を予約したまま死亡した場合,予約が残り続けると,ほかのNPCが通路を使えなくなります.
そこで,予約に有効期限を設定しています.
|
1 |
var LaneLeaseUntilByAgent : [agent]float = map{} |
このMapでは,NPCごとの予約期限を管理します.
予約を確認するときは,以下の条件を使用します.
|
1 |
OtherUntil > NowSeconds |
現在時刻が予約期限に達した場合,その予約を無効として扱います.
これにより,古い予約が永久に残ることを防ぎます.
また,予約が不要になった場合は,以下のように有効期限を0にして解放します.
|
1 |
if(set State.LaneLeaseUntilByAgent[Agent] = 0.0) {} |
これは,C言語のsem_post関数と同じように,ほかの処理が資源を使えるようにするための操作です.
ただし,実際の通路が空いているかどうかは別の問題なので,NPCの位置や移動状態も考慮する必要があります.
Read-Write Lock
Read-Write Lockとは,読み取りと書き込みを区別して共有データを保護する仕組みです.
C言語では,pthread_rwlock_rdlock関数とpthread_rwlock_wrlock関数を使用します.
読み取りは複数のスレッドで同時に行えますが,書き込みは排他的に行われます.
詳しくは以下の記事をご覧下さい.
-

-
【C言語】POSIXスレッドのRead-Write Lockの使い方
こういった悩みにお答えします. こういった私から学べます. 本記事ではスレッド,プロセス,ミューテックス,セマフォを理解している前提で説明しますので,これらを学びたいあなたはこちらからどうぞ. POS ...
続きを見る
一方,Verse言語では,中断のない同期処理を利用して共有状態を管理するため,C言語のRead-Write Lockとは異なる方法で実装できます.
AI Runnerでも,Laneの予約状態を読み取る関数と,予約情報を更新する関数を分けて実装しています.
ただし,これはC言語のRead-Write Lockと同じものではなく,Verse言語の実行モデルを利用した共有状態の管理です.
排他制御の注意点
Verse言語で排他制御を実装する場合は,以下の点に注意して下さい.
- 中断処理:予約の確認と更新の間に
Sleepを挟まない. - 予約の解放:NPCが死亡した場合などに,古い予約を残さない.
- タイムアウト:予約できないNPCが永久に待機しないようにする.
AI Runnerでは,これらを考慮して,Reservation,Lease,Watchdog,Recoveryなどを組み合わせています.
まとめ
今回は,C言語と比較しながらVerse言語の排他制御について解説しました.
重要なポイントは以下になります.
- C言語では,ミューテックスやセマフォなどを利用して排他制御を実現する.
- Verse言語では,中断のない同期処理を利用して共有状態を安全に操作できる.
- AI Runnerでは,Reservationを使って複数NPCによる通路の競合を防いでいる.
Sleepと予約の再試行を組み合わせることで,資源が利用可能になるまで待機できる.- Leaseを利用すると,古い予約が永久に残ることを防げる.
C言語とVerse言語では,排他制御を実現する方法が異なります.
特に,Verse言語では共有状態の確認と更新を中断のない処理として実装することが重要です.
この仕組みを理解することで,複数のNPCが同時に動作するゲームでも,安全に共有資源を管理できるようになります!
Unreal Editor for Fortnite(UEFN)で開発した,私のゲームをプレイしたい方は,以下の記事をご覧ください.
-

-
【無料】私がUnreal Editor for Fortnite(UEFN)で開発した,フォートナイトで遊べるゲーム
こういった私から学べます. 私がUnreal Editor for Fortnite(UEFN)で開発した,フォートナイトで遊べる無料ゲームを紹介します. 是非,私のゲームで遊びましょう! まずはUE ...
続きを見る
Verse言語を学びたい方は以下を読みましょう.






