目次

1. บทนำ

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

2. multiprocessing คืออะไร?

2.1 ความจำเป็นของการประมวลผลแบบขนาน

โดยปกติ Python ทำงานแบบซิงเกิลเธรด แต่เมื่อเจอกับงานที่ซับซ้อนหรือข้อมูลจำนวนมาก ความเร็วในการประมวลผลอาจมีข้อจำกัด การใช้การประมวลผลแบบขนานช่วยให้สามารถทำหลายงานได้พร้อมกัน ใช้ CPU ทุกคอร์ให้เต็มประสิทธิภาพ และลดเวลาในการทำงาน โมดูล multiprocessing สามารถหลีกเลี่ยง GIL (Global Interpreter Lock) ของ Python และใช้หลายโปรเซสเพื่อให้การทำงานแบบขนานที่แท้จริงเกิดขึ้นได้

2.2 ความแตกต่างกับซิงเกิลเธรด

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

年収訴求

3. ไวยากรณ์พื้นฐานของโมดูล multiprocessing

3.1 วิธีใช้คลาส Process

พื้นฐานของโมดูล multiprocessing คือการใช้ คลาส Process ที่ช่วยสร้างโปรเซสใหม่และทำงานแบบขนานได้ง่าย

import multiprocessing

def worker_function():
    print("โปรเซสใหม่กำลังทำงาน")

if __name__ == "__main__":
    process = multiprocessing.Process(target=worker_function)
    process.start()
    process.join()

ในโค้ดนี้ worker_function จะถูกรันในโปรเซสใหม่ start() ใช้ในการเริ่มโปรเซส และ join() ใช้รอจนกว่าโปรเซสจะทำงานเสร็จ

3.2 วิธีส่งอาร์กิวเมนต์ไปยังโปรเซส

สามารถส่งอาร์กิวเมนต์ไปยังโปรเซสได้โดยใช้พารามิเตอร์ args ตัวอย่างด้านล่างจะส่งค่าไปยังฟังก์ชัน worker:

def worker(number):
    print(f'Worker {number} กำลังทำงาน')

if __name__ == "__main__":
    process = multiprocessing.Process(target=worker, args=(5,))
    process.start()
    process.join()

ด้วยวิธีนี้สามารถส่งข้อมูลแบบไดนามิกให้โปรเซสและทำงานขนานได้

4. การแชร์ข้อมูลและการซิงโครไนซ์

4.1 การใช้หน่วยความจำร่วมในการแชร์ข้อมูล

ในมัลติโปรเซส สามารถแชร์ข้อมูลอย่างปลอดภัยระหว่างโปรเซสได้โดยใช้ Value หรือ Array ซึ่งทำหน้าที่เป็น shared memory object ที่หลายโปรเซสเข้าถึงพร้อมกันได้

import multiprocessing

def increment_value(shared_value):
    with shared_value.get_lock():
        shared_value.value += 1

if __name__ == "__main__":
    shared_value = multiprocessing.Value('i', 0)
    processes = [multiprocessing.Process(target=increment_value, args=(shared_value,)) for _ in range(5)]

    for process in processes:
        process.start()

    for process in processes:
        process.join()

    print(f'ค่าที่ได้สุดท้าย: {shared_value.value}')

โค้ดด้านบนแสดงตัวอย่างที่ 5 โปรเซสทำการเพิ่มค่าของตัวเลขใน shared memory พร้อมกัน โดยใช้ get_lock() เพื่อป้องกันการชนกันของข้อมูล

4.2 การใช้ Lock เพื่อป้องกันการชนกันของข้อมูล

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

侍エンジニア塾

5. การกระจายงานด้วย Process Pool

5.1 การใช้คลาส Pool

การใช้ Pool ช่วยกระจายงานหลายอย่างให้กับหลายโปรเซสเพื่อทำงานพร้อมกัน เหมาะสำหรับงานที่ต้องประมวลผลข้อมูลจำนวนมาก

from multiprocessing import Pool

def square(x):
    return x * x

if __name__ == "__main__":
    with Pool(4) as pool:
        results = pool.map(square, range(10))
    print(results)

โค้ดนี้ทำการคำนวณค่ากำลังสองของตัวเลขในลิสต์ โดยแบ่งไปให้ 4 โปรเซสทำงานพร้อมกัน การใช้ map() ทำให้การกระจายงานไปยังโปรเซสเป็นเรื่องง่าย

แผนภาพ: การกระจายงานด้วย Pool

โฟลว์การกระจายงาน

5.2 ตัวอย่างการใช้ starmap

ด้วย starmap() สามารถทำงานแบบขนานกับฟังก์ชันที่มีหลายอาร์กิวเมนต์ ตัวอย่างเช่น:

def multiply(x, y):
    return x * y

if __name__ == "__main__":
    with Pool(4) as pool:
        results = pool.starmap(multiply, [(1, 2), (3, 4), (5, 6), (7, 8)])
    print(results)

6. การใช้ทรัพยากร CPU ให้มีประสิทธิภาพสูงสุด

6.1 การใช้ cpu_count() เพื่อปรับจำนวนโปรเซส

ด้วย multiprocessing.cpu_count() สามารถตรวจสอบจำนวนคอร์ของระบบและกำหนดจำนวนโปรเซสได้อย่างเหมาะสม เพื่อหลีกเลี่ยงการสร้างโปรเซสมากเกินไป

from multiprocessing import Pool, cpu_count

if __name__ == "__main__":
    with Pool(cpu_count() - 1) as pool:
        results = pool.map(square, range(100))
    print(results)

6.2 การใช้ทรัพยากรระบบอย่างมีประสิทธิภาพ

ควรหลีกเลี่ยงการใช้ CPU ทุกคอร์ โดยเว้นไว้ 1 คอร์เพื่อไม่ให้กระทบต่อการทำงานอื่นของระบบ

7. กรณีการใช้งานจริงและแนวทางปฏิบัติที่ดีที่สุด

7.1 ตัวอย่างการใช้งานจริง

การใช้ multiprocessing มีประโยชน์ในสถานการณ์ เช่น:

  • การประมวลผลข้อมูลขนาดใหญ่: อ่านและประมวลผลหลายไฟล์พร้อมกัน
  • การฝึกโมเดล Machine Learning แบบขนาน: รันการเทรนโมเดลหลายโปรเซสพร้อมกันเพื่อลดเวลา
  • การทำ Web Crawling: ดึงข้อมูลจากหลายเพจพร้อมกันอย่างมีประสิทธิภาพ

7.2 แนวทางปฏิบัติที่ดีที่สุด

  • การจัดสรรทรัพยากรอย่างเหมาะสม: กำหนดจำนวนโปรเซสตามจำนวนคอร์จริง
  • การใช้ Debug และ Logging: ใช้ logging เพื่อติดตามสถานะของโปรเซสและจัดการข้อผิดพลาด
import logging
import multiprocessing

def worker_function():
    logging.info(f'โปรเซส {multiprocessing.current_process().name} เริ่มทำงาน')

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    process = multiprocessing.Process(target=worker_function, name='Worker1')
    process.start()
    process.join()

โค้ดนี้บันทึกการทำงานของแต่ละโปรเซสด้วย logging เพื่อสามารถตรวจสอบย้อนหลังได้

  • การจัดการข้อผิดพลาด: เนื่องจากมีหลายโปรเซสทำงานพร้อมกัน ต้องมีการใช้ try-except เพื่อจัดการข้อผิดพลาดไม่ให้กระทบกับโปรเซสอื่น

8. สรุป

บทความนี้ได้อธิบายการใช้โมดูล multiprocessing ของ Python เพื่อเพิ่มประสิทธิภาพในการทำงานแบบขนาน ตั้งแต่พื้นฐานการใช้ คลาส Process การแชร์ข้อมูล การกระจายงานด้วย Process Pool ไปจนถึงกรณีการใช้งานจริงและแนวทางปฏิบัติที่ดีที่สุด

การใช้การประมวลผลแบบขนานอย่างถูกต้องช่วยให้สามารถจัดการงานขนาดใหญ่ได้ดีขึ้น เช่น การประมวลผลข้อมูลจำนวนมาก การฝึกโมเดล Machine Learning และการ Web Crawling โมดูล multiprocessing จึงเป็นเครื่องมือสำคัญที่ช่วยเพิ่มประสิทธิภาพของ Python ได้อย่างมหาศาล

ขอแนะนำให้นำความรู้เหล่านี้ไปปรับใช้กับงานพัฒนาในชีวิตประจำวันเพื่อให้ได้ผลลัพธ์ที่ดีที่สุด