Thread Pool ในการพัฒนาแอปมือถือ — พื้นฐาน, กลุ่มเธรด และหลักการทำงาน

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

Thread Pool — เป็นกลไกการจัดการเธรดที่พูลของเธรดที่ถูกสร้างไว้ล่วงหน้าถูกนำมาใช้ซ้ำเพื่อดำเนินงาน หลีกเลี่ยงค่าใช้จ่ายเกินจำเป็นในการสร้างและทำลายเธรด ในการพัฒนาแอปมือถือ เธรดพูลใช้สำหรับการทำงานเบื้องหลัง: คำขอเครือข่าย การประมวลผลภาพ การทำงานกับฐานข้อมูล ตามเอกสารของ Google Android (2025) ExecutorService เป็นวิธีที่แนะนำสำหรับการจัดการเธรดเบื้องหลังใน Android ใน iOS OperationQueue และ GCD DispatchQueue กับคิว concurrent ทั่วโลกมีบทบาทคล้ายคลึงกัน

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

  • Thread Pool — กลุ่มของเธรดที่นำกลับมาใช้ใหม่ได้สำหรับดำเนินงานเบื้องหลังโดยไม่มีค่าใช้จ่ายเกินจำเป็นในการสร้างเธรด
  • ExecutorService ใน Android จัดการพูลผ่าน ThreadPoolExecutor ด้วยพารามิเตอร์ที่กำหนดค่าได้
  • OperationQueue ใน iOS ห่อหุ้มเธรดพูลผ่าน maxConcurrentOperationCount
  • Core pool size — จำนวนเธรดขั้นต่ำที่พร้อมดำเนินงานเสมอ
  • Work queue เก็บงานที่รอเธรดว่างในพูล

Thread Pool คืออะไร?

Thread Pool (กลุ่มเธรด) — เป็นรูปแบบสถาปัตยกรรมที่จำนวนเธรดคงที่ถูกสร้างไว้ล่วงหน้าและนำมาใช้ซ้ำเพื่อดำเนินงานหลายงาน แทนที่จะสร้างเธรดใหม่สำหรับแต่ละการดำเนินการ (ซึ่งมีค่าใช้จ่ายสูง: ประมาณ 1 MB สแต็กต่อเธรดใน JVM) งานจะถูกวางในคิวและดำเนินการโดยเธรดที่ว่างจากพูล ในการพัฒนาแอปมือถือ เธรดพูลมีความสำคัญต่อประสิทธิภาพ — Android และ iOS จำกัดจำนวนเธรดต่อแอปพลิเคชัน

ทำไม Thread Pool ถึงสำคัญในการพัฒนาแอปมือถือ

การสร้างเธรดเป็นการดำเนินการที่มีค่าใช้จ่ายสูง: การจัดสรรสแต็ก การลงทะเบียนระบบ การสลับบริบท บนอุปกรณ์มือถือที่มีทรัพยากรจำกัด การสร้างเธรดที่ไม่สามารถควบคุมได้นำไปสู่ OOM (OutOfMemoryError) บน Android และการจำกัดบน iOS Thread Pool แก้ปัญหาทั้งสอง: จำกัดจำนวนสูงสุดของเธรดที่ทำงานพร้อมกันและนำเธรดที่สร้างไว้แล้วกลับมาใช้ใหม่ Google แนะนำ ExecutorService แทน raw Thread() Apple แนะนำ OperationQueue แทน Thread

พารามิเตอร์ไม่มีพูล (raw Thread)มี Thread Pool
การสร้างเธรดสำหรับแต่ละงานครั้งเดียวเมื่อสร้างพูล
เธรดสูงสุดไม่จำกัด (เสี่ยง OOM)จำกัดโดย core/max pool size
การใช้ประโยชน์ต่ำ (เธรดตายหลังงาน)สูง (เธรดถูกนำมาใช้ซ้ำ)
การจัดการด้วยตนเอง (join, interrupt)อัตโนมัติ (ExecutorService)
การใช้หน่วยความจำเพิ่มขึ้นตามแต่ละงานคงที่

Thread Pool ทำงานอย่างไรในการพัฒนาแอปมือถือ?

เธรดพูลทำงานตามหลักการ Producer-Consumer: งาน (Runnable/Callable) จะถูกวางในคิวบล็อก (BlockingQueue) เธรดจากพูลรอ工作在คิวและนำไปดำเนินการ อัลกอริทึม: หากจำนวนเธรดว่างน้อยกว่า corePoolSize จะสร้างเธรดใหม่ หากถึง corePoolSize งานจะถูกวางในคิว หากคิวเต็มและจำนวนเธรดน้อยกว่า maximumPoolSize จะสร้างเธรดเพิ่ม หากเกิน maximumPoolSize งานจะถูกปฏิเสธผ่าน RejectedExecutionHandler

Core Pool Size เทียบกับ Maximum Pool Size

Core pool size — จำนวนเธรดที่ถูกเก็บในพูลแม้ในขณะว่าง Maximum pool size — จำนวนเธรดสูงสุดที่สามารถสร้างได้เมื่อคิวล้น ความแตกต่างระหว่างเธรดทั้งสองคือเธรดเพิ่มเติม (overflow) ที่ถูกสร้างขึ้นชั่วคราวและสิ้นสุดหลังหมดเวลาไม่ได้ใช้งาน บนอุปกรณ์มือถือ แนะนำให้ตั้ง corePoolSize เท่ากับ maximumPoolSize เพื่อหลีกเลี่ยงโหลดสูงสุดจากการสร้างเธรด

Work Queue และ RejectedExecutionHandler

BlockingQueue เก็บงานที่รอดำเนินการ การใช้งานที่นิยมที่สุด: LinkedBlockingQueue (ไม่จำกัด), ArrayBlockingQueue (จำกัด) และ SynchronousQueue (ไม่มีการจัดเก็บ — งานถูกส่งตรงไปยังเธรด) เมื่อคิวและพูลเต็ม RejectedExecutionHandler จะถูกเรียกใช้นโยบายมาตรฐาน: AbortPolicy (โยน RejectedExecutionException), CallerRunsPolicy (ดำเนินการในเธรดของผู้ส่ง), DiscardPolicy และ DiscardOldestPolicy

kotlin
// การสร้าง Thread Pool ใน Android
val threadPool = ThreadPoolExecutor(
    corePoolSize = 2,        // ขั้นต่ำ 2 เธรด
    maximumPoolSize = 4,     // สูงสุด 4 เธรด
    keepAliveTime = 30L,     // อายุของเธรดล้น
    unit = TimeUnit.SECONDS,
    workQueue = LinkedBlockingQueue<Runnable>(16),
    threadFactory = Executors.defaultThreadFactory(),
    handler = ThreadPoolExecutor.CallerRunsPolicy()
)

// การส่งงาน
threadPool.execute {
    val result = api.fetchData()
    runOnUiThread { showData(result) }
}

// การปิดพูล
threadPool.shutdown()
// รอให้งานทั้งหมดเสร็จ
threadPool.awaitTermination(10, TimeUnit.SECONDS)

Thread Pool ใน Android: ExecutorService

Android มีการใช้งานเธรดพูลหลายแบบผ่าน java.util.concurrent Executors — โรงงานที่มีการกำหนดค่าพร้อมใช้: newFixedThreadPool(n) (พูลคงที่), newCachedThreadPool() (ไม่จำกัด, สร้างเธรดตามต้องการ), newSingleThreadExecutor() (เธรดเดียว — ดำเนินการตามลำดับ) สำหรับโปรเจกต์มือถือ แนะนำ newFixedThreadPool ด้วยขีดจำกัดที่เหมาะสม (2-4 เธรด) เนื่องจากพูลแคชสามารถสร้างเธรดมากเกินไป

ThreadPoolExecutor ใน Android

ThreadPoolExecutor (TPE) — การใช้งานเต็มรูปแบบของ ExecutorService ด้วยพารามิเตอร์ที่กำหนดค่าได้ บน Android TPE ถูกใช้ภายใน AsyncTask, IntentService และ JobIntentService พารามิเตอร์ corePoolSize, maximumPoolSize, keepAliveTime, BlockingQueue และ RejectedExecutionHandler ช่วยปรับแต่งพฤติกรรมของพูล คำแนะนำสำหรับ Android: corePoolSize = จำนวนคอร์ CPU - 1 (สำหรับงาน IO-bound) หรือจำนวนคอร์ (สำหรับงาน CPU-bound) สำหรับแอปพลิเคชันทั่วไป: 2-4 เธรด

kotlin
// การกำหนดค่าพร้อมใช้ของ Executors
// 1. พูลคงที่ 3 เธรด
val fixedPool = Executors.newFixedThreadPool(3)

// 2. พูลแคช (ไม่แนะนำสำหรับมือถือ)
val cachedPool = Executors.newCachedThreadPool()

// 3. เธรดเดียว (การเรียงลำดับ)
val singlePool = Executors.newSingleThreadExecutor()

// 4. ตัวกำหนดเวลา (งานเป็นระยะ)
val scheduler = Executors.newScheduledThreadPool(2)

// การใช้กับ Callable และ Future
val future: Future<String> = fixedPool.submit(Callable {
    "Result: ${api.call()}"
})
// การรับผลลัพธ์ (บล็อกเธรด)
val result = future.get(5, TimeUnit.SECONDS)

// การปิดพูล
fixedPool.shutdownNow()

CoroutineDispatcher ในฐานะเธรดพูล

Coroutine ของ Kotlin มี CoroutineDispatcher — นามธรรมที่คล้ายกับเธรดพูล Dispatchers.IO ใช้พูล 64 เธรด (จำกัด) Dispatchers.Default — พูลเท่ากับจำนวนคอร์ CPU CoroutineDispatcher ไม่ต้องการการปิดด้วยตนเองและถูกจัดการโดยอัตโนมัติ สำหรับการปรับแต่งขั้นสูง ให้สร้าง ExecutorCoroutineDispatcher ที่กำหนดเองผ่าน Executors.newFixedThreadPool(2).asCoroutineDispatcher() Coroutine ไม่ได้แทนที่เธรดพูล แต่ห่อหุ้มมัน

Thread Pool ใน iOS: OperationQueue และ GCD

iOS มีกลไกหลักสองอย่างสำหรับจัดการเธรดพูล: OperationQueue (API ระดับสูงที่สร้างบน GCD) และ GCD DispatchQueue (API ระดับต่ำ C) OperationQueue ห่อหุ้มเธรดพูลผ่านคุณสมบัติ maxConcurrentOperationCount โดยค่าเริ่มต้น OperationQueue ใช้ค่าสูงสุดที่ระบบกำหนด (ขึ้นอยู่กับโหลดของระบบ) DispatchQueue.global() มีคิว concurrent กับพูลเธรดของระบบ

OperationQueue และ maxConcurrentOperationCount

OperationQueue จัดการพูลเธรดผ่าน maxConcurrentOperationCount ค่า 1 สร้างคิวแบบเรียงลำดับ (คล้ายพูลเธรดเดียว) ค่ามากกว่า 1 สร้างพูล concurrent ตามขีดจำกัดที่ระบุ โดยค่าเริ่มต้น maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount (ค่าที่เหมาะสมของระบบ ปกติ 4-8 เธรด) Operation รองรับการพึ่งพา ลำดับความสำคัญ และการยกเลิก แต่ละการดำเนินการทำงานบนเธรดว่างใดก็ได้จากพูลของระบบ

swift
// OperationQueue กับพูล 3 เธรด
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 3
queue.qualityOfService = .utility

// การสร้างการดำเนินการ
let operation1 = BlockOperation {
    let data = fetchData(from: url1)
    DispatchQueue.main.async { updateUI(data) }
}

let operation2 = BlockOperation {
    let data = fetchData(from: url2)
    DispatchQueue.main.async { updateUI(data) }
}

// การพึ่งพา: operation2 รอ operation1
operation2.addDependency(operation1)

// การเพิ่มในคิว
queue.addOperations([operation1, operation2], waitUntilFinished: false)

// การยกเลิกการดำเนินการทั้งหมด
queue.cancelAllOperations()

GCD DispatchQueue ในฐานะเธรดพูล

DispatchQueue — เป็นเธรดพูลของ Apple คิว concurrent (qos: .utility) ใช้พูลเธรดของระบบ ซึ่งปรับให้เหมาะสมกับโหลดปัจจุบันของอุปกรณ์ ระดับ QoS ที่แตกต่างกัน (userInteractive, userInitiated, utility, background) จับคู่กับพูลต่างกันที่มีลำดับความสำคัญต่างกัน DispatchGroup ช่วยให้ซิงโครไนซ์หลายงาน DispatchWorkItem รองรับการยกเลิกและ qualityOfService สำหรับการควบคุมละเอียด ให้สร้างคิว concurrent ที่กำหนดเองผ่าน DispatchQueue(label: qos: attributes: .concurrent)

swift
// GCD DispatchQueue ในฐานะเธรดพูล
let customQueue = DispatchQueue(
    label: "com.app.background",
    qos: .utility,
    attributes: .concurrent,
    autoreleaseFrequency: .workItem
)

// การส่งงานไปยังพูล
customQueue.async { self.processFile(file1) }
customQueue.async { self.processFile(file2) }

// DispatchGroup สำหรับการซิงโครไนซ์
let group = DispatchGroup()
let pool = DispatchQueue.global(qos: .utility)

pool.async(group: group) { fetchData() }
pool.async(group: group) { processImage() }

group.notify(queue: .main) {
    self.showResult() // งานทั้งสองเสร็จ
}

// การจำกัดการทำงานพร้อมกันผ่าน semaphore
let semaphore = DispatchSemaphore(value: 3)
for url in urls {
    pool.async {
        semaphore.wait()
        download(url)
        semaphore.signal()
    }
}

พารามิเตอร์การกำหนดค่า Thread Pool

การกำหนดค่าเธรดพูลส่งผลโดยตรงต่อประสิทธิภาพของแอปพลิเคชัน พารามิเตอร์ที่ไม่ถูกต้อง นำไปสู่การใช้ CPU ต่ำเกินไป (เธรดน้อยเกินไป) หรือโหลดระบบเกิน (มากเกินไป) สำหรับแอปพลิเคชันมือถือ ค่าที่เหมาะสมที่สุดแตกต่างจากฝั่งเซิร์ฟเวอร์เนื่องจากทรัพยากรจำกัดและการใช้พลังงาน พารามิเตอร์หลักคือ: corePoolSize, maxPoolSize, ความจุคิว และ keepAliveTime

การคำนวณขนาดพูลที่เหมาะสมที่สุด

สูตรสำหรับงาน IO-bound: corePoolSize = จำนวนคอร์ CPU × 2 (เธรดรอ I/O) สำหรับงาน CPU-bound: corePoolSize = จำนวนคอร์ CPU (เธรดยุ่งกับการคำนวณตลอดเวลา) บนอุปกรณ์มือถือสมัยใหม่ (6-8 คอร์) จะได้ 6-8 เธรดสำหรับ CPU-bound และ 12-16 สำหรับ IO-bound การทดสอบเชิงปฏิบัติแสดงให้เห็นว่าสำหรับแอปมือถือทั่วไป 3-4 เธรดเหมาะสมที่สุด — เธรดมากขึ้นเพิ่มการใช้พลังงานโดยไม่เพิ่มประสิทธิภาพ

ความจุคิวและพฤติกรรมเมื่อล้น

ขนาดของคิวงาน (work queue) กำหนดว่างานสามารถรอดำเนินการได้กี่งาน คิวไม่จำกัด (LinkedBlockingQueue ไม่มีขีดจำกัด) อาจทำให้เกิด OOM เมื่องานมาถึงอย่างรวดเร็ว คิวจำกัด (ArrayBlockingQueue ขนาดคงที่) ปฏิเสธงานเมื่อเต็ม สำหรับแอปพลิเคชันมือถือ แนะนำ ArrayBlockingQueue ด้วยความจุ 16-32 งาน CallerRunsPolicy เป็น RejectedExecutionHandler ที่ดีที่สุดสำหรับมือถือ: มันทำให้ผู้ส่งช้าลง (แรงดันย้อนกลับ) แทนที่จะสูญเสียงาน

พารามิเตอร์คำแนะนำสำหรับมือถือเหตุผล
corePoolSize2-4ทรัพยากรจำกัดของอุปกรณ์มือถือ
maxPoolSizecorePoolSize (หรือ +1-2)หลีกเลี่ยงโหลดสูงสุดจากการสร้างเธรด
keepAliveTime15-30 วินาทีปล่อยหน่วยความจำเร็วโดยไม่ต้องสร้างบ่อย
ความจุคิว16-32สมดุลระหว่างบัฟเฟอร์และความเสี่ยง OOM
HandlerCallerRunsPolicyแรงดันย้อนกลับโดยไม่สูญเสียงาน

ข้อผิดพลาดทั่วไปเมื่อทำงานกับกลุ่มเธรด

นักพัฒนาแอปพลิเคชันมือถือมักทำผิดพลาดเมื่อใช้เธรดพูลซึ่งนำไปสู่การคราช หน่วยความจำรั่ว และการทำงานไม่เสถียร ที่พบบ่อยที่สุด: ไม่เรียก shutdown() สำหรับ ExecutorService, สร้างพูลใหม่ทุกการดำเนินการ, พูลใหญ่เกินไป, deadlock ระหว่างงาน, ใช้ CachedThreadPool บน Android

Deadlock ใน Thread Pool

Deadlock เกิดขึ้นเมื่องานในพูลรอผลลัพธ์ของอีกงานจากพูลเดียวกัน แต่เธรดทั้งหมดไม่ว่าง ตัวอย่าง: งาน A ส่งงาน B ไปยังพูลเดียวกันและเรียก future.get() — หากพูลหมด งาน A รองาน B และงาน B ไม่สามารถดำเนินการได้เพราะไม่มีเธรดว่าง วิธีแก้: ใช้พูลแยกสำหรับงานระดับต่างๆ หรือ callback แบบอะซิงก์แทน .get() ที่บล็อก

kotlin
// Deadlock ใน Thread Pool
val pool = Executors.newFixedThreadPool(1)

// งาน A รองาน B — deadlock!
val futureA = pool.submit {
    // งานนี้จะไม่มีวันถูกดำเนินการ
    val futureB = pool.submit { 42 }
    futureB.get() // บล็อกตลอดไป
}

// วิธีแก้: พูลแยก
val workerPool = Executors.newFixedThreadPool(2)
val callbackPool = Executors.newSingleThreadExecutor()

workerPool.submit {
    callbackPool.submit {
        // กำลังทำงานในพูลแยก — deadlock เป็นไปไม่ได้
    }
}

// หรือใช้ CompletableFuture
workerPool.submit {
    CompletableFuture
        .supplyAsync { 42 }
        .thenAccept { result ->
            println(result)
        }
}

พูลที่ไม่สิ้นสุดและการรั่วไหล

ExecutorService ที่สร้างใน Activity ต้องถูกปิดใน onDestroy() หากไม่ทำ เธรดจะค้างในหน่วยความจำแม้หลังจาก Activity ถูกทำลาย วิธีแก้: เก็บพูลในขอบเขต Application หรือ ViewModel ไม่ใช่ใน Activity สำหรับ coroutine ใช้ viewModelScope หรือ lifecycleScope หากพูลถูกสร้างภายใน Activity ต้องเรียก pool.shutdown() ใน onDestroy() สำหรับการทดสอบ ใช้ es.shutdownNow() เพื่อหยุดทันที

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

Thread Pool แตกต่างจากเธรดปกติอย่างไร?

Thread Pool นำเธรดที่สร้างไว้แล้วกลับมาใช้ใหม่เพื่อดำเนินงานหลายงาน เธรดปกติ (raw Thread) ถูกสร้าง ดำเนินการหนึ่งงาน และถูกทำลาย การสร้างเธรดใช้หน่วยความจำประมาณ 1 MB และเวลาประมาณ 1 ms Thread Pool ลดค่าใช้จ่ายเกินจำเป็น จำกัดจำนวนเธรดสูงสุด และมี API สำหรับการจัดการ (shutdown, awaitTermination)

ควรมีเธรดกี่ตัวในพูลสำหรับแอปมือถือ?

สำหรับแอปมือถือทั่วไป 2-4 เธรด เหมาะที่สุด สำหรับงาน CPU-bound — จำนวนคอร์ CPU สำหรับงาน IO-bound — จำนวนคอร์ × 2 เธรดมากขึ้นเพิ่มการใช้พลังงานและการสลับบริบทโดยไม่เพิ่มประสิทธิภาพ บน Android ใช้ Process.availableProcessors() เพื่อหาจำนวนคอร์ บน iOS — ProcessInfo.processInfo.processorCount

CachedThreadPool คืออะไรและทำไมถึงอันตรายบน Android?

CachedThreadPool สร้างเธรดตามต้องการและนำเธรดที่มีอยู่กลับมาใช้ใหม่ ปัญหา: มันไม่จำกัดจำนวนเธรดสูงสุด หาก 100 งานมาถึงพร้อมกัน จะสร้าง 100 เธรด ซึ่งนำไปสู่ OOM บน Android (แต่ละเธรด ~1 MB) ใช้ newFixedThreadPool(n) ด้วยขีดจำกัดที่ชัดเจน CachedThreadPool ยอมรับได้เฉพาะงาน burst ระยะสั้นที่มีปริมาณน้อยที่รับประกัน

จำเป็นต้องเรียก shutdown() สำหรับ ExecutorService หรือไม่?

ใช่ หากพูลไม่ได้เป็นของคอนเทนเนอร์ที่จัดการ (เช่น coroutine) shutdown() หยุดรับงานใหม่และสิ้นสุดเธรดหลังจากงานปัจจุบันเสร็จ หากไม่มี shutdown() เธรดจะค้างในหน่วยความจำและแอปพลิเคชันไม่สิ้นสุด สำหรับ Activity เรียกใน onDestroy() สำหรับ ViewModel ใช้ coroutineScope การปิดพูลเป็นส่วนบังคับของการจัดการทรัพยากร คล้ายกับการปิด Cursor หรือ InputStream

OperationQueue และ DispatchQueue เป็นเธรดพูลหรือไม่?

ใช่ OperationQueue และ DispatchQueue เป็นเธรดพูลที่ iOS จัดให้ OperationQueue จำกัดการทำงานพร้อมกันผ่าน maxConcurrentOperationCount DispatchQueue.global() ใช้พูลเธรดของระบบโดยไม่ต้องควบคุมโดยตรง ต่างจาก Java ThreadPoolExecutor คุณไม่จัดการ corePoolSize หรือความจุคิว — ระบบปรับพูลให้เหมาะสมโดยอัตโนมัติตามโหลดปัจจุบันและการใช้พลังงานของอุปกรณ์

สรุป

  • Thread Pool — กลุ่มเธรดที่นำกลับมาใช้ใหม่ได้สำหรับงานเบื้องหลัง ลดค่าใช้จ่ายเกินจำเป็นในการสร้างเธรด
  • Android ใช้ ThreadPoolExecutor และ Executors.newFixedThreadPool(n) พร้อมขีดจำกัดขนาดพูลที่ชัดเจน
  • iOS มี OperationQueue กับ maxConcurrentOperationCount และ GCD DispatchQueue กับพูล QoS
  • Core pool size — จำนวนเธรดขั้นต่ำ; maximum pool size — สูงสุดเมื่อคิวล้น
  • Deadlock ในพูลเกิดขึ้นเมื่องานถูกบล็อกรออีกงานจากพูลเดียวกัน
  • CallerRunsPolicy เหมาะกว่าสำหรับแอปมือถือ — ทำให้ผู้ส่งช้าลงโดยไม่สูญเสียงาน
  • สำหรับแอปมือถือทั่วไป ขนาดพูลที่เหมาะสมคือ 2-4 เธรด พร้อม shutdown() เพื่อทำความสะอาด

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

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

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

อ่านเพิ่มเติม