여러 개의 실행중인 스레드를 어떻게 만들 수 있습니까?


59

동일한 코드 블록에서 여러 작업을 수행하지 않고 프로그램의 여러 부분을 함께 실행할 수있는 방법이 있습니까?

하나의 스레드가 외부 장치를 기다리는 동안 다른 스레드의 LED도 깜박입니다.


3
실제로 스레드가 필요한지 먼저 물어보십시오. 타이머는 이미 귀하의 요구에 적합 할 수 있으며 Arduino에서 기본적으로 지원됩니다.
jfpoilpret

1
Uzebox도 확인하고 싶을 수도 있습니다. 2 칩 홈 브루 비디오 게임 콘솔입니다. 따라서 Arduino는 아니지만 전체 시스템은 인터럽트를 기반으로합니다. 따라서 오디오, 비디오, 컨트롤 등은 모두 인터럽트로 구동되며 메인 프로그램은 걱정할 필요가 없습니다. 좋은 참조가 될 수 있습니다.
cbmeeks

답변:


50

Arduino에서는 다중 프로세스 또는 다중 스레딩이 지원되지 않습니다. 일부 소프트웨어를 사용하면 여러 스레드에 가까운 것을 수행 할 수 있습니다.

Protothreads 를보고 싶습니다 :

프로토스 레드는 소형 임베디드 시스템 또는 무선 센서 네트워크 노드와 같이 메모리 제약이 심한 시스템을 위해 설계된 초경량 스택리스 스레드입니다. 프로토스 레드는 C로 구현 된 이벤트 중심 시스템에 선형 코드 실행을 제공합니다. 프로토스 레드는 기본 운영 체제와 함께 또는 이벤트 핸들러를 차단하기 위해 기본 운영 체제와 함께 사용될 수 있습니다. 프로토스 레드는 복잡한 상태 머신이나 완전한 멀티 스레딩없이 순차적 인 제어 흐름을 제공합니다.

물론 여기code 예제 와 함께 Arduino 예제가 있습니다 . 이 SO 질문 도 유용 할 수 있습니다.

ArduinoThread 도 좋습니다.


Arduino DUE에는 여러 제어 루프가 포함 된 예외가 있습니다. arduino.cc/en/Tutorial/MultipleBlinks
tuskiomi

18

AVR 기반 Arduino는 (하드웨어) 스레딩을 지원하지 않습니다. ARM 기반 Arduino에 익숙하지 않습니다. 이 제한을 해결하는 한 가지 방법은 인터럽트, 특히 시간 인터럽트를 사용하는 것입니다. 타이머를 프로그래밍하여 너무 많은 마이크로 초마다 주 루틴을 중단하고 특정 다른 루틴을 실행할 수 있습니다.

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

9

요구 사항에 대한 설명에 따라 :

  • 외부 장치를 기다리는 하나의 스레드
  • 스레드 하나가 LED를 깜박임

첫 번째 "스레드"에 하나의 Arduino 인터럽트를 사용할 수있는 것 같습니다 (실제로는 "태스크"라고합니다).

Arduino 인터럽트는 외부 이벤트 (디지털 입력 핀의 전압 레벨 또는 레벨 변경)에 따라 하나의 기능 (코드)을 호출 할 수 있으며, 이로 인해 기능이 즉시 트리거됩니다.

그러나 인터럽트를 염두에 두어야 할 중요한 점은 호출 된 함수가 가능한 한 빨라야한다는 것입니다 (일반적으로 delay()호출 또는에 의존하는 다른 API 가 없어야 함 delay()).

외부 이벤트 트리거시 활성화해야 할 작업이 긴 경우 협력 스케줄러를 사용하여 인터럽트 기능에서 새 작업을 추가 할 수 있습니다.

인터럽트에 대한 두 번째 중요한 점은 그 수가 제한되어 있다는 것입니다 (예 : UNO에서는 2). 따라서 더 많은 외부 이벤트가 발생하기 시작하면 모든 입력을 하나로 멀티플렉싱하는 일종의 구현을 수행하고 인터럽트 함수가 멀티플 렉트 된 inut가 실제 트리거인지 판별해야합니다.


6

간단한 해결책은 스케줄러 를 사용하는 것 입니다. 몇 가지 구현이 있습니다. AVR 및 SAM 기반 보드에 사용할 수있는 것을 간략하게 설명합니다. 기본적으로 단일 통화는 작업을 시작합니다. "스케치 내 스케치".

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

Scheduler.start ()는 taskSetup을 한 번 실행 한 다음 Arduino 스케치가 작동하는 것처럼 반복적으로 taskLoop를 호출하는 새 작업을 추가합니다. 작업에는 자체 스택이 있습니다. 스택의 크기는 선택적 매개 변수입니다. 기본 스택 크기는 128 바이트입니다.

컨텍스트 전환을 허용하려면 작업이 yield () 또는 delay () 을 호출해야합니다 . 조건을 기다리는 데 필요한 지원 매크로도 있습니다.

await(Serial.available());

매크로는 다음에 대한 구문 설탕입니다.

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

대기는 또한 작업을 동기화하는 데 사용될 수 있습니다. 아래는 스 니펫 예제입니다.

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 () 함수와 타이머 인터럽트를 사용하여 폴링 스케줄러를 구축 할 수 있습니다.

Bill Earl의 다음 기사를 제안합니다.

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/

yielding () 또는 delay ()에서 릴레이하지 않고 컨텍스트 전환을 허용하기 위해 인터럽트 스케줄러를 사용합니다.

세 개의 스레드가 필요했기 때문에 라이브러리를 만들었고 다른 스레드가 무엇을 하든지 정확한 시간에 실행하려면 두 개가 필요했습니다. 첫 번째 스레드는 직렬 통신을 처리했습니다. 두 번째는 Eigen 라이브러리와 함께 float 행렬 곱셈을 사용하여 Kalman 필터를 실행했습니다. 세 번째는 고속 전류 제어 루프 스레드로 매트릭스 계산을 중단 할 수 있어야했습니다.

작동 원리

각 순환 스레드에는 우선 순위와 기간이 있습니다. 현재 실행 스레드보다 우선 순위가 높은 스레드가 다음 실행 시간에 도달하면 스케줄러가 현재 스레드를 일시 정지하고 우선 순위가 높은 스레드로 전환합니다. 우선 순위가 높은 스레드가 실행을 완료하면 스케줄러가 이전 스레드로 다시 전환됩니다.

스케줄링 규칙

ThreadHandler 라이브러리의 스케줄링 체계는 다음과 같습니다.

  1. 우선 순위가 가장 높습니다.
  2. 우선 순위가 동일하면 마감일이 가장 빠른 스레드가 먼저 실행됩니다.
  3. 두 스레드의 마감일이 동일하면 처음 작성된 스레드가 먼저 실행됩니다.
  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

그리고 여기 또 다른 마이크로 프로세서 협업 멀티 태스킹 라이브러리 – 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());
}

이것들은 "완료 완료"작업입니까?
Edgar Bonet

@ EdgarBonet 나는 당신이 무슨 뜻인지 잘 모르겠습니다. run()메소드가 호출 된 후에는 중단되지 않으므로 합리적으로 신속하게 완료해야합니다. 그러나 일반적으로 작업을 수행 한 다음 나중에 일정 LoopTask기간 동안 하위 클래스의 경우 자동으로 일정을 조정합니다 . 일반적인 패턴은 작업이 일부 내부 상태 머신 (사소한 예는 light_on_p_위 의 상태 임)을 유지하여 다음에 올 때 적절하게 작동하도록하는 것입니다.
노먼 그레이

따라서 그렇습니다. RtC (Run-to-Completion) 작업입니다 run(). 이 협동하여 CPU를 얻을 수있는 스레드, 예를 들어, 전화와 대조적이다 yield()delay(). 또는 선점 형 스레드는 언제든지 예약 할 수 있습니다. 나는 스레드를 찾는 많은 사람들이 상태 머신보다는 블로킹 코드 작성을 선호하기 때문에 그렇게하는 것을 보았으므로 구별이 중요하다고 생각합니다. CPU를 생성하는 실제 스레드를 차단하는 것이 좋습니다. RtC 작업 차단은 아닙니다.
Edgar Bonet

@EdgarBonet 유용한 차이점입니다. 예. 이 스타일과 yield 스타일 스레드를 선점 스레드가 아닌 단순히 다른 스타일의 협동 스레드로 간주하지만 코딩에 다른 접근 방식이 필요하다는 것은 사실입니다. 여기에 언급 된 다양한 접근 방식을 신중하고 깊이 비교하는 것이 흥미로울 것입니다. 위에서 언급하지 않은 멋진 라이브러리 중 하나는 프로토스 레드 입니다. 나는 둘 다 비판 할뿐만 아니라 칭찬 할 것도 있습니다. 가장 당연해 보이고 추가 스택이 필요하지 않기 때문에 (물론) 내 접근 방식을 선호합니다.
노먼 그레이

(보정 : @ sachleen 's answer 에서 protothreads 언급되었습니다 )
Norman Grey
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.