食事する哲学者 6/3 6/10 === 19:50まで - [x] デッドロック回避方法 - [ ] UML / Promelaのモデル - [ ] UML - [ ] Promela - モデル検査の実施内容 - デッドロックの検知のみ - 動作デモ - [ ] javascriptをデモ Promelaはコードのみ説明 - 苦労したこと、学んだこと -  --- 他の観点でのテスト - 公平性 - それぞれの哲学者が最低1回たべられること - ライブロック - 拡張性 - リアルタイムの哲学者増減 - - 連続運転 - モデル検査で不安をとれる - 他の検査について考える - 検査方法をindex.jsに追加 - 通信系・組み込み系 - ディレイがある - モデルの詳細化が必要になる - クリアの仕方を考える - この演習では通信の間でなにかあるかも - モデル検査が実際の現場でつかわれるか - 沢山はないが、設計モデル検証で事例を紹介 - AWSでの事例も公開されている - モデル検査よりよい方法はある? 1. テクニック的にはツール周り使いやすくなっている - CPUパワーの向上によるもの - 状態数を増やせる 2. 勉強によってツールを使わなくても気づきが得られるようになる - 考え方が身につく - 東証の改修対象システムの検査 - 石川先生 - モデル検査に精通する人のテストは? - 並列/平行の観点でテストしてくれる - プログラマが嫌がるポイントを押さえたテストができる - パラレルの違い? - 平行と並列の違いは - マルチコア(パラレル) - 真の並列操作のため、バグがふえた - - シングルコア(コンカレント) - コンテキストスイッチ(プログラムの切り替え)の発生 - 真の意味での並列動作はしない 進行性検査 ![](https://i.imgur.com/9CJjfc0.png) 1 /* 2 * TOPSE-MC-20XX 3 * Dining Philosophers 4 */ 5 #define PHIL_NUM (2) 6 mtype = {THINKING, HUNGRY, EATING}; /* 哲学者:思考中, 空腹, 食事中 */ 7 mtype = {FORK_IN_USE, FORK_VACANT}; /* フォーク:フォーク使用中, フォーク未使用 */ 8 mtype = {HAND_HOLDING, HAND_EMPTY}; /* 手:(フォークを)握っている, 空 */ 9 mtype fork[PHIL_NUM]; /* フォークの状態 */ 10 proctype Philosopher(int no) { 11 mtype phil = THINKING; /* 哲学者の状態の初期化 */ 12 mtype hand_r = HAND_EMPTY; /* 右手状態の初期化 */ 13 mtype hand_l = HAND_EMPTY; /* 左手状態の初期化 */ 14 do 15 ::(phil == THINKING) -> /* 思考中 */ 16 if 17 ::true -> phil = HUNGRY; 18 ::true -> skip; 19 fi; 20 ::(phil == HUNGRY) -> /* 空腹 */ 21 if 22 ::(fork[no % PHIL_NUM] == FORK_VACANT) -> 23 hand_r = HAND_HOLDING; 24 fork[no % PHIL_NUM] = FORK_IN_USE; 25 fi; 26 if 27 ::(fork[(no + 1) % PHIL_NUM] == FORK_VACANT) -> 28 hand_l = HAND_HOLDING; 29 fork[(no + 1) % PHIL_NUM] = FORK_IN_USE; 30 phil = EATING; 31 :: else -> 32 hand_r = HAND_EMPTY; 33 fork[no % PHIL_NUM] = FORK_VACANT; 34 fi; 35 36 ::(phil == EATING) -> /* 食事中 */ 37 if 38 ::true -> hand_r = HAND_EMPTY; 39 progress: 40 hand_l = HAND_EMPTY; 41 fork[no % PHIL_NUM] = FORK_VACANT; 42 fork[(no + 1) % PHIL_NUM] = FORK_VACANT; 43 phil = THINKING; 44 ::true -> skip; 45 fi; 46 ::true -> skip; 47 od; 48 } 49 init { 50 int i; 51 for(i in fork) { 52 fork[i] = FORK_VACANT; 53 } 54 atomic { 55 for(i in fork) { 56 run Philosopher(i); 57 } 58 } 59 } --- 正解 /* * TOPSE-MC-20XX * Dining Philosophers */ #define PHIL_NUM (2) mtype = {THINKING, HUNGRY, EATING}; /* 哲学者:思考中, 空腹, 食事中 */ mtype = {FORK_IN_USE, FORK_VACANT}; /* フォーク:フォーク使用中, フォーク未使用 */ mtype = {HAND_HOLDING, HAND_EMPTY}; /* 手:(フォークを)握っている, 空 */ mtype fork[PHIL_NUM]; /* フォークの状態 */ proctype Philosopher(int no) { mtype phil = THINKING; /* 哲学者の状態の初期化 */ mtype hand_r = HAND_EMPTY; /* 右手状態の初期化 */ mtype hand_l = HAND_EMPTY; /* 左手状態の初期化 */ do ::(phil == THINKING) -> /* 思考中 */ if ::true -> phil = HUNGRY; ::true -> skip; fi; ::(phil == HUNGRY) -> /* 空腹 */ if ::(fork[no % PHIL_NUM] == FORK_VACANT) -> hand_r = HAND_HOLDING; fork[no % PHIL_NUM] = FORK_IN_USE; fi; if ::(fork[(no + 1) % PHIL_NUM] == FORK_VACANT) -> hand_l = HAND_HOLDING; fork[(no + 1) % PHIL_NUM] = FORK_IN_USE; phil = EATING; :: else -> hand_r = HAND_EMPTY; fork[no % PHIL_NUM] = FORK_VACANT; fi; ::(phil == EATING) -> /* 食事中 */ if ::true -> hand_r = HAND_EMPTY; hand_l = HAND_EMPTY; fork[no % PHIL_NUM] = FORK_VACANT; fork[(no + 1) % PHIL_NUM] = FORK_VACANT; phil = THINKING; ::true -> skip; fi; ::true -> skip; od; } init { int i; for(i in fork) { fork[i] = FORK_VACANT; } atomic { for(i in fork) { run Philosopher(i); } } } --- /** * モデル検査入門Ⅱ Spin演習用プログラム * @file 食事する哲学者問題の食事する哲学者 * @author M. Usami */ const redis = require('redis'); const util = require('util'); const crypto = require('crypto'); /** redisクライアント * @constant * @default */ const client = redis.createClient(); /** * redisクライアント同期処理向け関数定義 */ client.getAsync = util.promisify(client.get); client.setAsync = util.promisify(client.set); client.setnxAsync = util.promisify(client.setnx); client.delAsync = util.promisify(client.del); /** 右手 * @constant * @type {Boolean} * @default */ const RIGHT = true; /** 左手 * @constant * @type {Boolean} * @default */ const LEFT = false; /** 右手 * @constant * @type {String} * @default */ const RIGHT_HAND = 'Right Hand'; /** 左手 * @constant * @type {String} * @default */ const LEFT_HAND = 'Left Hand'; const STATUS = { THINKING: 0, HUNGRY: 1, EATING: 2, }; let countEating = 0 /** * 同期スリープ関数 * await sleep(xxx) とawaitとセットで使用する * @param {Number} msec スリープする時間(ミリ秒) * @returns Promise */ const sleep = (msec) => new Promise(resolve => setTimeout(resolve, msec)); /** * 引数の取得処理 * 引数が不正の場合はUsageを表示してプログラムを終了する * @returns no,max no=哲学者の番号 max=哲学者の人数 */ const init = () => { let no = -1; let max = -1; if (process.argv.length >= 4) { no = Number(process.argv[2]); max = Number(process.argv[3]); console.log(`Philosopher No:${no} Number of philosophers:${max}\n`); if ((no < max) && (no >= 0 && no < 5) && (max > 1 && max <= 5)) { return { no, max }; } } console.log("Usage: node index.js [no] [max]"); console.log(" no: Philosopher No (0 ~ 4)"); console.log(" max: Number of philosophers (2 ~ 5)"); process.exit(1); } /** * RedisのKey-Valueを利用したロック処理 * @param {Number} no 哲学者No. * @param {Number} val ロックのための乱数 * @returns val:成功 -1:失敗 */ const lock = async (no, val) => { return await client.setnxAsync(`lock-${no}`, `${val}`) .then((val)=>{return val;}) .catch((err)=>{return -1;}); } /** * RedisKey-Valueを利用したアンロック処理 * @param {Number} no 哲学者No. * @param {Number} val ロックのための乱数 * @returns 1:成功 -1:失敗 */ const unlock = async (no, val) => { if (val === await client.getAsync(`lock-${no}`)) { return await client.delAsync(`lock-${no}`); } return -1; } /** * プログラムのシャットダウン処理 * 使用したRedis内のKeyを全て削除する * 3秒後に全てのキーを削除し、5秒後にプロセスを終了 */ const shutdown = () => { Philosopher.doing = false; console.log("Start shutdown process"); for(let i = 0; i < 5; i++) { setTimeout(() => { client.del(`lock-${i}`); }, 3000); } setTimeout(() => { client.quit(); console.log("Completed shutdown process"); process.exit(0) }, 5000); } /** * 終了処理 SIGINT */ process.on('SIGINT', () => { shutdown(); }); /** * 終了処理 SIGTERM */ process.on('SIGTERM', () => { shutdown(); }); /** * 腕(手) */ class Hand { /** ロック状態チェック周期時間(ミリ秒) * @static */ static INTERVAL = 10; /** * コンストラクタ * @param {Number} no 哲学者No. * @param {Number} max 哲学者の人数 * @param {Boolean} side true=右手/false=左手 */ constructor(no, max, side) { this.side = side; // true=右手, false=左手 this.rnd = 0; if (side === RIGHT) { this.no = no % max; this.str = RIGHT_HAND; } else { this.no = (no + 1) % max; this.str = LEFT_HAND; } } /** * フォークを1度だけ取得し、結果を返す * @param {*} status * @returns boolean | undefined */ async getOnce(status){ this.rnd = crypto.randomBytes(8).toString("hex"); if(status) { return 1 === await lock(this.no, this.rnd) } } /** * フォーク取得 * @param {動作状態} status true=実行中/false=終了処理中 */ async get(status) { this.rnd = crypto.randomBytes(8).toString("hex"); while(status) { if (1 === await lock(this.no, this.rnd)) { console.log(` ${this.str} Fork acquisition => Succeeded : no=${this.no}`); return; } console.log(` ${this.str} Fork acquisition => Failed : no=${this.no}`); await sleep(Hand.INTERVAL); } } /** * フォーク解放 * @param {動作状態} status true=実行中/false=終了処理中 */ async release(status) { while(status) { if (1 === await unlock(this.no, this.rnd)) { console.log(` ${this.str} Fork release => Succeeded : no=${this.no}`); return; } console.log(` ${this.str} Fork release => Failed : no=${this.no}`); await sleep(Hand.INTERVAL); } } } /** * 食事する哲学者クラス */ class Philosopher { /** 動作状態(true=実行中/false=終了処理中) * @static */ static doing = true; /** * コンストラクタ * @param {Number} no 哲学者No. * @param {Number} max 哲学者の人数 */ constructor(no, max) { this.no = no; this.max = max; this.status = STATUS.THINKING; this.handRight = new Hand(no, max, RIGHT); this.handLeft = new Hand(no, max, LEFT); } /** * 思考中処理 */ async think() { console.log(`Philosopher ${this.no} Thinking`); await sleep(crypto.randomInt(20) ); this.status = STATUS.HUNGRY; } /** * 空腹中処理 */ async hungry() { console.log(`Philosopher ${this.no} Hungry`); await sleep(crypto.randomInt(20) ); // if (Math.random() > 0.5) { // await this.handRight.get(Philosopher.doing); // await this.handLeft.get(Philosopher.doing); // } // else { // await this.handLeft.get(Philosopher.doing); // await this.handRight.get(Philosopher.doing); // } let successR = await this.handRight.getOnce(Philosopher.doing); if(successR){ let successL = await this.handLeft.getOnce(Philosopher.doing); if(successL){ this.status = STATUS.EATING; }else{ await this.handRight.release(Philosopher.doing); } } } /** * 食事中処理 */ async eat() { console.log(`Philosopher ${this.no} Eating ${countEating++}`); await sleep(crypto.randomInt(20) ); await this.handRight.release(Philosopher.doing); await this.handLeft.release(Philosopher.doing); // if (Math.random() > 0.5) { // await this.handRight.release(Philosopher.doing); // await this.handLeft.release(Philosopher.doing); // } // else { // await this.handLeft.release(Philosopher.doing); // await this.handRight.release(Philosopher.doing); // } this.status = STATUS.THINKING; } /** * 食事処理 * @param {Number} no 哲学者No. * @param {Number} max 哲学者の人数 */ async main() { while(Philosopher.doing) { switch(this.status) { case STATUS.THINKING: await this.think(); break; case STATUS.HUNGRY: await this.hungry(); break; case STATUS.EATING: await this.eat(); break; } } } }; let { no, max } = init(); phil = new Philosopher(no, max); phil.main(no, max);