+5

Multi-thread, Multi-process, CPU bound multi-thread hay multi-process?

Bài viết nằm trong series Multi-thread programming in depth

1. Multi-thread

Từ định nghĩa về Thread ở bài viết trước Thread và Process, mình thấy rằng Multi-thread là một tổ hợp nhiều thread riêng lẻ với nhau.

Multi-thread (đa luồng) là một tiến trình thực hiện nhiều luồng đồng thời. Một ứng dụng Java ngoài luồng chính có thể có các luồng khác thực thi đồng thời làm ứng dụng chạy nhanh và hiệu quả hơn. VD: Trình duyệt web hay các chương trình chơi nhạc là 1 ví dụ điển hình về đa luồng.

  • Khi duyệt 1 trang web, có rất nhiều hình ảnh, CSS, javascript… được tải đồng thời bởi các luồng khác nhau.
  • Khi play nhạc, chúng ta vẫn có thể tương tác được với nút điều khiển như: Play, pause, next, back … vì luồng phát nhạc là luồng riêng biệt với luồng tiếp nhận tương tác của người dùng.

1.1 Ưu điểm

  • Nó không chặn người sử dụng vì các luồng là độc lập và bạn có thể thực hiện nhiều công việc cùng một lúc.
  • Mỗi luồng có thể dùng chung và chia sẻ nguồn tài nguyên trong quá trình chạy, nhưng có thể thực hiện một cách độc lập.
  • Luồng là độc lập vì vậy nó không ảnh hưởng đến luồng khác nếu ngoại lệ xảy ra trong một luồng duy nhất.
  • Có thể thực hiện nhiều hoạt động với nhau để tiết kiệm thời gian. Ví dụ một ứng dụng có thể được tách thành : luồng chính chạy giao diện người dùng và các luồng phụ nhiệm gửi kết quả xử lý đến luồng chính.

1.2 Nhược điểm

  • Càng nhiều luồng thì xử lý càng phức tạp.
  • Xử lý vấn đề về tranh chấp bộ nhớ, đồng bộ dữ liệu khá phức tạp.
  • Cần phát hiện tránh các luồng chết (dead lock), luồng chạy mà không làm gì trong ứng dụng cả. (Mình sẽ nói chi tiết về deadlock trong một bài viết khác)

2. Multi-process

Multiprocessing hay Multi-process là khả năng của một hệ thống hỗ trợ nhiều bộ vi xử lý processor cùng một lúc. Các ứng dụng trong hệ thống đa xử lý được chia thành nhiều quy trình nhỏ và chạy độc lập, và hệ điều hành sẽ phân bổ các luồng này cho bộ vi xử lý để cải thiện hiệu suất của hệ thống. Thực tế thì không phải ngôn ngữ nào cũng hỗ trợ đầy đủ multi-processing.

3. CPU bound multi-thread hay multi-process?

3.1 Multitasking

Máy tính ngày nay CPU 4 lõi, 8 lõi (core)... luôn chạy nhiều chương trình cùng lúc. Máy tính ngày xưa khi chỉ có 1 CPU 1 core cũng vậy, chạy được nhiều chương trình "cùng lúc" nhờ CPU chuyển liên tục chạy các chương trình khác nhau, việc chuyển đổi rất nhanh này khiến người dùng có cảm giác là chạy cùng lúc. Ví dụ chạy 4 process A B C D:

A B C D A B D C B A C D...

chuyện này không thay đổi kể cả với máy tính nhiều core do số chương trình chạy luôn lớn hơn số core nhiều lần. Ví dụ:

$ grep -c processor /proc/cpuinfo
4
$ ps -ef | wc -l
265

PS: bạn đọc sau khi đọc xong command line linux nhé.

3.2 CPU Schedular

Việc sắp xếp các chương trình chạy thế nào (dùng CPU thế nào) do một bộ phận của kernel có tên "scheduler" thực hiện - Linux CPU scheduler Chi tiết mình sẽ đề cập trong bài viết sau.Tạm thời cứ biết vậy đã 😃

3.3 Một process chạy multi-thread trên mấy CPU Core?

Tham khảo tại man 7 sched

$ whatis sched
sched (7)            - overview of CPU scheduling

Trong tài liệu viết:

Scheduling policies
   The scheduler is the kernel component that decides which runnable thread
   will be executed by the CPU next.  Each thread has an associated
   scheduling policy and a  static scheduling  priority, sched_priority.  The
   scheduler makes  its  decisions  based  on knowledge  of  the  scheduling
   policy and static priority of all threads on the sys‐ tem.
API summary
   Linux provides the following system  calls for  controlling the CPU
   scheduling behavior, policy,  and  priority  of  processes (or, more
   precisely, threads).

Linux kernel scheduler sắp xếp lịch chạy trên CPU cho các thread (hay gọi là task).

Trong man 1 taskset viết:

-a, --all-tasks
  Set or retrieve the CPU affinity of all the tasks (threads) for a given PID.

Một process chạy multithreading trên mấy CPU core?

Với 10 process, mỗi process chỉ có 1 thread, sẽ là 10 thread cần chạy, kernel sẽ sched (xếp lịch) việc chạy 10 task này cho N CPU. Tương tự 1 process, chạy 10 thread, kernel cũng sẽ sched việc chạy 10 task này cho N CPU (N > 0).

Python multi-threaded vs multi-process Dòng thứ 2 trong tài liệu thư viện threading của Python viết

CPython implementation detail: In CPython, due to the Global Interpreter Lock, only one thread can execute Python code at once (even though certain performance-oriented libraries might overcome this limitation). If you want your application to make better use of the computational resources of multi-core machines, you are advised to use multiprocessing or concurrent.futures.ProcessPoolExecutor. However, threading is still an appropriate model if you want to run multiple I/O-bound tasks simultaneously.

Python là một trong số ít ngôn ngữ mà nhiều thread không chạy được trên nhiều CPU core cùng lúc do giới hạn của Global Interpreter Lock - GIL trong CPython/PyPy. Giới hạn này KHÔNG tồn tại trong các bản Python khác như Jython (trên JVM) và IronPython (trên .NET). Vì GIL, CPython chỉ có thể chạy trên CPU 1 thread 1 lúc, nên muốn chạy nhiều thread/process trên nhiều CPU core cùng lúc, Python có thư viện multiprocessing.

Multiprocessing is a package that supports spawning processes using an API similar to the threading module. The multiprocessing package offers both local and remote concurrency, effectively side-stepping the Global Interpreter Lock by using subprocesses instead of threads. Due to this, the multiprocessing module allows the programmer to fully leverage multiple processors on a given machine.

Tránh nhầm lẫn rằng python threading thực sự chạy các thread cùng lúc trên nhiều CPU core, việc các thread có vẻ chạy cùng lúc trong Python chỉ là multitasking, chạy chuyển đổi giữa các thread.

Java multithreading

Java hỗ trợ multithreading với các thread chạy cùng lúc như mong đợi, và nhiều thread này hoàn toàn có thể được chạy trên nhiều CPU core.

Vì multithreading chạy rất ngon lành, nên ít có lý do gì để sinh ra khái niệm "multiprocessing" như Python. Ngoài ra, bật 1 Java process là chạy 1 máy ảo JVM nặng nề, khởi động chậm (so với bật 1 process CPython interpreter 0.1s 8MB RAM) nên việc này rất ít thấy trong thực tế.

PS: Python threading API dựa trên API của Java.

Ngoài ra, trong Java 8 stream API có parallel stream thể hiện rõ đặc điểm của việc chạy multithread trên N core. Các bạn có thể xem chi tiết ở stackoverflow hoặc baeldung.

Rust multithreading

Tương tự Java, không tồn tại thư viện "multiprocessing" trong Rust.

In most current operating systems, an executed program’s code is run in a process, and the operating system will manage multiple processes at once. 
Within a program, you can also have independent parts that run simultaneously. 
The features that run these independent parts are called threads. 
For example, a web server could have multiple threads so that it could respond to more than one request at the same time.

Go multithreading, multiprocessing

Go không dùng khái niệm process hay thread của hệ điều hành mà dùng khái niệm Goroutine, tương tự thread, nhưng do Go runtime quản lý thay vì OS kernel.

A goroutine is a lightweight thread managed by the Go runtime. 
Goroutines run in the same address space, so access to shared memory must be synchronized.

Các goroutine cũng có thể được nhiều CPU core chạy cùng lúc

GOMAXPROCS sets the maximum number of CPUs that can be executing simultaneously and returns the previous setting.

3.4 Kết luận

  • CPU bound là chương trình dành phần lớn thời gian dùng CPU xử lý, khác với
  • IO bound là chương trình dành phần lớn thời gian đọc ghi file/network.

Câu hỏi này có thể là trap, cần hỏi lại dùng ngôn ngữ gì, trừ khi hỏi cụ thể tới Python thì trả lời dùng multiprocessing. Trong các ngôn ngữ khác như Rust/Java, multithreading là câu trả lời, vì không có thư viện multi-process mà chạy. Hay Go chỉ có goroutine chứ không có lựa chọn khác.

Khi nói chung chung, multi-process có ưu điểm là sự tách biệt giữa các process, một process bị crash sẽ không ảnh hưởng tới process khác, nhược điểm là việc giao tiếp giữa các process để chia sẻ data sẽ phức tạp. Nhiều chương trình dùng mô hình này như:

  • postgresql
  • nginx master-workers

Multi-threaded giúp dễ dàng truy cập bộ nhớ chung, nhưng có thể gặp trường hợp 1 thread crash khiến cả chương trình tắt ngóm, nhược điểm là dễ xảy ra race-condition: N thread tranh nhau truy cập cùng 1 tài nguyên.

Không có câu trả lời dễ dàng, vì đây là trường hợp của PostgreSQL, sau vài chục năm chạy multi-process, nay đang khám phá option multi-thread.

Let's make PostgreSQL multi-threaded

Reference

After credit

Đón chờ bài viết sau về chủ đề Cách OS quản lý thread nhé!!!

Anh Tu Phi


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí