同期処理

俺にとっては 20 年以上前から知ってる常識なんですけど。稀に良くあるケースがあってさ、10 年選手でも同期処理を正しく理解してない人がいるんですよ。とはいっても、同期処理って言葉が良くないのかもしれないんですけど。VB やってる人なら常識的に知ってる知識として DoEvents ってのがあるんですよね。これってなんだよって言うと、メッセージループのことですよ要は。メッセージループって何って思うんでしょうね、分かってない人は。イベントドリブンのプログラムしかやったことがない人はメッセージループも知らないからな。例えば以下のような疑似コードがあったとします。

global static int age = 5;

DoEvents;

console.log(age); // output 5 or 10?

function eventHandlerForAge () {

 age = 10;

}

これでログ出力される値は 5 なんですか 10 なんですかって話ですね。上記の疑似コードが理解できない人もいるかもですが、そういう人は放置します。結論を言うと前提条件がなければ 5 なのか 10 なのか分からないってことになります。DoEvents ってのは前に書いたようにメッセージループです。メッセージループってのはイベントを処理するってことです。つまりイベントが溜まっていたら全て処理してしまいますってことになります。なのでイベントがあれば DoEvents で age は 10 になるし、なければ 5 のままってことです。

それがどうしたって感じとは思うんですけど。DoEvents ってのは最近の javascript で言うと、近い存在として await ってのがあるんですよ。この await ってのが async 関数もしくはモジュール内グローバル処理でしか使えないって制約があるんですよね。それってどういうことなんだってことで。ちょっと理解しがたいことを言うかもしれませんが、await ってのは処理を同期処理的に処理するためのものです。この同期処理的ってのが曲者で、確かにその関数内では同期処理的に動くんですけど、本質的には非同期処理なんですよ。その非同期処理の影響がどこにでるかって言うと、画面操作ですよね。本質的に非同期処理なので画面操作は普通にできてしまう。それがモジュール内グローバル処理の await と async 関数内の await であるわけです。同期関数内では await できないのは、そういう理由があってのことだと思ってます。

<html>
<head>
</head>
<body>
<script>

var b = 1;

function func1 () {
    b += 10000;
}

</script>
<script type="module">

setInterval(() => { b++;}, 1000);
await new Promise((resolve) => {
    setTimeout(() => { resolve();}, 5500);
});
console.log(b);

</script>
<button onclick="func1();">b += 10000</button>
</body>
</html>

このコードを動かすと 6 って出力されます。ボタンを押せば押した分だけ 10000 加算されます。これだけで await の本質は理解できると思うんですけど。await は完全な同期処理ではない。疑似的な同期処理でしかないんです。それが理解できない人が await しないように、同期関数内では await できなくなっているんですね。javascript 設計者は良く考えてると思います。ネットとか検索するとコイツ正気かってコード見かけますから。単なるサンプルや手抜きだと信じたいですけど。たとえば以下みたいなコードですよ。

a = await f1();

b = await f2();

c = await f3();

これ見て即座に「非同期処理の意味がねえ!」ってバカだと思いました。何かしら理由がある可能性もありますけど。f1 f2 f3 が相互に絡み合った処理であれば、そういう風に同期処理する必要はあると思うんですよ。たとえば f1 の結果を f2 が使うとかさ。