目次

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 จะทำให้มั่นใจได้ว่าการเข้าถึงข้อมูลถูกซิงโครไนซ์
RUNTEQ(ランテック)|超実戦型エンジニア育成スクール

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 ได้อย่างมหาศาล ขอแนะนำให้นำความรู้เหล่านี้ไปปรับใช้กับงานพัฒนาในชีวิตประจำวันเพื่อให้ได้ผลลัพธ์ที่ดีที่สุด
RUNTEQ(ランテック)|超実戦型エンジニア育成スクール