Đồng tiền trong Python - Đa xử lý

Trong chương này, chúng ta sẽ tập trung hơn vào sự so sánh giữa đa xử lý và đa luồng.

Đa xử lý

Nó là việc sử dụng hai hoặc nhiều đơn vị CPU trong một hệ thống máy tính. Đó là cách tiếp cận tốt nhất để khai thác toàn bộ tiềm năng từ phần cứng của chúng tôi bằng cách sử dụng đầy đủ số lõi CPU có sẵn trong hệ thống máy tính của chúng tôi.

Đa luồng

Nó là khả năng của CPU để quản lý việc sử dụng hệ điều hành bằng cách thực thi nhiều luồng đồng thời. Ý tưởng chính của đa luồng là đạt được sự song song bằng cách chia một quy trình thành nhiều luồng.

Bảng sau đây cho thấy một số khác biệt quan trọng giữa chúng:

Đa xử lý Đa chương trình
Đa xử lý đề cập đến việc xử lý nhiều quá trình cùng một lúc bởi nhiều CPU. Đa chương trình lưu giữ một số chương trình trong bộ nhớ chính cùng một lúc và thực thi chúng đồng thời bằng cách sử dụng một CPU.
Nó sử dụng nhiều CPU. Nó sử dụng một CPU.
Nó cho phép xử lý song song. Quá trình chuyển đổi ngữ cảnh diễn ra.
Ít thời gian hơn để xử lý công việc. Nhiều thời gian hơn để xử lý công việc.
Nó tạo điều kiện cho việc sử dụng hiệu quả các thiết bị của hệ thống máy tính. Kém hiệu quả hơn đa xử lý.
Thường đắt hơn. Hệ thống như vậy ít tốn kém hơn.

Loại bỏ tác động của khóa thông dịch viên toàn cầu (GIL)

Trong khi làm việc với các ứng dụng đồng thời, có một hạn chế trong Python được gọi là GIL (Global Interpreter Lock). GIL không bao giờ cho phép chúng tôi sử dụng nhiều lõi của CPU và do đó chúng tôi có thể nói rằng không có luồng thực sự trong Python. GIL là mutex - khóa loại trừ lẫn nhau, giúp mọi thứ an toàn. Nói cách khác, chúng ta có thể nói rằng GIL ngăn nhiều luồng thực thi mã Python song song. Khóa chỉ có thể được giữ bởi một luồng tại một thời điểm và nếu chúng ta muốn thực thi một luồng thì trước tiên nó phải có được khóa.

Với việc sử dụng đa xử lý, chúng ta có thể bỏ qua hạn chế do GIL gây ra một cách hiệu quả -

  • Bằng cách sử dụng đa xử lý, chúng tôi đang sử dụng khả năng của nhiều quy trình và do đó chúng tôi đang sử dụng nhiều phiên bản GIL.

  • Do đó, không có giới hạn nào về việc thực thi bytecode của một luồng trong các chương trình của chúng tôi tại bất kỳ thời điểm nào.

Các quy trình bắt đầu bằng Python

Ba phương pháp sau có thể được sử dụng để bắt đầu một quy trình bằng Python trong mô-đun đa xử lý:

  • Fork
  • Spawn
  • Forkserver

Tạo quy trình với Fork

Lệnh Fork là một lệnh tiêu chuẩn được tìm thấy trong UNIX. Nó được sử dụng để tạo các quy trình mới được gọi là quy trình con. Tiến trình con này chạy đồng thời với tiến trình được gọi là tiến trình mẹ. Các quy trình con này cũng giống với các quy trình mẹ của chúng và kế thừa tất cả các tài nguyên có sẵn cho quy trình mẹ. Các lệnh gọi hệ thống sau được sử dụng trong khi tạo quy trình với Fork:

  • fork()- Nó là một lời gọi hệ thống thường được thực hiện trong nhân. Nó được sử dụng để tạo một bản sao của quy trình. P>

  • getpid() - Lệnh gọi hệ thống này trả về ID tiến trình (PID) của tiến trình gọi.

Thí dụ

Ví dụ tập lệnh Python sau đây sẽ giúp bạn hiểu rõ cách tạo một quy trình con mới và nhận PID của các quy trình con và quy trình mẹ -

import os

def child():
   n = os.fork()
   
   if n > 0:
      print("PID of Parent process is : ", os.getpid())

   else:
      print("PID of Child process is : ", os.getpid())
child()

Đầu ra

PID of Parent process is : 25989
PID of Child process is : 25990

Tạo quy trình với Spawn

Spawn có nghĩa là bắt đầu một cái gì đó mới. Do đó, sinh sản một quy trình có nghĩa là một quy trình mẹ tạo ra một quy trình mới. Tiến trình mẹ tiếp tục thực thi của nó một cách không đồng bộ hoặc đợi cho đến khi tiến trình con kết thúc quá trình thực thi của nó. Làm theo các bước sau để tạo ra một quy trình -

  • Nhập mô-đun đa xử lý.

  • Tạo tiến trình đối tượng.

  • Bắt đầu hoạt động của quy trình bằng cách gọi start() phương pháp.

  • Chờ cho đến khi quá trình kết thúc và thoát ra bằng cách gọi join() phương pháp.

Thí dụ

Ví dụ sau về tập lệnh Python giúp tạo ra ba quy trình

import multiprocessing

def spawn_process(i):
   print ('This is process: %s' %i)
   return

if __name__ == '__main__':
   Process_jobs = []
   for i in range(3):
   p = multiprocessing.Process(target = spawn_process, args = (i,))
      Process_jobs.append(p)
   p.start()
   p.join()

Đầu ra

This is process: 0
This is process: 1
This is process: 2

Tạo quy trình với Forkserver

Cơ chế Forkserver chỉ khả dụng trên các nền tảng UNIX được chọn hỗ trợ chuyển các bộ mô tả tệp qua Unix Pipes. Hãy xem xét các điểm sau để hiểu hoạt động của cơ chế Forkserver:

  • Máy chủ được khởi tạo khi sử dụng cơ chế Forkserver để bắt đầu quy trình mới.

  • Sau đó, máy chủ nhận lệnh và xử lý tất cả các yêu cầu tạo quy trình mới.

  • Để tạo một quy trình mới, chương trình python của chúng tôi sẽ gửi một yêu cầu đến Forkserver và nó sẽ tạo một quy trình cho chúng tôi.

  • Cuối cùng, chúng tôi có thể sử dụng quy trình được tạo mới này trong các chương trình của mình.

Quy trình daemon bằng Python

Python multiprocessingmô-đun cho phép chúng ta có các quy trình daemon thông qua tùy chọn daemonic của nó. Các quy trình daemon hoặc các quy trình đang chạy trong nền tuân theo khái niệm tương tự như các luồng daemon. Để thực hiện quá trình trong nền, chúng ta cần đặt cờ daemonic thành true. Tiến trình daemon sẽ tiếp tục chạy miễn là tiến trình chính đang thực thi và nó sẽ kết thúc sau khi kết thúc quá trình thực thi hoặc khi chương trình chính bị giết.

Thí dụ

Ở đây, chúng ta đang sử dụng ví dụ tương tự như được sử dụng trong các luồng daemon. Sự khác biệt duy nhất là sự thay đổi của mô-đun từmultithreading đến multiprocessingvà đặt cờ daemonic thành true. Tuy nhiên, sẽ có một sự thay đổi trong đầu ra như hình dưới đây -

import multiprocessing
import time

def nondaemonProcess():
   print("starting my Process")
   time.sleep(8)
   print("ending my Process")
def daemonProcess():
   while True:
   print("Hello")
   time.sleep(2)
if __name__ == '__main__':
   nondaemonProcess = multiprocessing.Process(target = nondaemonProcess)
   daemonProcess = multiprocessing.Process(target = daemonProcess)
   daemonProcess.daemon = True
   nondaemonProcess.daemon = False
   daemonProcess.start()
   nondaemonProcess.start()

Đầu ra

starting my Process
ending my Process

Đầu ra khác khi so sánh với đầu ra được tạo bởi các luồng daemon, vì quy trình ở chế độ không có daemon có đầu ra. Do đó, quá trình daemonic tự động kết thúc sau khi các chương trình chính kết thúc để tránh các quá trình đang chạy liên tục.

Kết thúc quy trình bằng Python

Chúng tôi có thể hủy hoặc chấm dứt quy trình ngay lập tức bằng cách sử dụng terminate()phương pháp. Chúng tôi sẽ sử dụng phương thức này để kết thúc tiến trình con, đã được tạo với sự trợ giúp của hàm, ngay trước khi hoàn thành việc thực thi.

Thí dụ

import multiprocessing
import time
def Child_process():
   print ('Starting function')
   time.sleep(5)
   print ('Finished function')
P = multiprocessing.Process(target = Child_process)
P.start()
print("My Process has terminated, terminating main thread")
print("Terminating Child Process")
P.terminate()
print("Child Process successfully terminated")

Đầu ra

My Process has terminated, terminating main thread
Terminating Child Process
Child Process successfully terminated

Kết quả đầu ra cho thấy rằng chương trình kết thúc trước khi thực hiện tiến trình con đã được tạo với sự trợ giúp của hàm Child_process (). Điều này ngụ ý rằng quá trình con đã được kết thúc thành công.

Xác định quy trình hiện tại bằng Python

Mọi quy trình trong hệ điều hành đều có nhận dạng quy trình được gọi là PID. Trong Python, chúng ta có thể tìm ra PID của quy trình hiện tại với sự trợ giúp của lệnh sau:

import multiprocessing
print(multiprocessing.current_process().pid)

Thí dụ

Ví dụ sau về tập lệnh Python giúp tìm ra PID của quy trình chính cũng như PID của quy trình con:

import multiprocessing
import time
def Child_process():
   print("PID of Child Process is: {}".format(multiprocessing.current_process().pid))
print("PID of Main process is: {}".format(multiprocessing.current_process().pid))
P = multiprocessing.Process(target=Child_process)
P.start()
P.join()

Đầu ra

PID of Main process is: 9401
PID of Child Process is: 9402

Sử dụng một quy trình trong lớp con

Chúng ta có thể tạo các chuỗi bằng cách phân loại threading.Threadlớp học. Ngoài ra, chúng tôi cũng có thể tạo các quy trình bằng cách phân loạimultiprocessing.Processlớp học. Để sử dụng một quy trình trong lớp con, chúng ta cần xem xét các điểm sau:

  • Chúng ta cần xác định một lớp con mới của Process lớp học.

  • Chúng tôi cần ghi đè _init_(self [,args] ) lớp học.

  • Chúng ta cần ghi đè run(self [,args] ) phương pháp để thực hiện những gì Process

  • Chúng ta cần bắt đầu quá trình này bằng cách gọistart() phương pháp.

Thí dụ

import multiprocessing
class MyProcess(multiprocessing.Process):
   def run(self):
   print ('called run method in process: %s' %self.name)
   return
if __name__ == '__main__':
   jobs = []
   for i in range(5):
   P = MyProcess()
   jobs.append(P)
   P.start()
   P.join()

Đầu ra

called run method in process: MyProcess-1
called run method in process: MyProcess-2
called run method in process: MyProcess-3
called run method in process: MyProcess-4
called run method in process: MyProcess-5

Mô-đun đa xử lý Python - Lớp nhóm

Nếu chúng ta nói về song song đơn giản processingcác tác vụ trong các ứng dụng Python của chúng tôi, sau đó mô-đun đa xử lý cung cấp cho chúng tôi lớp Pool. Các phương pháp sau củaPool lớp có thể được sử dụng để tăng số lượng quy trình con trong chương trình chính của chúng tôi

phương thức apply ()

Phương pháp này tương tự như.submit()phương pháp của .ThreadPoolExecutor.Nó chặn cho đến khi kết quả sẵn sàng.

phương thức apply_async ()

Khi chúng ta cần thực hiện song song các tác vụ của mình thì chúng ta cần sử dụngapply_async()phương pháp để gửi nhiệm vụ cho nhóm. Đây là một hoạt động không đồng bộ sẽ không khóa luồng chính cho đến khi tất cả các quy trình con được thực thi.

phương thức map ()

Giống như apply(), nó cũng chặn cho đến khi kết quả sẵn sàng. Nó tương đương với cài sẵnmap() chức năng chia dữ liệu có thể lặp lại thành một số phần và gửi đến nhóm quy trình dưới dạng các tác vụ riêng biệt.

phương thức map_async ()

Nó là một biến thể của map() phương pháp như apply_async() là với apply()phương pháp. Nó trả về một đối tượng kết quả. Khi kết quả sẵn sàng, một hàm có thể gọi được áp dụng cho nó. Có thể gọi phải được hoàn thành ngay lập tức; nếu không, luồng xử lý kết quả sẽ bị chặn.

Thí dụ

Ví dụ sau sẽ giúp bạn triển khai một nhóm quy trình để thực hiện thực thi song song. Một phép tính đơn giản về bình phương số đã được thực hiện bằng cách áp dụngsquare() chức năng thông qua multiprocessing.Poolphương pháp. Sau đópool.map() đã được sử dụng để gửi 5, vì đầu vào là danh sách các số nguyên từ 0 đến 4. Kết quả sẽ được lưu trữ trong p_outputs và nó được in.

def square(n):
   result = n*n
   return result
if __name__ == '__main__':
   inputs = list(range(5))
   p = multiprocessing.Pool(processes = 4)
   p_outputs = pool.map(function_square, inputs)
   p.close()
   p.join()
   print ('Pool :', p_outputs)

Đầu ra

Pool : [0, 1, 4, 9, 16]