Một trong những cuộc thảo luận sôi nổi nhất giữa các nhà phát triển mà tôi từng thấy ngoài tốc độ thực thi chậm của Python là xung quanh các vấn đề về phân luồng và rất nhiều người trong số họ phàn nàn về GIL [Khóa phiên dịch toàn cầu]. Một số thậm chí còn nói rằng Python và các ngôn ngữ khác không thực sự “đồng thời” và không thể mở rộng tốt
Trong blog trước đây của tôi, tôi đã thảo luận chi tiết về vấn đề đầu tiên giải thích khái niệm thực thi mã Python ở tốc độ C [https. //Trung bình. com/@hitechpundir/execute-python-code-at-the-speed-of-c-extending-python-93e081b53f04]
Bài viết này sẽ cố gắng giải quyết vấn đề thứ hai một cách chi tiết
Trong khi đồng thời xác định vấn đề, song song xác định việc thực hiện. Có nhiều giải pháp để đạt được điều này bao gồm phân luồng tiêu chuẩn [hoặc pthread] cho lập trình không đồng bộ, xử lý chuyển đổi, v.v. Mỗi một trong những mô hình này đều có những ưu và nhược điểm riêng phải được hiểu trước khi bạn là một lập trình viên thực sự có thể chọn sử dụng mô hình nào
Nhiều người không hiểu những ưu và nhược điểm của từng vấn đề chỉ tiếp cận vấn đề với giải pháp gần như phổ biến nhất hiện nay cho “vấn đề” đồng thời. Luồng và lập trình luồng. Và cách tiếp cận này sẽ là nguyên nhân khiến bạn đau đớn và phàn nàn. [Lưu ý cách tiếp cận của chúng tôi chứ không phải bản thân ngôn ngữ là vấn đề]
Luồng và GIL trong Python
Cả quy trình và luồng đều được tạo trong một ngôn ngữ lập trình nhất định và sau đó được lên lịch để chạy, bởi chính trình thông dịch [“luồng xanh”] hoặc bởi hệ điều hành [“luồng gốc”]
Guido nói để trả lời một câu hỏi luồng
“Tuy nhiên, bạn nói đúng, GIL không tệ như bạn nghĩ lúc đầu. bạn chỉ cần gỡ bỏ sự tẩy não mà bạn nhận được từ những người ủng hộ Windows và Java, những người dường như coi các luồng là cách duy nhất để tiếp cận các hoạt động đồng thời
Chỉ vì Java đã từng nhắm đến một hệ điều hành hộp giải mã tín hiệu không hỗ trợ nhiều không gian địa chỉ và chỉ vì việc tạo quy trình trong Windows từng chậm như chó, không có nghĩa là nhiều quy trình [với việc sử dụng IPC một cách hợp lý . ” https. //email. con trăn. org/pipermail/python-3000/2007-May/007414. html
GIL là khóa cấp thông dịch viên. Khóa này ngăn việc thực thi nhiều luồng cùng một lúc trong trình thông dịch Python. Mỗi luồng muốn chạy phải đợi GIL được giải phóng bởi luồng khác, điều đó có nghĩa là ứng dụng Python đa luồng của bạn thực sự là luồng đơn. GIL ngăn truy cập đồng thời vào các đối tượng Python bằng nhiều luồng
Bây giờ, câu hỏi xuất hiện trong đầu là “nếu chúng ta có GIL và một luồng phải sở hữu nó để thực thi trong trình thông dịch, thì điều gì quyết định xem GIL có nên được phát hành hay không?”
Câu trả lời là hướng dẫn mã byte. Khi một ứng dụng Python được thực thi, nó được biên dịch thành mã byte, các lệnh thực tế mà trình thông dịch sử dụng để thực thi ứng dụng. Thông thường, các tệp mã byte kết thúc bằng tên như “. pyc” hoặc “. pyo”. Một dòng nhất định của ứng dụng Python có thể là một mã byte đơn, trong khi những dòng khác, chẳng hạn như câu lệnh nhập, cuối cùng có thể mở rộng thành nhiều hướng dẫn mã byte cho trình thông dịch
Trình thông dịch CPython [đối với mã Python thuần túy] sẽ buộc GIL được giải phóng sau mỗi lệnh mã trăm byte. Điều này có nghĩa là nếu bạn có một dòng mã phức tạp hoạt động như một mã byte đơn thì GIL sẽ không được phát hành trong khoảng thời gian mà câu lệnh đó cần để chạy
Tuy nhiên, phần mở rộng C là ngoại lệ
Sử dụng Py_BEGIN_ALLOW_THREADS và Py_END_ALLOW_THREADS, bạn thực sự có thể nhận và giải phóng GIL một cách tự nguyện. [thêm về điều này có thể được trong một bài viết khác]
Py_BEGIN_ALLOW_THREADS
… Thực hiện một số thao tác chặn I/O…
Py_END_ALLOW_THREADS
Quay trở lại cuộc thảo luận chính, thực tế là GIL ngăn bạn với tư cách là một lập trình viên sử dụng đồng thời nhiều CPU. Tuy nhiên, Python với tư cách là một ngôn ngữ thì không
Cần lưu ý rằng GIL không ngăn quá trình chạy trên bộ xử lý khác của máy. Nó chỉ đơn giản là chỉ cho phép một luồng chạy cùng một lúc trong trình thông dịch
Vì vậy, đa xử lý không đa luồng sẽ cho phép bạn đạt được đồng thời thực sự
Hãy hiểu tất cả điều này thông qua một số điểm chuẩn vì chỉ có điều đó mới khiến bạn tin vào những gì đã nói ở trên. Và vâng, đó nên là cách để học - trải nghiệm thay vì chỉ đọc hoặc hiểu nó. Bởi vì nếu bạn đã trải qua một điều gì đó, thì không có lý lẽ nào có thể thuyết phục bạn về những suy nghĩ đối lập
import randomfrom threading import Threadfrom multiprocessing import Processsize = 10000000 # Number of random numbers to add to listthreads = 2 # Number of threads to createmy_list = []for i in xrange[0,threads]: my_list.append[[]]def func[count, mylist]: for i in range[count]: mylist.append[random.random[]]def multithreaded[]: jobs = [] for i in xrange[0, threads]: thread = Thread[target=func,args=[size,my_list[i]]] jobs.append[thread] # Start the threads for j in jobs: j.start[] # Ensure all of the threads have finished for j in jobs: j.join[]
def simple[]: for i in xrange[0, threads]: func[size,my_list[i]]
def multiprocessed[]: processes = [] for i in xrange[0, threads]: p = Process[target=func,args=[size,my_list[i]]] processes.append[p] # Start the processes for p in processes: p.start[] # Ensure all processes have finished execution for p in processes: p.join[]if __name__ == "__main__": multithreaded[] #simple[] #multiprocessed[]
kết quả điểm chuẩn
Để thực hiện đo điểm chuẩn, chúng tôi có thể sửa đổi tham số luồng trong tệp thành 2 , 3 , 4 để có kết quả bên dưới. Trong chức năng chính, chúng ta có thể chạy đa luồng, đơn giản hoặc đa xử lý cùng một lúc [bằng cách nhận xét hai cái khác như trên] và sử dụng
time python threadbenchmark.py
để có được thời gian tiêu thụ. [chúng ta chỉ nên nhìn vào kết quả thực tế để đo điểm chuẩn mà không phải lo lắng về người dùng và thời gian của hệ thống]
Thời gian thực hiện tính bằng giây cho 2, 3 và 4 luồng
simple threading multiprocessingthreads = 2 4.124 5.539 2.034threads = 3 6.391 13.772 3.376threads = 4 9.194 17.641 4.720
Vì vậy, luồng thậm chí còn chậm hơn so với thực thi đơn giản. Điều này được hiểu từ hành vi của GIL đã thảo luận ở trên và bây giờ chúng ta không nên ngạc nhiên
Thực sự những gì mang lại lợi ích hiệu suất về tốc độ thực thi là đa xử lý. Vì vậy, ngôn ngữ không giới hạn chúng ta ở tất cả. Tất nhiên, giống như đa luồng phải giải quyết các vấn đề về đồng bộ hóa, bế tắc, v.v. ; . [Giao tiếp giữa các quá trình] có thể được thảo luận trong một bài đăng khác
Vì vậy, chúng ta có thể dễ dàng kết luận rằng các luồng không nên được sử dụng cho các tác vụ bị ràng buộc bởi CPU. Tuy nhiên, cuộc thảo luận này không có nghĩa là chủ đề không hữu ích. Các chủ đề sau đó hữu ích trong Python ở đâu?
- Trong các ứng dụng GUI để giữ cho chuỗi giao diện người dùng phản hồi nhanh
- Nhiệm vụ IO [IO mạng hoặc IO hệ thống tập tin]
Theo dõi Practo trên twitter để cập nhật thường xuyên từ nhóm Kỹ thuật của chúng tôi. Nếu bạn thích bài viết này, vui lòng nhấn nút ❤ để giới thiệu nó. Điều này sẽ giúp những người dùng Phương tiện khác tìm thấy nó