2013-07-31 47 views
43

Tôi đang cố gắng khám phá tất cả các tùy chọn của tiêu chuẩn C++ 11 mới, trong khi sử dụng std :: async và đọc định nghĩa của nó, tôi nhận thấy 2 điều, ít nhất là dưới linux với gcc 4.8.1:Tại sao tôi nên sử dụng std :: async?

  • nó được gọi là async, nhưng nó có một "hành vi liên tục" thực sự, về cơ bản ở hàng nơi bạn gọi tương lai gắn liền với chức năng async của bạn foo, các khối chương trình cho đến khi thực hiện foo nó đã hoàn thành.
  • tùy thuộc vào chính xác thư viện bên ngoài như các giải pháp khác và các giải pháp không chặn tốt hơn, nghĩa là pthread, nếu bạn muốn sử dụng std::async, bạn cần pthread.

vào thời điểm này, tôi tự hỏi tại sao chọn std :: async hơn cả một tập hợp đơn giản của các functors? Đó là một giải pháp mà thậm chí không quy mô chút nào, tương lai bạn gọi càng nhiều, chương trình của bạn càng ít đáp ứng.

Tôi có thiếu gì đó không? Bạn có thể hiển thị một ví dụ được cấp để được thực hiện theo cách không đồng bộ, không chặn, không?

+0

@rsaxvc nơi bạn gọi hàm async, ví dụ 'future.get()' – user2485710

+1

Giả định của bạn sai. async() được thiết kế để cung cấp một điểm đồng bộ hóa để bạn có thể nhận được kết quả của hàm đang được đánh giá không đồng bộ. – DanielKO

+0

Ý tưởng hiện tại của C++ về "async" không thực sự mang lại bất kỳ điều gì đáng kể (ngoài tính di động) cho bảng so với các tùy chọn khác. Một khi nó được hỗ trợ tiếp tục hoàn thành (đó là một phần quan trọng của những gì hầu như mọi nền tảng khác gọi là "không đồng bộ"), tôi nghi ngờ bạn sẽ tìm thấy nhiều công dụng hơn cho nó. –

Trả lời

41

Nếu bạn cần kết quả hoạt động không đồng bộ, khi đó bạn để chặn, bất kể bạn sử dụng thư viện nào. Ý tưởng là bạn có thể chọn khi nào để chặn, và, hy vọng khi bạn làm điều đó, bạn chặn một thời gian không đáng kể bởi vì tất cả công việc đã được thực hiện.

Cũng lưu ý rằng std::async có thể được khởi chạy với các chính sách std::launch::async hoặc std::launch::deferred. Nếu bạn không chỉ định nó, việc thực hiện được phép chọn, và nó cũng có thể chọn sử dụng đánh giá hoãn lại, điều này sẽ dẫn đến tất cả công việc đang được thực hiện khi bạn cố gắng nhận kết quả từ tương lai, dẫn đến một khối dài hơn . Vì vậy, nếu bạn muốn đảm bảo rằng công việc được thực hiện không đồng bộ, hãy sử dụng std::launch::async.

+0

Tại sao "cần" chặn? tại sao không giả định rằng một hàm async cần phải được thực thi càng sớm càng tốt và cho khả năng truy xuất kết quả, và quan trọng nhất, nếu C++ 11 đang đề xuất một mô hình luồng mới tại sao không sử dụng nó trong định nghĩa của std :: async? std :: async thậm chí không cấp nhiều hơn 1 chuỗi sẽ được sử dụng. Ngoài ra các phương thức AFAIK theo tiêu chuẩn :: khởi động :: chỉ là trình bao bọc cho tương lai, đó là khái niệm chặn thực thi tương tự. – user2485710

+1

@ user2485710 nó cần chặn khi bạn truy xuất kết quả, * nếu * bạn cần kết quả trong chuỗi khởi chạy. Nó không thể sử dụng kết quả nếu kết quả chưa sẵn sàng. Vì vậy, nếu bạn đi để có được kết quả, bạn phải đợi cho đến khi nó đã sẵn sàng. Nếu nó đã sẵn sàng, thì thời gian chặn sẽ không đáng kể. – juanchopanza

+1

@ user2485710 cũng lưu ý, theo kinh nghiệm của tôi, 'std :: async' có hành vi không đồng bộ, như mong đợi. – juanchopanza

3

Trong reference: http://en.cppreference.com/w/cpp/thread/async

Nếu cờ async được thiết lập (tức là chính sách & std :: phóng :: async = 0!), Sau đó async thực thi hàm f trên một sợi riêng biệt thực hiện như thể sinh ra bởi std :: thread (f, args ...), ngoại trừ nếu hàm f trả về giá trị hoặc ném ngoại lệ, nó được lưu trữ trong trạng thái được chia sẻ trạng thái thông qua std :: future async trả lại cho người gọi .

Đây là tài sản tốt để lưu hồ sơ ngoại lệ.

+0

đó không phải là một đảm bảo, theo tiêu chuẩn tôi không thể thấy lý do tại sao một C++ 11 nhất định thực hiện buộc phải sử dụng một cái gì đó khác với một "thông thường" đơn luồng thực hiện. – user2485710

+2

@ user2485710 nó bị buộc bởi vì tiêu chuẩn nói như vậy: "như thể trong một chủ đề mới thực hiện" (§30.6.8/3). –

+1

@ user2485710: và "như thể trong một chuỗi mới thực hiện" có hiệu ứng quan sát được, ví dụ như người dân địa phương thread. Tất nhiên, nếu trình biên dịch có thể chứng minh rằng nó không thể quan sát được, nó hoàn toàn miễn phí để thực thi nó một cách đồng bộ theo nguyên tắc as-if, nhưng đây là vấn đề về chất lượng thực hiện. – JohannesD

10

Tôi nghĩ vấn đề của bạn là với std::future nói rằng nó chặn trên get. Nó chỉ chặn nếu kết quả chưa sẵn sàng.

Nếu bạn có thể sắp xếp kết quả đã sẵn sàng, đây không phải là vấn đề.

Có nhiều cách để biết kết quả đã sẵn sàng.Bạn có thể thăm dò ý kiến ​​future và yêu cầu nó (tương đối đơn giản), bạn có thể sử dụng khóa hoặc dữ liệu nguyên tử để chuyển tiếp thực tế là đã sẵn sàng, bạn có thể xây dựng một khung để phân phối các mục "hoàn thành" future thành hàng đợi mà người tiêu dùng có thể tương tác , bạn có thể sử dụng tín hiệu của một số loại (đó là chỉ chặn trên nhiều thứ cùng một lúc, hoặc bỏ phiếu).

Hoặc, bạn có thể hoàn thành tất cả công việc bạn có thể thực hiện tại địa phương và sau đó chặn trên công việc từ xa.

Ví dụ: hãy tưởng tượng một loại hợp nhất đệ quy song song. Nó chia mảng thành hai khối, sau đó sắp xếp async trên một đoạn trong khi sắp xếp các đoạn khác. Một khi nó được thực hiện phân loại một nửa của nó, các chủ đề ban đầu không thể tiến bộ cho đến khi nhiệm vụ thứ hai được hoàn thành. Vì vậy, nó có một .get() và khối. Khi cả hai nửa đã được sắp xếp, sau đó nó có thể thực hiện hợp nhất (theo lý thuyết, việc hợp nhất có thể được thực hiện ít nhất một phần song song).

Tác vụ này hoạt động như một tác vụ tuyến tính đối với những người tương tác với nó ở bên ngoài - khi nó được thực hiện, mảng được sắp xếp.

Sau đó, chúng tôi có thể bọc nó vào một nhiệm vụ std::async và có một mảng được sắp xếp future. Nếu chúng ta muốn, chúng ta có thể thêm vào một thủ tục ký tên để cho chúng tôi biết rằng future đã hoàn tất, nhưng điều đó chỉ có ý nghĩa nếu chúng ta có một chuỗi đang chờ tín hiệu.

56
  • nó được gọi là async, nhưng nó có một thực sự "hành vi tuần tự",

Không, nếu bạn sử dụng các chính sách std::launch::async sau đó nó chạy không đồng bộ trong một chủ đề mới. Nếu bạn không chỉ định chính sách, thì có thể sẽ chạy trong một chuỗi mới.

về cơ bản trong hàng mà bạn gọi tương lai được liên kết với hàm không đồng bộ của bạn foo, chương trình sẽ chặn cho đến khi quá trình thực thi hoàn tất.

Nó chỉ chặn nếu foo chưa hoàn thành, nhưng nếu nó chạy không đồng bộ (ví dụ: vì bạn sử dụng chính sách std::launch::async) có thể đã hoàn tất trước khi bạn cần.

  • nó phụ thuộc vào cùng một thư viện chính xác bên ngoài như những người khác, và tốt hơn, giải pháp non-blocking, có nghĩa pthread, nếu bạn muốn sử dụng std :: async bạn cần pthread.

sai, nó không nhất thiết phải được thực hiện sử dụng pthreads (và trên Windows nó không phải là, nó sử dụng các tính năng ConcRT.)

vào thời điểm này nó là điều tự nhiên đối với tôi hỏi tại sao chọn std :: async hơn cả một bộ đơn giản của các functors?

Bởi vì nó đảm bảo an toàn luồng và truyền bá ngoại lệ trên các chuỗi. Bạn có thể làm điều đó với một bộ đơn giản của các functors?

Đó là một giải pháp thậm chí không quy mô chút nào, càng có nhiều tương lai bạn gọi, chương trình của bạn càng ít đáp ứng.

Không nhất thiết. Nếu bạn không chỉ định chính sách khởi chạy thì việc triển khai thông minh có thể quyết định có nên bắt đầu một chuỗi mới hay trả về một hàm bị trì hoãn hoặc trả về một cái gì đó quyết định sau này khi có thêm tài nguyên. Bây giờ, đúng là với việc thực hiện GCC, nếu bạn không cung cấp chính sách khởi chạy thì với bản phát hành hiện tại, nó sẽ không bao giờ chạy trong một chủ đề mới (có một số bugzilla report cho điều đó) nhưng đó là tài sản của việc triển khai đó, chứ không phải là trong tổng số std::async nói chung. Bạn không nên nhầm lẫn các đặc điểm kỹ thuật trong tiêu chuẩn với một thực hiện cụ thể. Việc đọc triển khai thực hiện một thư viện chuẩn là một cách không tốt để tìm hiểu về C++ 11.

Bạn có thể hiển thị ví dụ được cấp để được thực thi theo cách không đồng bộ, không chặn, không?

này không nên chặn:

auto fut = std::async(std::launch::async, doSomethingThatTakesTenSeconds); 
auto result1 = doSomethingThatTakesTwentySeconds(); 
auto result2 = fut.get(); 

Bằng cách xác định các chính sách ra mắt bạn buộc thực hiện không đồng bộ, và nếu bạn làm các công việc khác trong khi nó đang thực hiện thì kết quả sẽ sẵn sàng khi bạn cần nó.

+2

Câu trả lời rất hay! Tôi có một câu hỏi về ví dụ cuối cùng: mặc dù bạn có thể buộc 'doSomethingThatTakesTenSeconds' được khởi chạy trong một chuỗi riêng biệt, nhưng bạn không thể ép buộc nó chạy" ngay lập tức ", đúng không? Nếu đó là trường hợp, thì 'fut.get()' vẫn có thể chặn. – GuLearn

+15

Có, nhưng nếu mất quá nhiều thời gian để một luồng mới bắt đầu thì hệ thống của bạn bị quá tải quá mức hoặc bộ lập lịch của nó là rác. –

+1

[Tôi đo trên coliru] (http://coliru.stacked-crooked.com/a/014dac336cc8374c) có khoảng 100µs đến 300µs độ trễ. Khoảng 33% của mili giây max là tốt. Tôi sẽ tò mò muốn biết những gì mà mã trên cửa sổ VS vs linux g + + trên cùng một hộp. – doug65536

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