2008-12-02 46 views
5

Nếu tôi sẽ viết:Làm thế nào để phát hiện một cuộc gọi Selector.wakeup

 
int selectedChannels = selector.select(); 
Set selectedKeys = selector.selectedKeys(); 
if (selectedChannels != selectedKeys.size()) { 
    // Selector.select() returned because of a call to Selector.wakeup() 
    // so do synchronization. 
} 
// Continue with handling selected channels. 

sẽ nó một cách chính xác phát hiện các wakeup-gọi?

Backgroundinformation:

Tôi đang viết một máy chủ mà hầu hết thời gian chỉ nhận được gói dữ liệu và lưu trữ chúng trong một tập tin. Rất hiếm khi ứng dụng có nhu cầu gửi cho chính nó một gói đặc biệt. Đối với điều này nó khởi tạo một kết nối (từ một thread khác nhau) vào socket server:

 
SocketChannel channel = SocketChannel.open(); 
channel.configureBlocking(false); 
channel.connect(new InetSocketAddress(InetAddress.getLocalHost(), PORT)); 
selector.wakeup(); 
SelectionKey key = channel.register(selector, SelectionKey.OP_CONNECT); 

Vấn đề là SelectableChannel.register() có thể ngăn chặn nếu các chủ đề chính là đã có trong Selector.select(). Để ngăn chặn điều này xảy ra, tôi gọi Selector.wakeup() để cho thread chính trả về sớm từ select(). Để đảm bảo luồng khác có cơ hội hoàn thành cuộc gọi đăng ký, tôi sẽ phải đồng bộ hóa chủ đề chính, nhưng tôi sẽ phải thực hiện sau mỗi lần trả về từ select(). Nếu tôi có thể phát hiện xem nó trả về từ select() vì một cuộc gọi wakeup(), thì tôi có thể tối ưu hóa nó cho trường hợp này. Vì vậy, về mặt lý thuyết đoạn mã hàng đầu nên hoạt động, nhưng tôi đã tự hỏi liệu nó có chỉ làm như vậy, bởi vì nó dựa vào một số hành vi không xác định không? Không phải là điều đó.

Cảm ơn mọi gợi ý.

+0

Động lực của bạn để tránh khóa là gì? Có phải bạn đang lo lắng về thời gian thực hiện hay bạn gọi select() ở nhiều nơi và muốn tránh trùng lặp mã? –

+0

Tôi lo lắng về thời gian thực hiện, mặc dù đây có thể là điểm tranh luận vì select() và thanh ghi() đã đồng bộ hóa trên các khóa đã đăng ký. – BugSlayer

+0

Đồng ý, đáng lo ngại về điều này ở đây là một ví dụ điển hình về tối ưu hóa sớm. Trừ khi bạn đang thực sự nói về dung sai micro giây duy nhất bạn sẽ không nhận thấy bất kỳ sự suy giảm nào. Tôi sẽ ném một câu trả lời bằng cách sử dụng khóa. –

Trả lời

3

Tôi cho rằng đoạn mã được đề xuất sẽ không hoạt động theo nguyên tắc, theo hợp đồng của Selector#select()Selector#selectedKeys(). Từ Selector:

  • Các lựa chọn-key bộ là tập hợp các phím như rằng kênh của mỗi chủ chốt đã được phát hiện để sẵn sàng cho ít nhất một trong những hoạt động được xác định trong bộ lợi ích của chính trong một hoạt động lựa chọn trước. Tập này được trả về bằng phương thức selectedKeys.
public abstract int select(long timeout) 
       throws IOException 
    Returns: 
     The number of keys, possibly zero, whose ready-operation sets were 
     updated 

Khi tôi đọc rằng, kích thước của tập selectedKeys nên luôn luôn bằng với số được trả về bởi select theo định nghĩa. Tôi đã nhận thấy - như bạn có thể có - rằng một số triển khai không hoàn toàn theo tài liệu và trên thực tế, selectedKeys trả về tất cả các khóa có bộ sẵn sàng hoạt động đã cập nhật, ngay cả khi chúng không được cập nhật trong khi gọi tới select. Chỉ báo duy nhất khác mà lựa chọn thức dậy do một cuộc gọi đến wakeup có thể là số lượng các phím bằng 0; tuy nhiên một trong hai phương pháp sẽ không đáng tin cậy, tốt nhất.

Cách thông thường để xử lý điều này, như được ngụ ý, thông qua kiểm soát đồng thời. Tôi sẽ không lo lắng về thời gian thực hiện ở đây; đây là một ví dụ cổ điển của premature optimization. Trừ khi bạn đang thực sự lo lắng về dung sai micro giây đơn, bạn sẽ không nhận thấy bất kỳ sự suy giảm nào - và nếu bạn lo lắng về mức độ khoan dung đó, thì Selector sẽ không đủ đáng tin cậy cho bạn.

Dưới đây là một ví dụ về cơ chế thông thường cho điều này, sử dụng một ReentrantLock để hoàn thành đồng thời phù hợp:

ReentrantLock selectorGuard; 
Selector selector; 

private void doSelect() { 
    // Don't enter a select if another thread is in a critical block 
    selectorGuard.lock(); 
    selectorGuard.unlock(); 

    selector.select(); 
    Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator(); 

    while(keyIter.hasNext()) { 

     SelectionKey key = keyIter.next(); 
     keyIter.remove(); 

     // Process key 
    } 
} 

private void addToSelector() { 

    // Lock the selector guard to prevent another select until complete 
    selectorGuard.lock(); 

    try { 
     selector.wakeup(); 

     // Do logic that registers channel with selector appropriately 

    } finally { 
     selectorGuard.unlock(); 
    } 
} 
+0

Lưu ý: * đã được cập nhật *. Điều đó chỉ có nghĩa là kết quả 'select()' phải bằng kích thước của tập hợp khóa đã chọn nếu nó được để trống trước khi bạn gọi là 'select()'. Nếu bạn không xóa nó sau mỗi 'select()', hoặc luân phiên loại bỏ từng 'SelectionKey' khi bạn xử lý nó, bạn sẽ nhận được các kết quả khác nhau. Đây không phải là vấn đề triển khai khác nhau, nó là một lỗi ứng dụng. – EJP

+0

@EJP Đã lâu lắm rồi, nhưng tôi tin rằng tập hợp khóa đã chọn luôn trống trước khi gọi đến 'select()'. Thay vào đó, hành vi như sau: a. bắt đầu với một tập trống, b. gọi 'select()', trả về 1, c. gọi 'selectedKeys()', trả về một tập hợp kích thước 2. –

0

Tôi không hiểu tại sao mã của bạn hoạt động nói chung.

Tại sao không chỉ kiểm tra volatile sau select?

+0

Tôi đang tìm một giải pháp 'ngầm', có nghĩa là tôi muốn sử dụng thông tin sẵn có mà không có bất kỳ dấu hiệu 'tường minh' nào như biến boolean. – BugSlayer

-1

Bạn thực sự không thể chắc chắn rằng lý do duy nhất mà bộ chọn thức dậy là do cuộc gọi báo thức. Bạn cũng có thể có hoạt động socket.

Vì vậy, bạn cần phải làm cho người gọi thức dậy cũng làm một cái gì đó như thiết lập một boolean dễ bay hơi để chỉ ra mong muốn của mình cho sự chú ý. Vòng lặp chọn có thể kiểm tra giá trị của boolean này mỗi khi nó thức dậy.

+0

Có thể. Nếu có hoạt động ổ cắm, nó sẽ trả về một giá trị dương. – EJP

0

Nếu select() lợi nhuận bằng không, hoặc là nó timed out hoặc nó đã được đánh thức.

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