มัลติเธร็ดและการทำงานพร้อมกันในการพัฒนามือถือ: คืออะไร หลักการและวิธีการทำงาน

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-03-12 เวลาอ่าน: 13 นาที

ทุกแอปพลิเคชันมือถือทำงานหลายอย่างพร้อมกัน: โหลดข้อมูลจากเครือข่าย ประมวลผลการสัมผัสของผู้ใช้ ทำให้อินเทอร์เฟซเคลื่อนไหว และบันทึกไฟล์ หากโค้ดทั้งหมดนี้ทำงานในเธร็ดเดียว แอปพลิเคชันจะค้างเมื่อมีความล่าช้าทางเครือข่าย มัลติเธร็ด (multithreading) และการทำงานพร้อมกันเป็นแนวคิดสำคัญที่ช่วยให้แอปพลิเคชันตอบสนองและมีประสิทธิภาพ ในบทความนี้เราจะกล่าวถึงเครื่องมือหลักทั้งหมด: ตั้งแต่ Main Thread และ RunLoop ไปจนถึง Coroutines ใน Kotlin และ Combine บน iOS เนื้อหาอ้างอิงจากเอกสารทางการของ Apple GCD

ประเด็นสำคัญ

  • Main Thread — เธร็ดเดียวสำหรับทำงานกับ UI; งานอื่นทั้งหมดถูกย้ายไป Background
  • GCD และ OperationQueue — กลไกหลักของมัลติเธร็ดใน iOS
  • Coroutines และ Flow — มาตรฐานสมัยใหม่สำหรับอะซิงโครนัสใน Kotlin/Android
  • RxJava, RxSwift และ Combine — เฟรมเวิร์กเชิงรับสำหรับทำงานกับสตรีมข้อมูล
  • Race Condition, Deadlock และ Livelock — ปัญหามัลติเธร็ดคลาสสิกที่ต้องการการซิงโครไนซ์
  • การเลือกเครื่องมือขึ้นอยู่กับแพลตฟอร์มและความซับซ้อนของงาน: สำหรับการเรียกอย่างง่าย Async/Await ก็เพียงพอ สำหรับสตรีมที่ซับซ้อน — Rx หรือ Combine

มัลติเธร็ดคืออะไร?

มัลติเธร็ดคือความสามารถของแอปพลิเคชันในการดำเนินการโค้ดหลายส่วนพร้อมกัน แต่ละส่วนทำงานใน เธร็ด (Thread) ที่แยกจากกัน — กระบวนการน้ำหนักเบาที่มีสแต็กการเรียกของตัวเอง ในการพัฒนามือถือ เธร็ดแบ่งออกเป็นสองประเภท: Main Thread (เธร็ด UI) และ Background Threads (เธร็ดพื้นหลัง)

ระบบปฏิบัติการจัดการการกระจายเธร็ดไปยังแกนประมวลผลด้วยตัวเอง อุปกรณ์สมัยใหม่มี 6–8 แกน ดังนั้นการดำเนินการแบบขนานสามารถเร่งความเร็วงานได้จริง อย่างไรก็ตาม การสร้างเธร็ดเป็นการดำเนินการที่แพง ดังนั้นจึงไม่แนะนำให้ทำงานกับ Thread โดยตรง แต่จะใช้สิ่งที่เป็นนามธรรมระดับสูงกว่าแทน: DispatchQueue, OperationQueue, CoroutineDispatcher

การทำงานพร้อมกัน (Concurrency) เป็นแนวคิดที่กว้างกว่ามัลติเธร็ด การทำงานพร้อมกันหมายความว่างานสามารถดำเนินการ "พร้อมกัน" ได้แม้บนแกนเดียวผ่านการสลับบริบท อะซิงโครนัส (Async/Await) เป็นโมเดลการเขียนโปรแกรมที่งานไม่ได้บล็อกเธร็ด แต่คืนการควบคุมในขณะที่รอผลลัพธ์ ภาษาสมัยใหม่ (Kotlin, Swift, Dart) มีการสนับสนุน Async/Await ในตัว

ที่ IT Sectr เราให้ความสำคัญเป็นพิเศษกับสถาปัตยกรรมมัลติเธร็ดที่ถูกต้องตั้งแต่เริ่มต้นโครงการ ข้อผิดพลาดในระยะแรกนำไปสู่บักที่จับได้ยาก: การแข่งข้อมูล การตายเบา และความไม่เสถียรของแอปพลิเคชันภายใต้โหลด ทุกโครงการของเราผ่านการตรวจสอบสถาปัตยกรรมการทำงานพร้อมกันในขั้นตอนการวางแผน

เธร็ดหลัก (Main/Background)

Main Thread (เธร็ดหลัก) — เธร็ดเดียวในแอปพลิเคชันมือถือที่สามารถเข้าถึง UI บน Android เรียกว่า UI Thread บน iOS — Main Thread การดำเนินการอินเทอร์เฟซทั้งหมด — การเปลี่ยนข้อความ อนิเมชัน การประมวลผลการสัมผัส — ดำเนินการบน Main Thread เท่านั้น หากดำเนินการหนัก (การโหลดไฟล์, การแยกวิเคราะห์ JSON) บนเธร็ดหลัก อินเทอร์เฟซจะหยุดตอบสนอง บน Android สิ่งนี้นำไปสู่ ANR (Application Not Responding) บน iOS — หน้าจอ "ค้าง"

Background Threads (เธร็ดพื้นหลัง) มีไว้สำหรับทุกอย่างที่ไม่เกี่ยวข้องกับ UI: คำขอเครือข่าย การดำเนินการฐานข้อมูล การประมวลผลภาพ การเข้ารหัส หลังจากเสร็จสิ้น ผลลัพธ์จะถูกส่งไปยัง Main Thread เพื่อแสดงผล แต่ละแพลตฟอร์มมีเครื่องมือของตัวเองสำหรับสลับระหว่างเธร็ด: DispatchQueue.main.async ใน iOS, runOnUiThread หรือ withContext(Dispatchers.Main) ใน Android

RunLoop — วงรอบการประมวลผลเหตุการณ์บนเธร็ดหลักของ iOS RunLoop รอเหตุการณ์ (การสัมผัส ตัวจับเวลา การแจ้งเตือน) และจัดส่งไปยังตัวจัดการที่เหมาะสม บน Android สิ่งที่เทียบเท่าคือ Looper ที่เชื่อมโยงกับแต่ละ Main Thread Main Looper ดึงข้อความจากคิวอย่างไม่สิ้นสุดและส่งไปยัง Handler เพื่อประมวลผล การเข้าใจ RunLoop และ Looper ช่วยหลีกเลี่ยงการรั่วไหลของหน่วยความจำและการ "กระตุก" ของอินเทอร์เฟซ

GCD และ OperationQueue (iOS)

Grand Central Dispatch (GCD) — ไลบรารีของ Apple สำหรับจัดการมัลติเธร็ดในระดับภาษา C GCD ทำงานกับ DispatchQueue — คิวงาน นักพัฒนาไม่ได้สร้างเธร็ดด้วยตนเอง GCD จัดการพูลเธร็ด (Thread Pool) โดยกระจายงานไปยังแกนประมวลผลที่มีอยู่ DispatchQueue มีสองประเภท: Serial Queue (คิวแบบเรียงลำดับ — งานดำเนินการทีละรายการ) และ Concurrent Queue (คิวพร้อมกัน — งานสามารถดำเนินการพร้อมกันได้)

Main DispatchQueue — คิวแบบเรียงลำดับที่ผูกกับเธร็ดหลัก Global Queues — คิวพร้อมกันที่มีลำดับความสำคัญต่างกัน (QoS — Quality of Service): userInteractive, userInitiated, utility, background การเลือก QoS ที่ถูกต้องสำคัญต่อประสิทธิภาพ: .userInteractive — สำหรับงานที่ส่งผลต่อ UI (อนิเมชัน, การเรนเดอร์); .background — สำหรับงานที่ไม่สำคัญด้านเวลา (การซิงโครไนซ์, การล้างแคช)

OperationQueue — สิ่งที่เป็นนามธรรมเหนือ GCD ที่มีความสามารถเพิ่มเติม: การยกเลิกงาน, การตั้งค่าการพึ่งพาระหว่างการดำเนินการ, การควบคุมจำนวนสูงสุดของการดำเนินการพร้อมกัน การดำเนินการเป็นออบเจกต์ของคลาส Operation (หรือ BlockOperation) ตัวอย่าง: หากคุณต้องโหลดรูปภาพ จากนั้นใช้ฟิลเตอร์ และหลังจากนั้นจึงแสดง — OperationQueue ที่มีการพึ่งพาจัดการได้อย่างสมบูรณ์แบบ ใน GCD คุณต้องซิงโครไนซ์ขั้นตอนเหล่านี้ด้วยตนเองโดยใช้ DispatchGroup หรือเซมาฟอร์

Async/Await ใน Swift 5.5+ — ทางเลือกสมัยใหม่ของ GCD คำสำคัญ async และ await ทำให้โค้ดอะซิงโครนัสเป็นเชิงเส้นและอ่านง่าย ฟังก์ชันถูกทำเครื่องหมายเป็น async และการเรียกจะรอผ่าน await ระบบจัดการการสลับบริบทด้วยตัวเอง: โดยค่าเริ่มต้น ฟังก์ชัน async ทำงานบนเธร็ดพื้นหลัง ในขณะที่การอัปเดต UI ทำงานบน MainActor @MainActor — คุณลักษณะที่รับประกันการดำเนินการโค้ดบนเธร็ดหลัก

Coroutines และ Flow (Kotlin)

Coroutines (โคโรทีน) — เธร็ดน้ำหนักเบาสำหรับ Kotlin ที่พัฒนาโดย JetBrains ต่างจากเธร็ดทั่วไป โคโรทีนไม่ได้ผูกกับ Thread เฉพาะ โคโรทีนหลายพันตัวสามารถทำงานบนหลายเธร็ดได้โดยไม่มีโอเวอร์เฮดที่มีนัยสำคัญ CoroutineScope จัดการวงจรชีวิตของโคโรทีน: viewModelScope ผูกกับ ViewModel, lifecycleScope — กับ Activity/Fragment เมื่อสโคปถูกทำลาย โคโรทีนลูกทั้งหมดจะถูกยกเลิกโดยอัตโนมัติ

Dispatchers กำหนดว่าพูลเธร็ดใดที่โคโรทีนทำงาน: Dispatchers.Main — เธร็ด UI; Dispatchers.IO — สำหรับคำขอเครือข่ายและการดำเนินการดิสก์; Dispatchers.Default — สำหรับการคำนวณที่ใช้ CPU มาก การเปลี่ยนดิสแพตเชอร์ใช้ withContext โคโรทีนรองรับการทำงานพร้อมกันแบบมีโครงสร้าง (structured concurrency): โคโรทีนแต่ละตัวมีพาเรนต์ และเมื่อพาเรนต์ถูกยกเลิก โคโรทีนลูกทั้งหมดจะถูกยกเลิก ซึ่งป้องกันการรั่วไหลของหน่วยความจำและงานที่ค้าง

Flow — สตรีมข้อมูลอะซิงโครนัสแบบเย็นจากไลบรารีโคโรทีน Flow ปล่อยค่าตามลำดับ: (1) โปรดิวเซอร์สร้างข้อมูล, (2) โอเปอเรเตอร์แปลงสตรีม, (3) คอลเลกเตอร์ใช้ผลลัพธ์ ต่างจาก LiveData, Flow รองรับเชนโอเปอเรเตอร์ที่ซับซ้อน (map, filter, flatMapConcat, catch) และปลอดภัยต่อเธร็ดอย่างสมบูรณ์ StateFlow และ SharedFlow — รูปแบบร้อนของ Flow เหมาะสำหรับสถานะ UI และเหตุการณ์ครั้งเดียว (Snackbar, การนำทาง)

Channel — สิ่งที่เป็นนามธรรมของโคโรทีนอีกอย่างสำหรับส่งข้อมูลระหว่างโคโรทีน Channel ทำงานเหมือนคิว: ผู้ส่งหนึ่งราย (send) และผู้รับหนึ่งรายหรือมากกว่า (receive) แชนเนลที่มีบัฟเฟอร์ (Channel(UNLIMITED), Channel(BUFFERED)) อนุญาตให้กำหนดค่าพฤติกรรมเมื่อล้น Channel มักใช้กับ Flow เพื่อเชื่อมต่อ API ที่ใช้ callback เข้ากับโคโรทีน: callbackFlow { … }

ที่ IT Sectr เราใช้โคโรทีนและ Flow อย่างแข็งขันในทุกโครงการ Android ซึ่งช่วยให้เขียนโคเด้ดอะซิงโครนัสที่ดูเหมือนซิงโครนัส ทดสอบง่าย (runTest, TestDispatcher) และไม่ต้องการการจัดการเธร็ดด้วยตนเอง ตัวอย่างของโคโรทีนอย่างง่ายพร้อมการโหลดข้อมูล:

kotlin
class UserRepository(
    private val api: UserApi,
    private val dao: UserDao
) {
    suspend fun getUsers(): List<User> = withContext(Dispatchers.IO) {
        return@withContext try {
            val users = api.fetchUsers()
            dao.insertAll(users)
            users
        } catch (e: Exception) {
            dao.getAll()
        }
    }
}

Rx และ Combine

การเขียนโปรแกรมเชิงรับ — กระบวนทัศน์ที่ข้อมูลแพร่กระจายเป็นสตรีมอะซิงโครนัส (Observable, Publisher) RxJava/RxKotlin — การนำไปใช้ที่ได้รับความนิยมมากที่สุดสำหรับ Android, พอร์ตจาก .NET Rx RxSwift — ไลบรารีที่คล้ายกันสำหรับ iOS ส่วนประกอบหลัก: Observable (แหล่งเหตุการณ์), Observer (ผู้สมัครสมาชิก), Scheduler (การจัดการเธร็ด), Operators (การแปลงสตรีม)

Combine — เฟรมเวิร์กของ Apple สำหรับการเขียนโปรแกรมเชิงรับที่เปิดตัวใน iOS 13 Combine ใช้โปรโตคอล Publisher (ผู้เผยแพร่) และ Subscriber (ผู้สมัครสมาชิก) ต่างจาก RxSwift, Combine ถูกสร้างไว้ใน SDK และรวมเข้ากับ SwiftUI อย่างใกล้ชิด โอเปอเรเตอร์ใน Combine: map, filter, combineLatest, zip, debounce, throttle — ครอบคลุมสถานการณ์ส่วนใหญ่: ตั้งแต่การผูกข้อมูลกับ UI ไปจนถึงดีเบานซ์คำค้นหา

Future และ Promise — รูปแบบสำหรับทำงานกับผลลัพธ์อะซิงโครนัสเดียว Future แทนค่าที่จะพร้อมใช้งานในภายหลัง Promise คือคำมั่นสัญญาที่จะให้ค่า ใน Rx นี่คือ Single (การตอบสนองที่สำเร็จหนึ่งครั้งหรือข้อผิดพลาด) ใน Combine — Future Publisher ในทางปฏิบัติ Future/Promise สะดวกสำหรับคำขอ API เดี่ยว ในขณะที่ Observable/Publisher — สำหรับสตรีมต่อเนื่อง (ตำแหน่งทางภูมิศาสตร์, การป้อนข้อความ)

Callback และ Delegate — รูปแบบคลาสสิกสำหรับการดำเนินการอะซิงโครนัส Callback — ฟังก์ชันที่ส่งเป็นอาร์กิวเมนต์และเรียกเมื่อการดำเนินการเสร็จสมบูรณ์ Delegate — ออบเจกต์ที่ใช้โปรโตคอลพร้อมเมธอดตัวจัดการเหตุการณ์ ข้อเสีย: "callback hell" (callback ซ้อนกัน) และความซับซ้อนของการจัดการข้อผิดพลาด NotificationCenter (iOS) และ EventBus (Android) — กลไกการกระจายเหตุการณ์ มีประโยชน์สำหรับการสื่อสารแบบหลวม ๆ แต่นำไปสู่การพึ่งพาโดยนัย

ปัญหามัลติเธร็ด (Race Condition, Deadlock)

มัลติเธร็ดเปิดประตูสู่ประสิทธิภาพสูง แต่ในขณะเดียวกันก็สร้างความเสี่ยงของข้อผิดพลาดที่จับได้ยาก ที่พบบ่อยที่สุด: Race Condition (สภาวะการแข่ง), Deadlock (การตายเบา), Livelock (การล็อกแบบมีชีวิต) และ Starvation (การอดอยากของเธร็ด) การเข้าใจปัญหาเหล่านี้เป็นทักษะจำเป็นสำหรับนักพัฒนามือถือทุกคน

Race Condition

Race Condition เกิดขึ้นเมื่อสองเธร็ดขึ้นไปอ่านและเขียนข้อมูลเดียวกันพร้อมกันโดยไม่มีการซิงโครไนซ์ ผลลัพธ์ขึ้นอยู่กับว่าเธร็ดใดดำเนินการก่อน ตัวอย่างคลาสสิก: สองเธร็ดเพิ่มค่าตัวนับ การดำเนินการ "อ่าน → เพิ่ม → เขียน" ไม่เป็นอะตอม ดังนั้นเมื่อดำเนินการพร้อมกัน การเพิ่มหนึ่งครั้งจะ "สูญหาย" วิธีแก้ไข — ใช้ การดำเนินการแบบอะตอม (AtomicInteger, AtomicReference) หรือการล็อก (Mutex, Semaphore, synchronized)

Deadlock

Deadlock — สถานการณ์ที่แต่ละเธร็ดถือทรัพยากรและรอทรัพยากรที่ถูกยึดโดยเธร็ดอื่น ไม่มีเธร็ดใดสามารถดำเนินการต่อได้ เงื่อนไขการเกิด: การกีดกันร่วมกัน, การถือและรอ, ไม่มีการยึดคืน, การรอแบบวนรอบ การป้องกัน: กำหนดลำดับการได้มาซึ่งล็อกเดียว, ใช้ tryLock กับหมดเวลา, ใช้ อัลกอริทึมแบบ Lock-Free (ConcurrentHashMap, CopyOnWriteArrayList)

Livelock และ Starvation

Livelock — เธร็ดไม่ได้ถูกล็อก แต่ส่งต่อทรัพยากรให้กันและกันอย่างต่อเนื่องโดยไม่ทำงานที่มีประโยชน์ ตัวอย่าง: สองคนพบกันในทางเดินและทั้งคู่หลบทาง เคลื่อนที่ไปในทิศทางเดียวกัน Starvation — เธร็ดไม่ได้รับการเข้าถึงทรัพยากรเพราะเธร็ดอื่นสกัดกั้นอย่างต่อเนื่อง วิธีแก้ไข: ล็อกที่เป็นธรรม (fair locks), ลำดับความสำคัญของเธร็ดด้วยความระมัดระวัง

เครื่องมือซิงโครไนซ์

เพื่อป้องกันปัญหามัลติเธร็ด จะใช้พื้นฐานการซิงโครไนซ์: Mutex (การกีดกันร่วมกัน), Semaphore (การจำกัดจำนวนการเข้าถึงพร้อมกัน), Lock (อินเทอร์เฟซกับ tryLock), Synchronized (ล็อกระดับ JVM), @MainActor (Swift — รับประกันการดำเนินการบนเธร็ดหลัก) บน Android ยังมี ThreadPool (พูลเธร็ด) ผ่าน Executors.newFixedThreadPool, newCachedThreadPool อย่างไรก็ตาม การจัดการพูลด้วยตนเองเป็นสิทธิพิเศษของโครงการเดิม; ในโครงการใหม่ควรใช้โคโรทีน

เครื่องมือ แพลตฟอร์ม ประเภท คุณสมบัติ
DispatchQueue (GCD)iOSคิวงานเรียงลำดับ/พร้อมกัน, ลำดับความสำคัญ QoS, Thread Pool จัดการโดยระบบ
OperationQueueiOSคิวการดำเนินการการพึ่งพา, การยกเลิก, maxConcurrentOperationCount
Coroutines + FlowAndroidโคโรทีนน้ำหนักเบา, การทำงานพร้อมกันแบบมีโครงสร้าง, StateFlow, Channel
RxJava / RxKotlinAndroidสตรีมเชิงรับObservable, Schedulers, ชุดโอเปอเรเตอร์ที่หลากหลาย
CombineiOSสตรีมเชิงรับPublisher/Subscriber, การรวมกับ SwiftUI
Async/Await + TaskiOS / Androidโมเดลอะซิงโครนัสโค้ดเชิงเส้น, @MainActor, การทำงานพร้อมกันแบบมีโครงสร้าง

คำถามที่พบบ่อย

Main Thread ต่างจาก Background Thread อย่างไร?

Main Thread (เธร็ด UI) รับผิดชอบการเรนเดอร์อินเทอร์เฟซและการประมวลผลการสัมผัส Background Thread ดำเนินการงานพื้นหลัง — โหลดข้อมูล, การคำนวณ, งานเครือข่าย การบล็อก Main Thread ทำให้อินเทอร์เฟซค้าง (ANR บน Android, frozen UI บน iOS)

Race Condition คืออะไรและหลีกเลี่ยงได้อย่างไร?

Race Condition — สภาวะการแข่งเมื่อสองเธร็ดเข้าถึงข้อมูลที่ใช้ร่วมกันพร้อมกันและผลลัพธ์ขึ้นอยู่กับลำดับการดำเนินการ หลีกเลี่ยงได้โดยการซิงโครไนซ์: Mutex, Semaphore, Lock, Synchronized, @MainActor หรือการดำเนินการแบบอะตอม

Coroutines หรือ RxJava: เลือกอะไรสำหรับ Android?

Coroutines เป็นมาตรฐานสมัยใหม่สำหรับ Android (JetBrains, รองรับโดย Google) RxJava/RxKotlin เป็นแนวทางเชิงรับพร้อมชุดโอเปอเรเตอร์ที่หลากหลาย Coroutines ง่ายกว่าสำหรับการเรียกอะซิงโครนัส, RxJava มีประสิทธิภาพมากกว่าสำหรับสตรีมข้อมูลที่ซับซ้อน ที่ IT Sectr เราใช้ Coroutines + Flow สำหรับโครงการใหม่

Deadlock และ Livelock คืออะไร?

Deadlock — การบล็อกซึ่งกันและกันเมื่อสองเธร็ดรอทรัพยากรของกันและกัน Livelock — เธร็ดไม่ได้ถูกล็อกแต่ส่งต่อทรัพยากรอย่างต่อเนื่องโดยไม่ทำงานที่มีประโยชน์ ปัญหาทั้งสองแก้ไขได้ด้วยลำดับการล็อกที่เหมาะสมและหมดเวลา

ทำไมต้องใช้ DispatchQueue ใน iOS?

DispatchQueue เป็นสิ่งที่เป็นนามธรรมของ Grand Central Dispatch (GCD) สำหรับจัดการเธร็ด Main Queue ดำเนินงานบนเธร็ดหลัก Global Queues — บนเธร็ดพื้นหลัง Serial Queue รับประกันการดำเนินการตามลำดับ, Concurrent Queue — แบบขนาน ในโครงการสมัยใหม่ GCD มักถูกแทนที่ด้วย Async/Await และ Task

สรุป

  • Main Thread — UI เท่านั้น; การดำเนินการอื่นทั้งหมดไปที่ Background
  • GCD และ OperationQueue — พื้นฐานของมัลติเธร็ดบน iOS; Async/Await — ทางเลือกสมัยใหม่
  • Coroutines และ Flow — มาตรฐานสำหรับ Android; การทำงานพร้อมกันแบบมีโครงสร้างป้องกันการรั่วไหล
  • RxJava, RxSwift, Combine — เฟรมเวิร์กเชิงรับสำหรับสตรีมข้อมูลที่ซับซ้อน
  • Race Condition และ Deadlock — ปัญหาหลัก; แก้ไขด้วยการล็อกและลำดับการได้มาซึ่งทรัพยากรที่เหมาะสม
  • Thread Pool จัดการโดยระบบ (GCD) หรือเฟรมเวิร์ก (โคโรทีน); ไม่แนะนำให้สร้างเธร็ดด้วยตนเอง
  • การเลือกเครื่องมือขึ้นอยู่กับแพลตฟอร์ม: Coroutines สำหรับ Android, GCD/Combine สำหรับ iOS

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ