หัดเขียนโปรแกรมแบบบ้านๆ ตอนที่ 9 Coroutine

 บทความก่อนหน้า


Coroutine

ปกติการทำงานของโปรแกรมจะเป็นการทำงานเรียงลำดับไปทีละคำสั่งแต่ coroutine ในภาษา Lua เป็นการจำลองการทำงานแบบ multithreading หรือการแบ่งงานออกเป็นส่วนย่อยที่เรียกว่า thread ให้แยกทำงานไปพร้อมๆ กันได้ แต่ coroutine จะใช้วิธีสลับการทำงานระหว่างแต่ละ thread ทำให้ดูเหมือนว่าจะทำงานไปพร้อมกันแทนโดยแต่ละ thread ไม่ต้องรอให้ thread ทำงานเสร็จก่อนแต่จะไม่จัดการสลับงานอัตโนมัติเราต้องเป็นคนจัดการสั่งให้แต่ละ thread หยุดหรือเริ่มงานเอง

ตัวอย่างการทำงานแบบปกติ

thread1 กับ thread2 ทำงานเรียงลำดับตามปกติ
  
  function thread1()
    print"thread1..."
    for i = 1, 3 do
      print(i)
    end
  end
  
  function thread2()
    print"thread2..."
    for i = 5, 7 do
      print(i)
    end
  end
  
  -- โปรแกรมหลัก
  for i = 1, 2 do
    print("#"..i)
    thread1()
    thread2()
  end
  
  -- ผลลัพธ์
  --[[
  #1                                    -- รอบที่ 1
  thread1...                            -- thread1 เริ่มทำงาน
  1
  2
  3
  thread2...                            -- thread2 เริ่มทำงาน
  5
  6
  7
  #2                                    -- รอบที่ 2
  thread1...
  1
  2
  3
  thread2...
  5
  6
  7
  ]]
  

ตัวอย่างการทำงานแบบ Coroutine

มีรูปแบบการรับข้อมูลดังนี้

  --Ex.1
  thread2 = coroutine.create(function()
    for i = 1, 3 do
      print"thread2..."
      print(i)
      coroutine.yield()                 -- หยุดการทำงานของ thread ชั่วคราว
    end
  end)
  
  thread1 = function()
    for i = 5, 7 do
      print"thread1..."
      print(i)
      coroutine.resume(thread2)         -- สลับไป thread1
    end
  end
  
  -- โปรแกรมหลัก
  thread1()
  
  -- ผลลัพธ์
  --[[
  thread1...
  5
  thread2...
  1
  thread1...
  6
  thread2...
  2
  thread1...
  7
  thread2...
  3
  ]]
  
  --Ex.2
  thread1 = coroutine.create(function()
    for i = 1, 2 do
      print("thread1..."..coroutine.status(thread1))
      print(i)
      coroutine.yield()                 -- หยุดการทำงานของ thread ชั่วคราว
      print("รอบที่ "..(i+1))
    end
    print"thread1...end"
  end)
  
  thread2 = coroutine.wrap(function()
    print"thread2..."
    for i = 5, 7 do
      print(i)
      coroutine.yield()                 -- หยุดการทำงานของ thread ชั่วคราว
    end
  end)
  
  -- โปรแกรมหลัก
  print("status0:"..coroutine.status(thread1))
  for i = 1, 3 do
    coroutine.resume(thread1)           -- ทำงาน thread1 ต่อจากที่หยุดไว้
    print("status"..i..":"..coroutine.status(thread1))
    thread2()                           -- ทำงาน thread2 ต่อจากที่หยุดไว้
  end
  
  -- ผลลัพธ์
  --[[
  status0:suspended                     -- status ของ thread1 ที่โปรแกรมหลักก่อนเข้าลูป
  thread1...running                     -- thread1 รอบที่ 1
  1
  status1:suspended                     -- status ของ thread1 ที่โปรแกรมหลักรอบที่ 1
  thread2...                            -- thread2 รอบที่ 1
  5
  รอบที่ 2                                -- thread1 รอบที่ 2 ( i==1 )
  thread1...running                     -- i==2
  2
  status2:suspended                     -- status ของ thread1 ที่โปรแกรมหลักรอบที่ 2
  6                                     -- thread2 รอบที่ 2
  รอบที่ 3                                -- thread1 รอบที่ 3 ( i==2 )
  thread1...running                     -- i==3
  3
  thread1...end
  status3:dead                          -- status ของ thread1 ที่โปรแกรมหลักรอบที่ 3
  7                                     -- thread2 รอบที่ 3
  ]]
  
การใช้คำสั่ง thread1 = coroutine.create(function()...end) จะได้ thread1 เป็นข้อมูลประเภท thread ซึ่งต้องใช้ coroutine.resume(thread1) เพื่อเรียกให้ thread1 ทำงานจนเจอคำสั่ง coroutine.yield() จึงจะหยุดการทำงานของ thread1 เหมือนจบการทำงานในฟังก์ชันปกติและกลับมาทำงานอื่นต่อจนเมื่อมีการเรียก coroutine.resume(thread1) อีกครั้งจึงจะกลับไปทำงานใน thread1 ต่อจากคำสั่ง coroutine.yield() ล่าสุดในฟังก์ชันนั้น และสามารถใช้คำสั่ง coroutine.status(thread1) เพื่อตรวจสอบสถานะของ thread ได้

ส่วนการใช้คำสั่ง thread2 = coroutine.wrap(function()...end) จะได้ thread2 เป็นข้อมูลประเภท function ที่สามารถหยุดการทำงานชั่วคราวเมื่อเจอคำสั่ง coroutine.yield() ได้เหมือนกับ thread แต่สามารถเรียกใช้งานได้ด้วย function call ( thread2() ) เหมือนกับฟังก์ชันธรรมดา

การรับส่งค่าใน thread

stat [, var1, var2, ...] = coroutine.resume(thread, ...) จะรับค่า ... ส่งไปให้ฟังก์ชันใน thread และเรียก thread ทำงานและหยุดเมื่อเจอ coroutine.yield([val1, val2, ...]) และส่งค่า true ไปที่ตัวแปร stat ( ถ้า status เป็น dead จะคืนค่า false กับข้อความแสดงข้อผิดพลาดแทน ) กับค่า val ไปที่ตัวแปร var
ถ้าเป็นฟังก์ชันที่ใช้ coroutine.wrap() เมื่อเรียกใช้ฟังก์ชันจะเหมือนกับใช้ coroutine.resume() เรียก thread แต่จะคืนแต่ค่า val ที่ yield ออกมาเท่านั้น

  --Ex.1
  co1 = coroutine.create(function(n)
      print(string.format("send %d to co",n))
      n = coroutine.yield(2)            -- ส่งค่า 2 กลับไปที่โปรแกรมหลัก
                                        -- และรับค่าจากโปรแกรมหลักเก็บที่ n
      print(string.format("send %d to co",n))
      n = coroutine.yield(4)            -- ส่งค่า 4 กลับไปที่โปรแกรมหลัก
                                        -- และรับค่าจากโปรแกรมหลักเก็บที่ n
      print(string.format("send %d to co",n))
  end)
  
  -- โปรแกรมหลัก
  _, m = coroutine.resume(co1, 1)       -- เรียก co ทำงานโดยส่งค่า 1 เป็น argument 
                                        -- และคืนค่าที่ yield ออกมา
  print(string.format("received %d from co",m))
  _, m = coroutine.resume(co1, 3)       -- เรียก co ทำงานโดยส่งค่า 3 เป็น argument 
                                        -- และคืนค่าที่ yield ออกมา
  print(string.format("received %d from co",m))
  
  -- ผลลัพธ์
  --[[
  send 1 to co
  received 2 from co
  send 3 to co
  received 4 from co
  ]]
  
  --Ex.2
  co2 = coroutine.wrap(function(n)
      print(string.format("send %d to co",n))
      n = coroutine.yield(2)            -- ส่งค่า 2 กลับไปที่โปรแกรมหลัก
                                        -- และรับค่าจากโปรแกรมหลักเก็บที่ n
      print(string.format("send %d to co",n))
      n = coroutine.yield(4)            -- ส่งค่า 4 กลับไปที่โปรแกรมหลัก
                                        -- และรับค่าจากโปรแกรมหลักเก็บที่ n
      print(string.format("send %d to co",n))
  end)
  
  -- โปรแกรมหลัก
  m = co2(1)                            -- เรียก co ทำงานโดยส่งค่า 1 เป็น argument 
                                        -- และคืนค่าที่ yield ออกมา
  print(string.format("received %d from co",m))
  m = co2(3)                            -- เรียก co ทำงานโดยส่งค่า 3 เป็น argument 
                                        -- และคืนค่าที่ yield ออกมา
  print(string.format("received %d from co",m))
  
  -- ผลลัพธ์
  --[[
  send 1 to co
  received 2 from co
  send 3 to co
  received 4 from co
  ]]
นอกจากนี้ใน thread เองยังสามารถเรียกใช้ฟังก์ชันปกติซึ่งสามารถหยุดการทำงานชั่วคราวด้วย coroutine.yield() และเริ่มทำงานในฟังก์ชันนั้นต่อเมื่อเรียก thread นั้นทำงานต่อ

  function iter(t)
    for i = 1, #t do
      coroutine.yield(i,t[i])
    end
  end
  
  co = coroutine.create(function(t)
    iter(t)
  end)
  
  -- โปรแกรมหลัก
  local a = {"a","b","c"}
  local s, index, value = coroutine.resume(co,a)
  print(1, s, value, coroutine.status(co), index)
  s, index, value = coroutine.resume(co)
  print(2, s, value, coroutine.status(co), index)
  s, index, value = coroutine.resume(co)
  print(3, s, value, coroutine.status(co), index)
  s, index, value = coroutine.resume(co)
  print(4, s, value, coroutine.status(co), index)
  
  -- ผลลัพธ์
  --[[
  1       true    a       suspended       1
  2       true    b       suspended       2
  3       true    c       suspended       3
  4       true    nil     dead    nil
  5       false   nil     dead    cannot resume dead coroutine
  ]]
  
ตอนต่อไปมารู้จัก Iterator กันครับ

บทความถัดไป

ความคิดเห็น