2012-01-12 46 views
6

Một số lý do tại sao viết đoạn mã sau đây được coi là thực hành không tốt?Tại sao chặn thay vì lặp?

while (someList.isEmpty()) { 
    try { 
     Thread.currentThread().sleep(100); 
    } 
    catch (Exception e) {} 
    } 
    // Do something to the list as soon as some thread adds an element to it. 

Đối với tôi, chọn một giá trị tùy ý để giấc ngủ không phải là thói quen tốt, và tôi sẽ sử dụng một BlockingQueue trong tình huống này, nhưng tôi muốn biết nếu có nhiều hơn một lý do tại sao người ta không nên viết mã như vậy.

Trả lời

6

Nó áp đặt độ trễ trung bình là 50 mili giây trước khi sự kiện được kích hoạt và nó thức dậy 10 lần một giây khi không có sự kiện nào để xử lý. Nếu không phải của những điều đó đặc biệt quan trọng, thì nó chỉ là không thích hợp.

+0

Chỉ cần tự hỏi, đây có phải là cách chức năng chờ đợi hoạt động trong nội bộ không? (Không bao gồm ngoại lệ). – Mehrdad

+1

@ Mehrdad- Thông thường là không. Thông thường các chủ đề được đặt trong một "hàng đợi" và không được cung cấp bất kỳ thời gian xử lý nào. Khi một số sự kiện xảy ra sẽ đánh thức chúng, chúng được đặt trở lại hàng đợi để chúng được lên kế hoạch. Điều này có nghĩa rằng bạn có thể có một triệu chủ đề ngủ mà không bị mất hiệu suất nếu chỉ có hai hoặc ba luồng hoạt động cùng một lúc. – templatetypedef

+0

@templatetypedef: Vậy thì hệ điều hành chính xác tìm hiểu xem các luồng có nên được đánh thức tại một lát thời gian cụ thể không? Không nên kiểm tra trạng thái của luồng trong một vòng lặp ở mọi lát thời gian? – Mehrdad

1

Có nhiều lý do để không thực hiện việc này. Đầu tiên, như bạn đã lưu ý, điều này có nghĩa rằng có thể có một sự chậm trễ lớn giữa thời gian xảy ra sự kiện mà luồng phải trả lời và thời gian phản hồi thực tế, vì luồng có thể đang ngủ. Thứ hai, vì bất kỳ hệ thống nào chỉ có quá nhiều bộ vi xử lý khác nhau, nếu bạn phải tiếp tục khởi động các chuỗi quan trọng của bộ xử lý để chúng có thể yêu cầu thread chuyển sang chế độ ngủ khác, bạn giảm tổng số công việc hữu ích được thực hiện bởi hệ thống và tăng mức sử dụng năng lượng của hệ thống (điều quan trọng trong các hệ thống như điện thoại hoặc thiết bị nhúng).

0

bạn cũng giới thiệu các điều kiện chủng tộc cho lớp học của mình. nếu bạn đang sử dụng hàng đợi chặn thay vì danh sách bình thường - luồng sẽ chặn cho đến khi có mục nhập mới trong danh sách. Trong trường hợp của bạn một sợi thứ hai có thể đặt và lấy một phần tử từ danh sách trong khi chuỗi công nhân của bạn đang ngủ và bạn thậm chí sẽ không nhận thấy.

0

Để thêm vào các câu trả lời khác, bạn cũng có một điều kiện chủng tộc nếu bạn có các mục hơn một thread loại bỏ khỏi hàng đợi:

  1. hàng đợi rỗng
  2. thread đặt một phần tử vào hàng đợi
  3. chủ đề B kiểm tra xem hàng đợi có trống không; nó không phải là
  4. chủ đề C kiểm tra xem hàng đợi có trống không; nó không phải là
  5. luồng B lấy từ hàng đợi; thành công
  6. chuỗi C lấy từ hàng đợi; thất bại

Bạn có thể xử lý điều này bằng cách nguyên tử (trong khối synchronized) kiểm tra xem hàng đợi có trống không và iff không, lấy phần tử từ nó; tại vòng lặp của bạn trông giống một xấu xí tóc:

T item; 
while ((item = tryTake(someList)) == null) { 
    try { 
     Thread.currentThread().sleep(100); 
    } 
    catch (InterruptedException e) { 
     // it's almost never a good idea to ignore these; need to handle somehow 
    } 
} 
// Do something with the item 

synchronized private T tryTake(List<? extends T> from) { 
    if (from.isEmpty()) return null; 
    T result = from.remove(0); 
    assert result != null : "list may not contain nulls, which is unfortunate" 
    return result; 
} 

hoặc bạn có thể có chỉ được sử dụng một BlockingQueue.

+0

Bạn không có nghĩa là 'loại bỏ (0)'? Tôi cho rằng bạn không muốn bỏ qua yếu tố đầu tiên. –

+0

erm có, lỗi đánh máy, Sửa ngay bây giờ. – yshavit

+0

Trong mô tả của bạn, bạn tham khảo một 'hàng đợi' nhưng trong mã của bạn, bạn sử dụng một' Danh sách'. Điều này có thể gây nhầm lẫn. Bạn có thể sử dụng 'Queue.remove()'; BTW một LinkedList cũng là một Queue. –

1

Vòng lặp là một ví dụ tuyệt vời về những việc không nên làm. ;)


Thread.currentThread().sleep(100); 

Không cần để có được những currentThread() vì đây là một phương pháp tĩnh. Nó cũng giống như

Thread.sleep(100); 

catch (Exception e) {} 

Đây là thói quen rất xấu.Thật tệ, tôi sẽ không đề nghị bạn đặt điều này ngay cả trong các ví dụ, vì ai đó có thể sao chép mã. Một phần tốt của câu hỏi trên diễn đàn này sẽ được giải quyết bằng cách in ra và đọc các ngoại lệ nhất định.


You don't need to busy wait here. esp. when you expect to be waiting for such a long time. Busy waiting can make sense if you expect to be waiting a very very short amount of time. e.g. 

// From AtomicInteger 
public final int getAndSet(int newValue) { 
    for (;;) { 
     int current = get(); 
     if (compareAndSet(current, newValue)) 
      return current; 
    } 
} 

Như bạn có thể thấy, nó nên được khá hiếm hoi mà vòng lặp này cần phải đi xung quanh nhiều hơn một lần, và theo cấp số nhân ít có khả năng để đi xung quanh nhiều lần. (Trong một ứng dụng thực tế, chứ không phải là một điểm chuẩn nhỏ) Vòng lặp này có thể ngắn tới 10 ns, mà không phải là một sự chậm trễ lâu dài.


Nó có thể chờ 99 ms không cần thiết. Nói rằng nhà sản xuất đang thêm một mục nhập 1 ms sau đó, nó đã chờ đợi một thời gian dài không có gì.

Giải pháp đơn giản và rõ ràng hơn.

BlockingQueue<E> queue = 

E e = queue.take(); // blocks until an element is ready. 

Danh sách này/hàng đợi chỉ sẽ thay đổi trong một chủ đề và một mô hình đơn giản hơn nhiều cho việc quản lý chủ đề và các hàng đợi được sử dụng một ExecutorService

ExecutorService es = 

final E e = 
es.submit(new Runnable() { 
    public void run() { 
     doSomethingWith(e); 
    } 
}); 

Như bạn thấy, bạn không cần phải làm việc với hàng đợi hoặc chủ đề trực tiếp. Bạn chỉ cần nói những gì bạn muốn các hồ bơi thread để làm.

0

Tôi không thể thêm trực tiếp vào các câu trả lời exellent do David, templatetypedef, v.v. - nếu bạn muốn tránh các chủ đề liên quan đến độ trễ và lãng phí tài nguyên, không thực hiện liên kết giữa các luồng với các vòng ngủ().

Lên lịch/gửi đi ưu tiên:

Ở cấp CPU, ngắt là chìa khóa. Hệ điều hành không làm gì cho đến khi một gián đoạn xảy ra làm cho mã của nó được nhập vào. Lưu ý rằng, trong các điều khoản OS, các ngắt có hai mùi vị - phần cứng 'thực' ngắt khiến trình điều khiển được chạy và 'ngắt phần mềm' - đây là các cuộc gọi hệ điều hành từ các luồng đã chạy có khả năng gây ra tập hợp các luồng đang chạy thay đổi. Các phím bấm, di chuyển chuột, thẻ mạng, đĩa, lỗi trang tạo ra các ngắt phần cứng. Các chức năng chờ và tín hiệu, và sleep(), thuộc về thể loại thứ hai đó. Khi một phần cứng gián đoạn làm cho một trình điều khiển được chạy, trình điều khiển thực hiện bất kỳ phần cứng quản lý nó được thiết kế để làm. Nếu trình điều khiển cần báo hiệu hệ điều hành mà một số luồng cần được chạy, (có lẽ một bộ đệm đĩa hiện đã đầy và cần được xử lý), hệ điều hành cung cấp một cơ chế nhập mà trình điều khiển có thể gọi thay vì trực tiếp thực hiện ngắt trả lại chính nó, (quan trọng!).

Ngắt như các ví dụ trên có thể tạo các chuỗi đang chờ sẵn sàng chạy và/hoặc có thể tạo một luồng đang chạy vào trạng thái chờ. Sau khi xử lý mã của ngắt, hệ điều hành áp dụng thuật toán lập lịch trình của nó/s để quyết định xem tập hợp các luồng đang chạy trước ngắt có giống như tập hợp mà bây giờ sẽ được chạy hay không. Nếu họ đang có, hệ điều hành chỉ gián đoạn trả về, nếu không, hệ điều hành phải preempt một hoặc nhiều chủ đề đang chạy. Nếu hệ điều hành cần phải preempt một thread đang chạy trên một lõi CPU mà không phải là một trong đó xử lý ngắt, nó đã giành quyền kiểm soát của lõi CPU đó. Nó thực hiện điều này bằng phương tiện ngắt phần cứng 'thực' - trình điều khiển bộ vi xử lý giữa hệ điều hành thiết lập một tín hiệu phần cứng làm gián đoạn lõi đang chạy luồng mà nó được ưu tiên.

Khi một chuỗi được đặt trước vào mã OS, hệ điều hành có thể lưu một ngữ cảnh hoàn chỉnh cho chuỗi.Một số thanh ghi sẽ được lưu vào ngăn xếp của luồng bằng cách ngắt mục và do đó việc lưu ngăn xếp của chuỗi sẽ 'lưu' tất cả các thanh ghi đó một cách hiệu quả, nhưng hệ điều hành thông thường sẽ cần phải làm nhiều hơn, ví dụ. bộ nhớ đệm có thể cần được xả, trạng thái FPU có thể cần phải được lưu lại và trong trường hợp luồng mới được chạy thuộc một quy trình khác với quy trình được ưu tiên, các thanh ghi bảo vệ quản lý bộ nhớ sẽ cần được hoán đổi . Thông thường, hệ điều hành chuyển từ ngăn xếp luồng bị gián đoạn sang ngăn xếp hệ điều hành riêng càng sớm càng tốt để tránh gây ra các yêu cầu ngăn xếp hệ điều hành trên mỗi ngăn xếp luồng.

Khi ngữ cảnh/s được lưu, hệ điều hành có thể 'hoán đổi trong' ngữ cảnh mở rộng cho s/s mới sẽ được chạy. Bây giờ, hệ điều hành cuối cùng có thể tải stack-pointer cho thread mới/s và thực hiện ngắt-trả về để làm cho các chủ đề sẵn sàng mới của nó đang chạy.

Hệ điều hành sau đó không làm gì cả. Các luồng chạy chạy cho đến khi một ngắt khác, (cứng hoặc mềm), xảy ra.

Những điểm quan trọng:

1) kernel hệ điều hành nên được xem xét như một ngắt-handler lớn mà có thể quyết định làm gián đoạn-trở lại một bộ khác nhau của chủ đề hơn so với những người bị gián đoạn.

2) Hệ điều hành có thể kiểm soát và dừng nếu cần thiết, bất kỳ luồng nào trong bất kỳ quy trình nào, bất kể trạng thái của nó ở trạng thái nào hoặc lõi nào có thể đang chạy.

3) Tính năng lập lịch và gửi đi ưu tiên không tạo ra tất cả sự cố đồng bộ hóa vv được đăng tải trên các diễn đàn này. Nhược điểm lớn là phản ứng nhanh ở cấp độ luồng tới các ngắt cứng. Nếu không có điều này, tất cả những ứng dụng hiệu suất cao mà bạn chạy trên PC của bạn - phát trực tuyến video, mạng nhanh, v.v., hầu như không thể.

4) Bộ hẹn giờ hệ điều hành chỉ là một trong một bộ ngắt lớn có thể thay đổi tập hợp các chuỗi đang chạy. 'Time-slicing', (ugh - tôi ghét thuật ngữ đó), giữa các chủ đề sẵn sàng chỉ xảy ra khi máy tính bị quá tải, tức là. tập hợp các chuỗi sẵn sàng lớn hơn số lõi CPU có sẵn để chạy chúng. Nếu bất kỳ văn bản nào có ý định giải thích việc lập lịch biểu của hệ điều hành đề cập đến 'cắt thời gian' trước khi 'gián đoạn', điều này có thể gây nhầm lẫn nhiều hơn giải thích. Bộ đếm thời gian gián đoạn chỉ là 'đặc biệt' trong nhiều cuộc gọi hệ thống có thời gian chờ để sao lưu chức năng chính của chúng, (OK, cho sleep(), timeout IS là chức năng chính :).

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