ฉันจะสร้างหลายเธรดที่กำลังรันอยู่ได้อย่างไร


59

มีวิธีที่ฉันสามารถมีหลายส่วนของโปรแกรมทำงานร่วมกันโดยไม่ต้องทำหลายสิ่งในบล็อกรหัสเดียวกันหรือไม่

หนึ่งเธรดกำลังรออุปกรณ์ภายนอกในขณะที่ยังกะพริบไฟ LED ในเธรดอื่น


3
คุณควรถามตัวเองก่อนว่าคุณต้องการเธรดหรือไม่ ตัวจับเวลาอาจไม่เป็นไรสำหรับความต้องการของคุณแล้วและพวกเขาได้รับการสนับสนุนใน Arduino
jfpoilpret

1
คุณอาจต้องการตรวจสอบ Uzebox ด้วย มันเป็นเกมคอนโซลโฮมวิดีโอชิปสองตัว ดังนั้นในขณะที่มันไม่ได้เป็น Arduino แน่นอนระบบทั้งหมดถูกสร้างขึ้นบนอินเทอร์รัปต์ ดังนั้นเสียงวิดีโอการควบคุมและอื่น ๆ ทั้งหมดจึงถูกขัดจังหวะในขณะที่โปรแกรมหลักไม่ต้องกังวลกับมัน อาจเป็นการอ้างอิงที่ดี
cbmeeks

คำตอบ:


50

ไม่มีหลายกระบวนการหรือสนับสนุนหลายเธรดใน Arduino คุณสามารถทำบางสิ่งบางอย่างที่ใกล้เคียงกับหลายเธรดด้วยซอฟต์แวร์บางอย่าง

คุณต้องการดูProtothreads :

Protothreads เป็นเธรดที่ไม่มีน้ำหนักเบามากที่ออกแบบมาสำหรับระบบที่ จำกัด หน่วยความจำอย่างรุนแรงเช่นระบบฝังตัวขนาดเล็กหรือโหนดเครือข่ายเซ็นเซอร์ไร้สาย Protothreads จัดเตรียมการประมวลผลโค้ดเชิงเส้นสำหรับระบบที่ขับเคลื่อนด้วยเหตุการณ์ที่นำมาใช้ใน C. Protothreads สามารถใช้โดยมีหรือไม่มีระบบปฏิบัติการพื้นฐานเพื่อจัดเตรียมการบล็อกตัวจัดการเหตุการณ์ Protothreads จัดเตรียมการไหลต่อเนื่องของการควบคุมโดยไม่ต้องใช้เครื่องจักรรัฐที่ซับซ้อนหรือมัลติเธรดแบบเต็ม

แน่นอนว่ายังเป็นตัวอย่างที่ Arduino ที่นี่มีโค้ดตัวอย่าง คำถาม SOนี้อาจมีประโยชน์เช่นกัน

Arduino ด้ายก็เป็นสิ่งที่ดีเช่นกัน


โปรดทราบว่า Arduino เนื่องจากมีข้อยกเว้นนี้ด้วยการควบคุมหลายลูป: arduino.cc/en/Tutorial/MultipleBlinks
tuskiomi

18

Arduino ของ Arduino ไม่สนับสนุน (ฮาร์ดแวร์) เธรดฉันไม่คุ้นเคยกับ ARM ที่ใช้ Arduino วิธีหนึ่งในข้อ จำกัด นี้คือการใช้อินเทอร์รัปต์ คุณสามารถตั้งโปรแกรมจับเวลาเพื่อขัดจังหวะรูทีนหลักทุก ๆ ไมโครวินาทีเพื่อเรียกใช้รูทีนอื่นที่เฉพาะเจาะจง

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


15

เป็นไปได้ที่จะทำซอฟต์แวร์หลายเธรดใน Uno ไม่รองรับเธรดระดับฮาร์ดแวร์

เพื่อให้บรรลุ multithreading มันจะต้องมีการใช้งานของตัวจัดตารางเวลาขั้นพื้นฐานและการบำรุงรักษากระบวนการหรือรายการงานเพื่อติดตามงานต่าง ๆ ที่จำเป็นต้องเรียกใช้

โครงสร้างของตัวกำหนดตารางเวลาแบบไม่มีการยึดครองง่ายมากจะเป็นเช่น:

//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;
}

แต่ละฟังก์ชั่นสามารถทำงานแยกต่างหากเช่นfunction1ทำการจัดการ LED และfunction2ทำการคำนวณแบบลอย มันจะเป็นความรับผิดชอบของแต่ละงาน (ฟังก์ชั่น) ให้เป็นไปตามเวลาที่จัดสรรให้

หวังว่านี่จะเพียงพอสำหรับคุณในการเริ่มต้น


2
ฉันไม่แน่ใจว่าฉันจะพูดถึง "กระทู้" เมื่อใช้ตัวจัดตารางเวลาที่ไม่ได้รับการดูแล อย่างไรก็ตามตัวกำหนดตารางเวลาดังกล่าวมีอยู่แล้วในฐานะห้องสมุด arduino: arduino.cc/en/Reference/Scheduler
jfpoilpret

5
@jfpoilpret - มัลติเธรดแบบมีส่วนร่วมเป็นเรื่องจริง
Connor Wolf

ใช่คุณพูดถูก! ความผิดพลาดของฉัน; เมื่อไม่นานมานี้ฉันไม่ได้เผชิญหน้ากับการทำงานแบบมัลติเธรดแบบมีส่วนร่วมซึ่งในความคิดของฉัน
jfpoilpret

9

ตามคำอธิบายของความต้องการของคุณ:

  • หนึ่งเธรดกำลังรออุปกรณ์ภายนอก
  • ด้ายหนึ่งกะพริบไฟ LED

ดูเหมือนว่าคุณสามารถใช้ Arduino หนึ่งอินเทอร์รัปต์สำหรับ "เธรด" แรก (ฉันอยากจะเรียกมันว่า "งาน" ในความเป็นจริง)

Arduino ขัดจังหวะสามารถเรียกใช้ฟังก์ชั่นหนึ่ง (รหัสของคุณ) ขึ้นอยู่กับเหตุการณ์ภายนอก (ระดับแรงดันไฟฟ้าหรือการเปลี่ยนแปลงระดับบนพินอินพุตดิจิตอล) ที่จะเรียกใช้ฟังก์ชั่นของคุณทันที

อย่างไรก็ตามจุดสำคัญอย่างหนึ่งที่ควรคำนึงถึงในการขัดจังหวะคือฟังก์ชั่นที่เรียกควรเร็วที่สุด (โดยทั่วไปไม่ควรมีการdelay()เรียกหรือ API อื่นใดที่ขึ้นอยู่กับdelay())

หากคุณมีงานยาวที่จะเปิดใช้งานเมื่อทริกเกอร์เหตุการณ์ภายนอกคุณอาจใช้ตัวจัดตารางเวลาแบบมีส่วนร่วมและเพิ่มงานใหม่จากงานอินเตอร์รัปต์ของคุณ

จุดสำคัญที่สองเกี่ยวกับการขัดจังหวะคือจำนวนของพวกเขาถูก จำกัด (เช่น 2 ใน UNO เท่านั้น) ดังนั้นหากคุณเริ่มมีเหตุการณ์ภายนอกมากขึ้นคุณจะต้องใช้มัลติเพล็กซ์ทุกชนิดในอินพุตและให้ฟังก์ชันอินเทอร์รัปต์ตัดสินว่าอินพุทมัลติเพล็กใดเป็นทริกเกอร์จริง


6

วิธีง่ายๆคือการใช้การจัดตารางเวลา มีการใช้งานหลายอย่าง อธิบายสั้น ๆ ว่าบอร์ดนี้มีให้ใช้สำหรับบอร์ดที่ใช้ AVR และ SAM โดยพื้นฐานแล้วการโทรครั้งเดียวจะเริ่มงาน "ร่างภายในร่าง"

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

Scheduler.start () จะเพิ่มงานใหม่ที่จะเรียกใช้ taskSetup หนึ่งครั้งจากนั้นเรียก taskLoop ซ้ำ ๆ เช่นเดียวกับที่ร่าง Arduino ทำงาน ภารกิจมีสแต็กของตนเอง ขนาดของสแต็กเป็นพารามิเตอร์ที่เป็นทางเลือก ขนาดสแต็กเริ่มต้นคือ 128 ไบต์

ในการอนุญาตให้เปลี่ยนบริบทงานที่ต้องการโทรหาอัตราผลตอบแทน ()หรือล่าช้า () นอกจากนี้ยังมีแมโครสนับสนุนการรอเงื่อนไข

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 ถึงปุ่ม debounce และเชลล์แบบง่ายพร้อมบรรทัดคำสั่งที่ไม่บล็อก เทมเพลตและเนมสเปซสามารถใช้เพื่อช่วยโครงสร้างและลดซอร์สโค้ด ด้านล่างภาพร่างแสดงวิธีการใช้ฟังก์ชั่นเทมเพลตสำหรับการกะพริบหลายจุด เพียงพอสำหรับ 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

จากคาถาก่อนหน้าของฟอรั่มนี้คำถาม / คำตอบต่อไปนี้ถูกย้ายไปยังวิศวกรรมไฟฟ้า มันมีตัวอย่างรหัส Arduino เพื่อกระพริบไฟ LED โดยใช้ตัวจับเวลาขัดจังหวะในขณะที่ใช้วงหลักที่จะทำอนุกรม IO

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

repost:

การขัดจังหวะเป็นวิธีการทั่วไปในการทำสิ่งต่างๆในขณะที่สิ่งอื่นกำลังดำเนินอยู่ ในตัวอย่างด้านล่าง, LED delay()กะพริบโดยไม่ต้องใช้ เมื่อใดก็ตามที่Timer1เกิดไฟisrBlinker()จะเรียกรูทีนการบริการขัดจังหวะ (ISR) มันสลับไฟ LED เปิด / ปิด

หากต้องการแสดงให้เห็นว่าสิ่งอื่น ๆ สามารถเกิดขึ้นพร้อมกันได้ให้loop()เขียน foo / bar ไปยังพอร์ตอนุกรมที่เป็นอิสระจากไฟ LED กะพริบ

#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 แบบเมทริกซ์

ในหนึ่งคำคุณอาจสร้างตัวจัดตารางเวลาการทำโพลโดยใช้ฟังก์ชั่น millis () และตัวขัดจังหวะตัวจับเวลาใน Arduino

ฉันแนะนำบทความต่อไปนี้จาก 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 library ของฉันได้

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

มันใช้ตัวกำหนดตารางเวลาการขัดจังหวะเพื่อให้การสลับบริบทโดยไม่มีการส่งต่อผลผลิต () หรือความล่าช้า ()

ฉันสร้างห้องสมุดเพราะฉันต้องการสามเธรดและฉันต้องการสองเธรดในเวลาที่แม่นยำไม่ว่าคนอื่น ๆ จะทำอะไร เธรดแรกจัดการการสื่อสารแบบอนุกรม อย่างที่สองคือเรียกใช้ตัวกรองคาลมานโดยใช้การคูณเมทริกซ์ลอยกับห้องสมุด Eigen และอันที่สามคือเธรดลูปควบคุมปัจจุบันที่รวดเร็วซึ่งจะต้องสามารถขัดจังหวะการคำนวณเมทริกซ์

มันทำงานอย่างไร

แต่ละรอบด้ายมีความสำคัญและระยะเวลา หากเธรดที่มีลำดับความสำคัญสูงกว่าเธรดที่กำลังดำเนินการอยู่ในปัจจุบันถึงเวลาดำเนินการถัดไปของตัวกำหนดเวลาจะหยุดเธรดปัจจุบันและสลับไปที่ลำดับความสำคัญที่สูงกว่า เมื่อเธรดที่มีลำดับความสำคัญสูงเสร็จสิ้นการดำเนินการตัวกำหนดตารางเวลาจะสลับกลับไปที่เธรดก่อนหน้า

กฎการกำหนดเวลา

โครงร่างการกำหนดตารางเวลาของไลบรารี ThreadHandler มีดังนี้:

  1. ลำดับความสำคัญสูงสุดก่อน
  2. หากลำดับความสำคัญเท่ากันเธรดที่มีกำหนดส่งเร็วที่สุดจะถูกดำเนินการก่อน
  3. หากสองเธรดมีวันครบกำหนดเดียวกันเธรดที่สร้างขึ้นครั้งแรกจะดำเนินการก่อน
  4. เธรดสามารถถูกขัดจังหวะโดยเธรดที่มีลำดับความสำคัญสูงกว่าเท่านั้น
  5. เมื่อเธรดกำลังดำเนินการเธรดจะบล็อกการดำเนินการของเธรดทั้งหมดที่มีลำดับความสำคัญต่ำกว่าจนกว่าจะเรียกใช้ฟังก์ชันที่ส่งคืน
  6. ฟังก์ชันวนรอบมีความสำคัญ -128 เมื่อเทียบกับเธรด ThreadHandler

วิธีใช้

สามารถสร้างเธรดผ่านการสืบทอด 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: คิวลำดับความสำคัญสำหรับการเรียกใช้งานง่าย

ในรูปแบบนี้ด้ายจะดำเนินการเป็น subclass ของที่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()งานสามารถเรียกใช้ก่อนที่หนึ่งในปัจจุบันไม่มีการดำเนินการเสร็จสมบูรณ์โดยการกลับมาจาก นี้เป็นในทางตรงกันข้ามกับหัวข้อสหกรณ์ที่สามารถให้ผลผลิตของ CPU โดยการเช่นการเรียกหรือyield() delay()หรือกระทู้ยึดเอาเสียก่อนที่สามารถกำหนดออกได้ตลอดเวลา ฉันรู้สึกแตกต่างเป็นสิ่งสำคัญเนื่องจากฉันเห็นว่ามีหลายคนที่มาที่นี่เพื่อค้นหาเธรดทำเช่นนั้นเพราะพวกเขาชอบเขียนโค้ดบล็อกมากกว่าเครื่องของรัฐ การบล็อกเธรดจริงที่ให้ CPU ทำงานได้ดี การบล็อกงาน RtC ไม่ใช่
Edgar Bonet

@EdgarBonet มันเป็นความแตกต่างที่มีประโยชน์ใช่ ฉันจะพิจารณาทั้งสไตล์นี้และเธรดสไตล์ผลตอบแทนเป็นสไตล์ที่แตกต่างกันของเธรดที่ทำงานร่วมกันซึ่งต่างจากเธรดที่ยึดเอาเสียก่อน แต่เป็นความจริงที่ว่าพวกเขาต้องการแนวทางที่แตกต่าง มันน่าสนใจที่จะเห็นการเปรียบเทียบอย่างรอบคอบและเชิงลึกของวิธีการต่างๆที่กล่าวถึงที่นี่ หนึ่งไลบรารีที่ดีที่ไม่ได้กล่าวถึงข้างต้นคือโปรโตทเธต ฉันพบสิ่งต่าง ๆ ที่จะวิพากษ์วิจารณ์ทั้งสอง แต่ก็ชื่นชมเช่นกัน ฉัน (แน่นอน) ชอบแนวทางของฉันเพราะมันชัดเจนที่สุดและไม่ต้องการกองซ้อนเพิ่มเติม
Norman Gray

(การแก้ไข: มีการกล่าวถึงโปรโตเธนในคำตอบของ @ sachleen )
Norman Grey
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.