2011-09-01 20 views
22

Mẫu này xuất hiện rất nhiều nhưng tôi không thể tìm được câu trả lời thẳng.Hành vi của time.sleep của Python (0) trong linux - Nó có gây ra chuyển đổi ngữ cảnh không?

Một không quan trọng, chương trình un thân thiện có thể làm

while(True): 
    # do some work 

Sử dụng công nghệ và nền tảng khác, nếu bạn muốn cho phép chương trình này để chạy nóng (sử dụng càng nhiều chu kỳ CPU càng tốt) nhưng phải lịch sự - cho phép các chương trình khác đang chạy nóng để có hiệu quả làm chậm tôi xuống, bạn sẽ thường xuyên viết:

while(True): 
    #do some work 
    time.sleep(0) 

tôi đã đọc thông tin mâu thuẫn về việc liệu các tiếp cận thứ hai sẽ làm những gì tôi muốn hy vọng vào python, chạy trên một hộp linux. Nó có gây ra sự chuyển đổi ngữ cảnh, dẫn đến hành vi mà tôi đã đề cập ở trên không?

EDIT: Đối với những gì đáng giá, chúng tôi đã thử một chút thử nghiệm trong Apple OSX (không có hộp linux tiện dụng). Hộp này có 4 lõi cộng hyperthreading vì vậy chúng tôi quay lên 8 chương trình chỉ với một

while(True): 
    i += 1 

Đúng như dự đoán, các Monitor hoạt động cho mỗi người trong số 8 quá trình như tiêu thụ hơn 95% CPU (rõ ràng với 4 lõi và hyperthreading bạn nhận được 800% tổng số). Sau đó chúng tôi quay một chương trình thứ chín như vậy. Bây giờ tất cả 9 chạy khoảng 85%. Bây giờ hãy giết người thứ chín và quay lên một chương trình với

while(True): 
    i += 1 
    time.sleep(0) 

Tôi hy vọng rằng quá trình này sẽ sử dụng gần 0% và 8 khác sẽ chạy 95%. Nhưng thay vào đó, tất cả chín chạy khoảng 85%. Vì vậy, trên Apple OSX, giấc ngủ (0) dường như không có hiệu lực.

Trả lời

19

tôi chưa bao giờ nghĩ về điều này, vì vậy tôi đã viết kịch bản này:

import time 

while True: 
    print "loop" 
    time.sleep(0.5) 

Cũng giống như một thử nghiệm. Chạy này với strace -o isacontextswitch.strace -s512 python test.py mang đến cho bạn kết quả này trên vòng lặp:

write(1, "loop\n", 5)     = 5 
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout) 
write(1, "loop\n", 5)     = 5 
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout) 
write(1, "loop\n", 5)     = 5 
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout) 
write(1, "loop\n", 5)     = 5 
select(0, NULL, NULL, NULL, {0, 500000}) = 0 (Timeout) 
write(1, "loop\n", 5) 

select() là lời kêu gọi hệ thống, vì vậy có, bạn đang bối cảnh chuyển đổi (ok về mặt kỹ thuật một chuyển ngữ cảnh là không thực sự cần thiết khi bạn thay đổi tới kernel không gian, nhưng nếu bạn có các tiến trình khác đang chạy, những gì bạn đang nói ở đây là trừ khi bạn có dữ liệu sẵn sàng để đọc trên bộ mô tả tệp của bạn, các quy trình khác có thể chạy cho đến lúc đó) vào hạt nhân để thực hiện điều này. Thật thú vị, sự chậm trễ là trong việc lựa chọn trên stdin. Điều này cho phép python làm gián đoạn đầu vào của bạn trên các sự kiện như đầu vào ctrl+c, nếu họ muốn, mà không cần phải chờ mã để hết thời gian - mà tôi nghĩ là khá gọn gàng.

Tôi nên lưu ý rằng điều tương tự cũng áp dụng cho time.sleep(0) ngoại trừ tham số thời gian được chuyển vào là {0,0}. Và khóa quay đó không thực sự lý tưởng cho bất kỳ điều gì nhưng sự chậm trễ rất ngắn - multiprocessingthreads cung cấp khả năng chờ các đối tượng sự kiện.

Chỉnh sửa: Vì vậy, tôi đã xem xét chính xác những gì linux làm. Việc thực hiện trong do_select (fs\select.c) làm cho việc kiểm tra này:

if (end_time && !end_time->tv_sec && !end_time->tv_nsec) { 
    wait = NULL; 
timed_out = 1; 
} 

if (end_time && !timed_out) 
    slack = select_estimate_accuracy(end_time); 

Nói cách khác, nếu một kết thúc thời gian được cung cấp và cả hai thông số là zero (0 = 1 và trả về true trong C!) Sau đó chờ đợi được thiết lập để NULL và lựa chọn được coi là hết thời gian chờ.Tuy nhiên, điều đó không có nghĩa là hàm trả về cho bạn; nó lặp lại trên tất cả các mô tả tập tin bạn có và gọi cond_resched, do đó có khả năng cho phép một tiến trình khác chạy. Nói cách khác, những gì xảy ra hoàn toàn phụ thuộc vào lịch trình; nếu quá trình của bạn đã được hogging CPU thời gian so với các quá trình khác, rất có thể là một bối cảnh chuyển đổi sẽ diễn ra. Nếu không, nhiệm vụ bạn đang ở (hàm do_select của hạt nhân) có thể chỉ tiếp tục cho đến khi nó hoàn thành.

Tôi sẽ lặp lại, tuy nhiên, cách tốt nhất để đẹp hơn với các quy trình khác thường liên quan đến việc sử dụng các cơ chế khác ngoài khóa xoay.

+0

Điều đó hữu ích. Tôi muốn chắc chắn rằng tôi hiểu kết luận của bạn. Time.sleep (0) đang gây ra một chuyển đổi ngữ cảnh - phải không? Nhưng tôi nghĩ rằng có lẽ giả định của tôi là sai rằng điều này sẽ khiến chương trình của tôi thân thiện hơn với các quy trình khác? –

+0

@Matthew có hai thứ - có một chuyển ngữ cảnh (chuyển đổi nhiệm vụ như chuyển sang một tiến trình khác) và chuyển sang chế độ hạt nhân - chỉ cần chuyển sang chế độ hạt nhân không nhất thiết có nghĩa là bạn cũng sẽ cung cấp thời gian CPU khác. Thêm một sự chậm trễ có thể (nếu có cái gì khác đang chạy) sẽ. Điều này (ngủ (0)) chắc chắn sẽ đưa bạn vào chế độ hạt nhân; nó phụ thuộc vào hạt nhân trong câu hỏi là liệu yêu cầu không có sự chậm trễ ngay lập tức đánh thức chương trình của bạn một lần nữa, hoặc nếu nó tìm kiếm các quá trình khác cũng đang chờ đợi thời gian cpu với hết thời gian chờ hết hạn trên bộ mô tả tập tin. –

+0

Trên thực tế lựa chọn tôi không phải trên stdin. Đối số đầu tiên để chọn() là số lượng các trình đệ trình trong ba tập hợp ... trong trường hợp này 0. Đây là một thủ thuật để thực hiện các lần truy cập dựa trên nano giây. –

1

time.sleep (0) dường như không có bất kỳ ảnh hưởng nào, nó dường như bị bỏ qua. Cung cấp cho nó một số lượng nhỏ của sự chậm trễ giảm sử dụng CPU từ 100% đến 0%:

import time 
while True: 
    time.sleep(0.0001); 

Lưu ý: True/False được viết hoa trong python.

+1

Nó hoàn toàn không có tác dụng. Nó thậm chí là đơn vị được thử nghiệm trong cpython: https://github.com/python/cpython/blob/6db7033192cd537ca987a65971acb01206c3ba82/Lib/test/test_import/__init__.py#L432. 'time.sleep (0)' từ bỏ ngữ cảnh của chuỗi gọi, đó là câu hỏi đặt ra. – erikreed

8

Tôi nghĩ bạn đã có câu trả lời từ @Ninefingers, nhưng trong câu trả lời này, chúng tôi sẽ cố gắng đi sâu vào mã nguồn python.

Đầu tiên mô-đun python time được triển khai trong C và để xem triển khai thực hiện chức năng time.sleep, bạn có thể xem Modules/timemodule.c. Như bạn có thể thấy (và không nhận được tất cả các chi tiết cụ thể về nền tảng) chức năng này sẽ ủy quyền cuộc gọi đến hàm floatsleep.

Bây giờ floatsleep được thiết kế để làm việc trong nền tảng khác nhau nhưng vẫn hành vi được thiết kế để trở thành tương tự như bất cứ khi nào nó có thể, nhưng như chúng ta chỉ quan tâm đến nền tảng Unix-như chúng ta hãy kiểm tra that part only thì chúng tôi:

... 
Py_BEGIN_ALLOW_THREADS 
sleep((int)secs); 
Py_END_ALLOW_THREADS 

Như bạn thấy floatsleep đang kêu gọi C ngủ và từ sleep man page:

giấc ngủ() chức năng có trách nhiệm gây ra tiểu trình đang gọi bị đình chỉ từ điện tử xecution cho đến khi một trong hai số giây thời gian thực theo quy định bởi giây tranh luận đã trôi qua hay ...

Nhưng chờ một phút đã làm chúng ta không quên về GIL?

Vâng đây là nơi Py_BEGIN_ALLOW_THREADSPy_END_ALLOW_THREADS macro bước vào hành động (kiểm tra Include/ceval.h nếu bạn quan tâm về định nghĩa của hai macro này), mã C trên có thể được dịch sử dụng này hai macro để:

Save the thread state in a local variable. 
Release the global interpreter lock. 
... Do some blocking I/O operation ... (call sleep in our case) 
Reacquire the global interpreter lock. 
Restore the thread state from the local variable. 

Thông tin thêm có thể được tìm thấy về hai macro này trong the c-api doc.

Hy vọng điều này hữu ích.

7

Về cơ bản, bạn đang cố gắng chiếm đoạt công việc của bộ lập lịch CPU OS. Nó sẽ có khả năng tốt hơn nhiều để chỉ cần gọi os.nice(100) để thông báo cho lịch trình rằng bạn đang rất thấp ưu tiên để nó có thể làm công việc của mình đúng.

+0

Vâng, bạn nói đúng. –

+0

Chúng tôi chỉ đang cố gắng tìm ra cách nó hoạt động ... –

Các vấn đề liên quan