(7)並列処理に関するトラブル集 [ 並列処理の不具合と対策 ]

 

並列処理動作に関する問題について、いくつかの例をあげてみます

 

 

1.割り込み処理の例

 

通常の組み込みソフトでは割り込み処理が多く使われますが、割り込み動作でも並列処理と同じ問題が起きることがあります。

の図の動作を説明します。

 

通常の処理関数(function1)が子関数(functionS)を呼出し、子関数の処理中に割り込みが発生します。その結果、子関数(functionS)の処理は中断します。
割り込み処理関数(interrupt1)が動き、同じ子関数(functionS)を呼出して処理を行います。
割り込み処理関数(interrupt1)が終了すると、通常処理から呼ばれて中断していた子関数(functionS)が①の処理の続きを行います。

 

ここで問題となるのは、途中まで動いていた子関数(functionS) が同時に2つ動作中となり、先に動いていた処理が後の処理に追い抜かれて、その後再開することです。

この子関数(functionS)が以下のような処理であった時、どうなるでしょうか。

 

int  functionS( int i )

{

  x = a + i;

  y = b + i;

  return  x + y;

 

}

a = 10, b = 20 であったとして、考えてみましょう。

 

通常の処理関数(function1) からの呼出しが functionS( 1 ) で、割り込み処理関数(interrupt1)からの呼出しが functionS( 2 ) だとします。途中で割り込みが入ったとする動作を示します。

図のように、通常関数からの戻り値は32になるはずですが、34になってしまいます。

この例の、functionS関数は再入可能な構造ではないので、誤った戻り値を返しますが、 x y の変数をオート変数に変更することで再入可能となり、同じように割り込み処理が動作しても正しい結果が得られます。また、functionS関数の一連の処理を割り込み禁止状態にすることでも回避できます。

 

int  functionS( int i )

{

 int  x, y;  // オート変数は関数動作毎に固有の領域に確保(複数動作時は別領域)される

    x = a + i;

    y = b + i;

    return  x + y;

      }

2.関連性のある変数に関する不十分な排他処理

 

共有変数の読み書きに排他処理を用いたとしても、関連する複数の変数を別々に排他処理すると、不整合な状態を作り出していまいます。

図において、「主情報」と「補足情報」がセットで意味を持つ情報である時、ひとつひとつの書き込み処理を排他処理したとしても、その間で別タスクから参照があれば、不整合な状態を参照することになります。

このような場合、2つの変数に対する書き込み処理区間をまとめて排他処理する必要があります。

 

また、このような場合、処理の内容によっては、参照側のタスクにも、2変数の参照処理間に書き込みが行われないよう排他処理が必要となることがあります。

3.同一共有変数の複数回参照

 

2つのタスクが動いている時に、片方のタスクのWRITEした変数をもう片方のタスクがREADする例を考えます。複数回のWRITEREADがあると、READしたタスクが得る値は同じとは限りません。

図のように、タスク1が複数回書き込む変数Aをタスク2の関数が2カ所でREADする時、その値は同じ値ではないかもしれません。その瞬間の値が必要であれば問題ありませんが、同じ値として処理したいのなら、一旦ローカル変数に移してから使用すべきです。

  void function()

  {

   int X = A;

 

 B = X & 0x01;

.

.

.

  if ( X )

 

 

 

  }

4.共有変数読み書きのタイミングに関する問題

 

2つのタスクが動いている時に、片方のタスクのWRITEした変数をもう片方のタスクがREADする例を考えます。連続してWRITEREADするタスクのタイミングに違いがある時、データの読み飛ばしや重ね読みが発生します。

読み飛ばしや重ね読みが起きることを想定した処理を行う。または、同期を合わせる処理を挿入する必要があります。

5.コンパイラの最適化に関する問題

 

ループ内で共有変数を参照する時、コンパイラの最適化の影響を受けることがあります。

コンパイラはループ処理を高速化するために、影響のない範囲で、ループ内の変数アクセスや演算処理をループ外へ移動することがあります。実際にプログラムを変更するのではなく、コンパイル時に解釈を変えてコンパイルするようなイメージです。

 

しかしこのような最適化が行われてしまうと、ループ内で変数Aを読まなくなってしまいます。これを回避するためには、変数A volatile(揮発性:代入しなくても値が変わる)属性であることを示す必要があります。

これと似た例として、タイマレジスタ情報を上位・下位に分けて読み込む際にHI LO HIと読んで上位の桁上りを確認することがあります。このような場合にも、コンパイラの最適化により最後のHIデータを実際には読まずに最初のHIデータの値で処理されてしまうような問題もあり、この場合もvolatile記述で回避できます。

 

 なお、本例のように前回値を扱う処理で見落としがちなのが、変数 ave の初期値への配慮です。 

ループ内で前回の値との平均を求めていますが、初回のループでは前回の値が保存されてないので平均値が得られません。このような処理では変数の初期値に気を付ける必要があります。