実行中の複数のスレッドを作成するにはどうすればよいですか?


60

同じコードブロックで複数のことを行わずに、プログラムの複数の部分を一緒に実行する方法はありますか?

1つのスレッドが外部デバイスを待っている間、別のスレッドのLEDも点滅します。


3
おそらく、スレッドが本当に必要かどうかを最初に自問するべきでしょう。タイマーは既にニーズに対応している場合があり、Arduinoでネイティブにサポートされています。
jfpoilpret 14

1
Uzeboxもチェックしてみてください。自作の2チップビデオゲームコンソールです。したがって、Arduinoそのものではありませんが、システム全体は割り込みに基づいて構築されています。そのため、オーディオ、ビデオ、コントロールなどはすべて割り込み駆動であり、メインプログラムはそれについて心配する必要はありません。参考になるかもしれません。
cbmeeks

回答:


50

Arduinoでは、マルチプロセスもマルチスレッドもサポートされていません。ただし、いくつかのソフトウェアを使用すると、複数のスレッドに近いものを実行できます。

あなたは見てみたいProtothreads

プロトスレッドは、小型の組み込みシステムやワイヤレスセンサーネットワークノードなど、メモリの制約が厳しいシステム向けに設計された非常に軽量なスタックレススレッドです。Protothreadsは、Cで実装されたイベント駆動型システムに対して線形コード実行を提供します。Protothreadsは、基盤となるオペレーティングシステムの有無にかかわらず、イベントハンドラーをブロックするために使用できます。プロトスレッドは、複雑なステートマシンや完全なマルチスレッドを使用せずに、連続した制御フローを提供します。

もちろん、ここサンプルコードを含むArduinoの例があります。このSOの質問も役に立つかもしれません。

ArduinoThreadも良いものです。


アルドゥイーノDUEは、複数の制御ループと、この例外があることに注意してください:arduino.cc/en/Tutorial/MultipleBlinks
tuskiomi

18

AVRベースのArduinoは(ハードウェア)スレッドをサポートしていません。ARMベースのArduinoには慣れていません。この制限を回避する1つの方法は、割り込み、特に時限割り込みの使用です。特定のその他のルーチンを実行するために、非常に多くのマイクロ秒ごとにメインルーチンを中断するタイマーをプログラムできます。

http://arduino.cc/en/Reference/Interrupts


15

Unoでソフトウェア側のマルチスレッドを実行することは可能です。ハードウェアレベルのスレッドはサポートされていません。

マルチスレッドを実現するには、基本的なスケジューラの実装と、実行する必要のあるさまざまなタスクを追跡するためのプロセスまたはタスクリストの維持が必要です。

非常に単純な非プリエンプティブスケジューラの構造は次のようになります。

//Pseudocode
void loop()
{

for(i=o; i<n; i++) 
run(tasklist[i] for timelimit):

}

ここでtasklistは、関数ポインタの配列にすることができます。

tasklist [] = {function1, function2, function3, ...}

次の形式の各関数で:

int function1(long time_available)
{
   top:
   //Do short task
   if (run_time<time_available)
   goto top;
}

各関数は、function1LED操作の実行やfunction2フロート計算の実行など、個別のタスクを実行できます。割り当てられた時間を遵守することは、各タスク(機能)の責任です。

うまくいけば、これで開始できます。


2
非プリエンプティブスケジューラを使用するときに「スレッド」について話すかどうかはわかりません。ところで、そのようなスケジューラーはすでにarduinoライブラリーとして存在しています:arduino.cc/en/Reference/Scheduler
jfpoilpret

5
@jfpoilpret-協調型マルチスレッドは本物です。
コナーウルフ

はい、あなたが正しい!私の間違い; ずっと前から協調マルチスレッドに直面していなかったので、私の考えでは、マルチスレッドはプリエンプティブでなければなりませんでした。
jfpoilpret 14

9

要件の説明に従って:

  • 外部デバイスを待機する1つのスレッド
  • 1つのスレッドがLEDを点滅

最初の「スレッド」に対して1つのArduino割り込みを使用できるようです(実際には「タスク」と呼びます)。

Arduino割り込みは、外部イベント(デジタル入力ピンの電圧レベルまたはレベル変化)に基づいて1つの関数(コード)を呼び出すことができます。これにより、すぐに関数がトリガーされます。

ただし、割り込みに関して留意すべき重要な点の1つは、呼び出される関数が可能な限り高速であることです(通常、delay()呼び出しやに依存する他のAPI はありませんdelay())。

外部イベントトリガーでアクティブにする長いタスクがある場合は、潜在的に協調スケジューラを使用し、割り込み関数から新しいタスクを追加することができます。

割り込みに関する2番目の重要な点は、その数が制限されていることです(たとえば、UNOで2つのみ)。したがって、より多くの外部イベントの処理を開始する場合は、すべての入力を1つに多重化する何らかの種類を実装し、実際のトリガーである多重化された入力を割り込み関数に決定させる必要があります。


6

簡単な解決策は、スケジューラを使用することです。いくつかの実装があります。これは、AVRおよびSAMベースのボードで利用可能なものを簡単に説明しています。基本的に、1回の呼び出しでタスクが開始されます。「スケッチ内のスケッチ」。

#include <Scheduler.h>
....
void setup()
{
  ...
  Scheduler.start(taskSetup, taskLoop);
}

Scheduler.start()は、taskSetupを1回実行する新しいタスクを追加し、Arduinoスケッチが機能するようにtaskLoopを繰り返し呼び出します。タスクには独自のスタックがあります。スタックのサイズはオプションのパラメーターです。デフォルトのスタックサイズは128バイトです。

コンテキストの切り替えを許可するには、タスクはyield()またはdelay()を呼び出す必要があります。条件を待機するためのサポートマクロもあります。

await(Serial.available());

マクロは、次の構文糖衣です:

while (!(Serial.available())) yield();

Awaitは、タスクの同期にも使用できます。以下にスニペットの例を示します。

volatile int taskEvent = 0;
#define signal(evt) do { await(taskEvent == 0); taskEvent = evt; } while (0)
...
void taskLoop()
{
  await(taskEvent);
  switch (taskEvent) {
  case 1: 
  ...
  }
  taskEvent = 0;
}
...
void loop()
{
  ...
  signal(1);
}

詳細については、を参照してください。複数のLED点滅からデバウンスボタンまでの例と、ノンブロッキングコマンドライン読み取りのある単純なシェルがあります。テンプレートと名前空間を使用して、ソースコードの構造化と削減に役立てることができます。以下のスケッチは、マルチ点滅用のテンプレート関数の使用方法を示しています。スタックには64バイトで十分です。

#include <Scheduler.h>

template<int pin> void setupBlink()
{
  pinMode(pin, OUTPUT);
}

template<int pin, unsigned int ms> void loopBlink()
{
  digitalWrite(pin, HIGH);
  delay(ms);
  digitalWrite(pin, LOW);
  delay(ms);
}

void setup()
{
  Scheduler.start(setupBlink<11>, loopBlink<11,500>, 64);
  Scheduler.start(setupBlink<12>, loopBlink<12,250>, 64);
  Scheduler.start(setupBlink<13>, loopBlink<13,1000>, 64);
}

void loop()
{
  yield();
}

また、パフォーマンス、つまりタスクを開始する時間、コンテキストの切り替えなどを把握するためのベンチマークもあります

最後に、タスクレベルの同期と通信のサポートクラスがいくつかあります。キューセマフォ


3

このフォーラムの以前の呪文から、次の質問/回答は電気工学に移されました。メインループを使用してシリアルIOを実行している間に、タイマー割り込みを使用してLEDを点滅させるサンプルのarduinoコードがあります。

https://electronics.stackexchange.com/questions/67089/how-can-i-control-things-without-using-delay/67091#67091

再投稿:

割り込みは、何かが進行している間に物事を成し遂げるための一般的な方法です。次の例では、を使用せずにLEDが点滅していdelay()ます。Timer1起動するたびに、割り込みサービスルーチン(ISR)isrBlinker()が呼び出されます。LEDのオン/オフを切り替えます。

他のことが同時に発生する可能性があることを示すためloop()に、LEDの点滅に関係なく、foo / barをシリアルポートに繰り返し書き込みます。

#include "TimerOne.h"

int led = 13;

void isrBlinker()
{
  static bool on = false;
  digitalWrite( led, on ? HIGH : LOW );
  on = !on;
}

void setup() {                
  Serial.begin(9600);
  Serial.flush();
  Serial.println("Serial initialized");

  pinMode(led, OUTPUT);

  // initialize the ISR blinker
  Timer1.initialize(1000000);
  Timer1.attachInterrupt( isrBlinker );
}

void loop() {
  Serial.println("foo");
  delay(1000);
  Serial.println("bar");
  delay(1000);
}

これは非常にシンプルなデモです。ISRははるかに複雑で、タイマーと外部イベント(ピン)によってトリガーできます。共通ライブラリの多くは、ISRを使用して実装されます。


3

マトリックスLEDディスプレイを実装しているときにも、このトピックに取り組みました。

一言で言えば、Arduinoでmillis()関数とタイマー割り込みを使用して、ポーリングスケジューラを構築できます。

ビルアールの次の記事をお勧めします。

https://learn.adafruit.com/multi-tasking-the-arduino-part-1/overview

https://learn.adafruit.com/multi-tasking-the-arduino-part-2/overview

https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview


2

私のThreadHandlerライブラリを試してみることもできます

https://bitbucket.org/adamb3_14/threadhandler/src/master/

割り込みスケジューラを使用して、yield()またはdelay()で中継せずにコンテキストを切り替えることができます。

ライブラリを作成したのは、3つのスレッドが必要であり、他のスレッドが何をしていても正確に実行するには2つのスレッドが必要だったからです。最初のスレッドはシリアル通信を処理しました。2つ目は、Eigenライブラリでフロート行列乗算を使用してカルマンフィルターを実行していました。そして、3番目は高速な電流制御ループスレッドであり、マトリックス計算を中断できる必要がありました。

使い方

各周期スレッドには、優先順位と期間があります。現在実行中のスレッドよりも優先度の高いスレッドが次の実行時間に達すると、スケジューラは現在のスレッドを一時停止し、優先度の高いスレッドに切り替えます。優先度の高いスレッドの実行が完了すると、スケジューラは前のスレッドに戻ります。

スケジューリング規則

ThreadHandlerライブラリのスケジューリングスキームは次のとおりです。

  1. 最も高い優先度が最初です。
  2. 優先度が同じ場合、最も早い期限のスレッドが最初に実行されます。
  3. 2つのスレッドの期限が同じ場合、最初に作成されたスレッドが最初に実行されます。
  4. スレッドは、優先度の高いスレッドによってのみ中断できます。
  5. スレッドが実行されると、run関数が戻るまで、優先順位の低いすべてのスレッドの実行がブロックされます。
  6. ループ関数の優先度は、ThreadHandlerスレッドと比較して-128です。

使い方

スレッドはC ++継承を介して作成できます

class MyThread : public Thread
{
public:
    MyThread() : Thread(priority, period, offset){}

    virtual ~MyThread(){}

    virtual void run()
    {
        //code to run
    }
};

MyThread* threadObj = new MyThread();

またはcreateThreadおよびラムダ関数を介して

Thread* myThread = createThread(priority, period, offset,
    []()
    {
        //code to run
    });

スレッドオブジェクトは、作成時にThreadHandlerに自動的に接続します。

作成されたスレッドオブジェクトの実行を開始するには、以下を呼び出します。

ThreadHandler::getInstance()->enableThreadExecution();

1

そして、もう1つのマイクロプロセッサ協調マルチタスクライブラリであるPQRSTがあります。これは、単純なタスクを実行するための優先度キューです。

このモデルでは、スレッドはのサブクラスとして実装されますTask。これは将来の時間にスケジュールされます(一般的には、LoopTask代わりにサブクラスになる場合は、定期的に再スケジュールされます)。run()タスクの期限が切れると、オブジェクトのメソッドが呼び出されます。run()この方法は、いくつかの原因作業を行い、その後(これは協調ビットである)を返します。通常、連続した呼び出しでのアクションを管理するために、ある種のステートマシンを維持します(簡単な例は、light_on_p_以下の例の変数です)。コードの編成方法を少し考え直す必要がありますが、かなり集中的な使用で非常に柔軟で堅牢であることが実証されています。

それは時間単位についてとらわれないですので、単位で実行されているように幸せであるmillis()ようにmicros()、または便利である任意の他のダニ。

このライブラリを使用して実装された「点滅」プログラムを次に示します。これは、実行中の単一のタスクのみを示していますsetup()。通常、他のタスクが作成され、内で開始されます。

#include "pqrst.h"

class BlinkTask : public LoopTask {
private:
    int my_pin_;
    bool light_on_p_;
public:
    BlinkTask(int pin, ms_t cadence);
    void run(ms_t) override;
};

BlinkTask::BlinkTask(int pin, ms_t cadence)
    : LoopTask(cadence),
      my_pin_(pin),
      light_on_p_(false)
{
    // empty
}
void BlinkTask::run(ms_t t)
{
    // toggle the LED state every time we are called
    light_on_p_ = !light_on_p_;
    digitalWrite(my_pin_, light_on_p_);
}

// flash the built-in LED at a 500ms cadence
BlinkTask flasher(LED_BUILTIN, 500);

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);
    flasher.start(2000);  // start after 2000ms (=2s)
}

void loop()
{
    Queue.run_ready(millis());
}

これらは「実行完了」タスクですよね?
エドガーボネット

@EdgarBonet私はあなたが何を意味するのかよく分かりません。run()メソッドが呼び出された後、メソッドは中断されないため、合理的に迅速に終了する責任があります。ただし、通常は、作業を実行してから、LoopTask将来の時間のために(おそらくサブクラスの場合は自動的に)スケジュールを変更します。一般的なパターンは、タスクlight_on_p_が次の期日になると適切に動作するように、内部状態マシンを維持することです(簡単な例は上記の状態です)。
ノーマングレイ

はい、そうです、これらはrun-to-completion(RtC)タスクです。現在のタスクがから戻って実行を完了するまで、タスクは実行できませんrun()。これは、たとえばyield()またはを呼び出すことでCPUを譲ることができる協調スレッドとは対照的delay()です。または、プリエンプティブスレッド。いつでもスケジュールできます。スレッドを検索するためにここにやってくる多くの人々が、ステートマシンよりもブロッキングコードを書くことを好むため、スレッドを検索する多くの人がそうしているのを見たように、区別は重要だと感じています。CPUを生成する実際のスレッドをブロックしても問題ありません。RtCタスクをブロックすることはできません。
エドガーボネット

@EdgarBonetそれは便利な区別です、はい。プリエンプティブスレッドとは対照的に、このスタイルとyieldスタイルスレッドの両方を単に協調スレッドの異なるスタイルと見なしますが、コーディングには異なるアプローチが必要であることは事実です。ここで言及されているさまざまなアプローチの思慮深く詳細な比較を見ることは興味深いでしょう。上記で言及されていない素晴らしいライブラリの1つがprotothreadsです。両方で批判するだけでなく、賞賛することもあります。私は(もちろん)私のアプローチを好みます。なぜなら、それは最も明確に見え、余分なスタックを必要としないからです。
ノーマングレイ

(訂正:protothreadsをされた中で、言及したの@ sachleenの答え
ノーマングレー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.