আমি পরিস্থিতিটি এখন কিছুটা ভালভাবে বুঝতে পেরেছি (এখানে উত্তরগুলির কারণে অল্প পরিমাণে নয়!), তাই আমি ভেবেছিলাম আমি নিজের লেখাটি একটু যোগ করব।
সি ++ 11 এ দুটি স্বতন্ত্র, যদিও সম্পর্কিত ধারণা রয়েছে: অ্যাসিনক্রোনাস গণনা (একটি ফাংশন যা অন্য কোথাও বলা হয়), এবং একযোগে সম্পাদন (একটি থ্রেড , যা কিছু একযোগে কাজ করে)। দুটি হ'ল কিছুটা অরথোগোনাল ধারণা। অ্যাসিঙ্ক্রোনাস গণনা হ'ল ফাংশন কলের কেবল আলাদা স্বাদ, যখন একটি থ্রেড একটি বাস্তবায়ন প্রসঙ্গ। থ্রেডগুলি তাদের নিজস্বভাবে কার্যকর, তবে এই আলোচনার উদ্দেশ্যটির জন্য, আমি তাদের বাস্তবায়ন বিশদ হিসাবে বিবেচনা করব।
অ্যাসিনক্রোনাস গণনার জন্য বিমূর্তনের একটি শ্রেণিবিন্যাস রয়েছে। উদাহরণস্বরূপ, ধরুন আমাদের একটি ফাংশন রয়েছে যা কিছু যুক্তি দেখায়:
int foo(double, char, bool);
প্রথমে, আমাদের কাছে টেমপ্লেট রয়েছে std::future<T>
, যা ভবিষ্যতের ধরণের মানের প্রতিনিধিত্ব করে T
। সদস্যটি ফাংশনের মাধ্যমে মানটি পুনরুদ্ধার করা যায় get()
, যা ফলাফলটির জন্য অপেক্ষা করে কার্যকরভাবে প্রোগ্রামটিকে সিঙ্ক্রোনাইজ করে। বিকল্পভাবে, একটি ভবিষ্যত সমর্থন করে wait_for()
, যা ফলাফল ইতিমধ্যে উপলব্ধ কিনা তা অনুসন্ধানের জন্য ব্যবহার করা যেতে পারে। ফিউচারগুলি সাধারণ রিটার্নের ধরণের অ্যাসিনক্রোনাস ড্রপ-ইন প্রতিস্থাপন হিসাবে ভাবা উচিত। আমাদের উদাহরণ ফাংশন জন্য, আমরা একটি আশা করিstd::future<int>
।
এখন, স্তরক্রমের দিকে, সর্বোচ্চ থেকে নিম্নতম স্তরের:
std::async
: অ্যাসিঙ্ক্রোনাস গণনা সম্পাদনের সর্বাধিক সুবিধাজনক এবং সোজা-সামনের উপায় হ'ল async
ফাংশন টেমপ্লেটের মাধ্যমে যা ম্যাচের ভবিষ্যতে অবিলম্বে ফিরে আসে:
auto fut = std::async(foo, 1.5, 'x', false); // is a std::future<int>
বিশদগুলির উপর আমাদের খুব কম নিয়ন্ত্রণ রয়েছে। বিশেষত, আমরা এমনকি ফাংশনটি যুগপত, ক্রমিকভাবে get()
বা অন্য কোনও কালো যাদু দ্বারা সম্পাদিত হয় কিনা তাও জানি না । তবে প্রয়োজনে ফলাফল সহজেই পাওয়া যায়:
auto res = fut.get(); // is an int
আমরা এখন বিবেচনা করতে পারি যে কীভাবে এমন কিছু বাস্তবায়ন করা যায় async
তবে এটি একটি ফ্যাশনে আমরা নিয়ন্ত্রণ করি। উদাহরণস্বরূপ, আমরা জোর দিয়ে বলতে পারি যে ফাংশনটি একটি পৃথক থ্রেডে কার্যকর করা হবে। আমরা ইতিমধ্যে জানি যে আমরা std::thread
ক্লাসের মাধ্যমে একটি পৃথক থ্রেড সরবরাহ করতে পারি ।
বিমূর্তনের পরবর্তী নিম্ন স্তরের ঠিক এটি করে: std::packaged_task
। এটি একটি টেমপ্লেট যা কোনও ফাংশনকে আবৃত করে এবং ফাংশনগুলির রিটার্ন মানটির জন্য ভবিষ্যত সরবরাহ করে তবে অবজেক্টটি নিজেই কলযোগ্য এবং এটিকে কল করা ব্যবহারকারীর বিবেচনার ভিত্তিতে। আমরা এটির মতো এটি সেট আপ করতে পারি:
std::packaged_task<int(double, char, bool)> tsk(foo);
auto fut = tsk.get_future(); // is a std::future<int>
আমরা যখন টাস্কটি কল করি এবং কলটি শেষ হয় তখন ভবিষ্যত প্রস্তুত হয়। এটি আলাদা থ্রেডের জন্য আদর্শ কাজ। কাজটি থ্রেডে স্থানান্তরিত করার জন্য আমাদের কেবল নিশ্চিত করতে হবে :
std::thread thr(std::move(tsk), 1.5, 'x', false);
থ্রেড সঙ্গে সঙ্গে চলতে শুরু করে। আমরা হয় detach
তা রাখতে পারি, বা join
সুযোগের শেষে থাকতে পারি, বা যখনই (যেমন অ্যান্টনি উইলিয়ামসের scoped_thread
মোড়ক ব্যবহার করে , যা সত্যই স্ট্যান্ডার্ড লাইব্রেরিতে থাকা উচিত)। ব্যবহারের বিশদটি std::thread
আমাদের এখানে উদ্বিগ্ন করে না; শুধু যোগদান বা thr
অবশেষে বিচ্ছেদ নিশ্চিত । গুরুত্বপূর্ণ বিষয় হ'ল যখনই ফাংশন কল শেষ হয়, তখন আমাদের ফলাফল প্রস্তুত থাকে:
auto res = fut.get(); // as before
এখন আমরা সর্বনিম্ন পর্যায়ে নেমে করছি: আমরা চাই কিভাবে বাস্তবায়ন প্যাকেজ কাজের? এটি এখানেই std::promise
আসে The প্রতিশ্রুতি হ'ল ভবিষ্যতের সাথে যোগাযোগের জন্য বিল্ডিং ব্লক। প্রধান পদক্ষেপগুলি হ'ল:
কলিং থ্রেড একটি প্রতিশ্রুতি দেয়।
কলিং থ্রেড প্রতিশ্রুতি থেকে ভবিষ্যত প্রাপ্ত করে।
প্রতিশ্রুতি, ফাংশন আর্গুমেন্ট সহ, একটি পৃথক থ্রেডে সরানো হয়।
নতুন থ্রেডটি কার্য সম্পাদন করে এবং প্রতিশ্রুতি পূর্ণ করে।
মূল থ্রেড ফলাফল পুনরুদ্ধার করে।
উদাহরণ হিসাবে, এখানে আমাদের নিজস্ব নিজস্ব "প্যাকেজড টাস্ক" রয়েছে:
template <typename> class my_task;
template <typename R, typename ...Args>
class my_task<R(Args...)>
{
std::function<R(Args...)> fn;
std::promise<R> pr; // the promise of the result
public:
template <typename ...Ts>
explicit my_task(Ts &&... ts) : fn(std::forward<Ts>(ts)...) { }
template <typename ...Ts>
void operator()(Ts &&... ts)
{
pr.set_value(fn(std::forward<Ts>(ts)...)); // fulfill the promise
}
std::future<R> get_future() { return pr.get_future(); }
// disable copy, default move
};
এই টেম্পলেটটির ব্যবহার মূলত একই রকম std::packaged_task
। নোট করুন যে পুরো টাস্কটি সরিয়ে নেওয়া প্রতিশ্রুতিটি সরিয়ে দেয়। অধিকতর অ্যাডহক পরিস্থিতিতে, কেউ প্রতিশ্রুতিযুক্ত বস্তুকে স্পষ্টভাবে নতুন থ্রেডে নিয়ে যেতে পারে এবং এটি থ্রেড ফাংশনের একটি ফাংশন আর্গুমেন্ট হিসাবে তৈরি করতে পারে, তবে উপরের মতো একটি টাস্ক র্যাপারটি আরও নমনীয় এবং স্বল্প হস্তক্ষেপের মতো মনে হয়।
ব্যতিক্রম করা
প্রতিশ্রুতি নিবিড়ভাবে ব্যতিক্রম সম্পর্কিত। একমাত্র প্রতিশ্রুতি দেওয়ার ইন্টারফেসটি তার রাজ্যটি সম্পূর্ণরূপে জানাতে যথেষ্ট নয়, তাই যখনই কোনও প্রতিশ্রুতির কোনও ক্রিয়াকলাপটি বোঝায় না তখন ব্যতিক্রমগুলি ছুঁড়ে দেওয়া হয়। সমস্ত ব্যতিক্রম প্রকারের std::future_error
, যা থেকে প্রাপ্ত std::logic_error
। প্রথমে কিছু প্রতিবন্ধকতার বর্ণনা:
একটি ডিফল্ট-নির্মিত প্রতিশ্রুতি নিষ্ক্রিয়। নিষ্ক্রিয় প্রতিশ্রুতি পরিণতি ছাড়াই মারা যেতে পারে।
ভবিষ্যতের মাধ্যমে প্রাপ্ত হলে একটি প্রতিশ্রুতি সক্রিয় হয় get_future()
। তবে, কেবলমাত্র একটি ভবিষ্যত পাওয়া যেতে পারে!
একটি প্রতিশ্রুতি হয় হয় এর মাধ্যমে সন্তুষ্ট হতে হবে set_value()
বা set_exception()
তার জীবনকাল শেষ হওয়ার আগেই তার ব্যতিক্রম সেট করতে হবে যদি তার ভবিষ্যতটি গ্রাস করতে হয়। একটি সন্তুষ্ট প্রতিশ্রুতি পরিণতি ছাড়াই মারা যেতে পারে এবং get()
ভবিষ্যতে উপলব্ধ হয়। একটি ব্যতিক্রম সহ একটি প্রতিশ্রুতি get()
ভবিষ্যতে ডেকে সঞ্চিত ব্যতিক্রম বাড়িয়ে তুলবে । যদি প্রতিশ্রুতি কোনও মূল্য বা ব্যতিক্রম না দিয়ে মারা যায়, get()
ভবিষ্যতের দিকে আহ্বান করা একটি "ভাঙা প্রতিশ্রুতি" ব্যতিক্রম উত্থাপন করবে।
এই বিভিন্ন ব্যতিক্রমী আচরণগুলি প্রদর্শনের জন্য এখানে একটি সামান্য টেস্ট সিরিজ দেওয়া আছে। প্রথম, জোতা:
#include <iostream>
#include <future>
#include <exception>
#include <stdexcept>
int test();
int main()
{
try
{
return test();
}
catch (std::future_error const & e)
{
std::cout << "Future error: " << e.what() << " / " << e.code() << std::endl;
}
catch (std::exception const & e)
{
std::cout << "Standard exception: " << e.what() << std::endl;
}
catch (...)
{
std::cout << "Unknown exception." << std::endl;
}
}
এখন পরীক্ষা।
কেস 1: নিষ্ক্রিয় প্রতিশ্রুতি
int test()
{
std::promise<int> pr;
return 0;
}
// fine, no problems
কেস 2: সক্রিয় প্রতিশ্রুতি, অব্যবহৃত
int test()
{
std::promise<int> pr;
auto fut = pr.get_future();
return 0;
}
// fine, no problems; fut.get() would block indefinitely
কেস 3: অনেক বেশি ফিউচার
int test()
{
std::promise<int> pr;
auto fut1 = pr.get_future();
auto fut2 = pr.get_future(); // Error: "Future already retrieved"
return 0;
}
মামলা 4: সন্তুষ্ট প্রতিশ্রুতি
int test()
{
std::promise<int> pr;
auto fut = pr.get_future();
{
std::promise<int> pr2(std::move(pr));
pr2.set_value(10);
}
return fut.get();
}
// Fine, returns "10".
কেস 5: অত্যধিক সন্তুষ্টি
int test()
{
std::promise<int> pr;
auto fut = pr.get_future();
{
std::promise<int> pr2(std::move(pr));
pr2.set_value(10);
pr2.set_value(10); // Error: "Promise already satisfied"
}
return fut.get();
}
আছে যদি একাধিক একই ব্যতিক্রম ফেলা হয় পারেন এর set_value
বা set_exception
।
মামলা 6: ব্যতিক্রম
int test()
{
std::promise<int> pr;
auto fut = pr.get_future();
{
std::promise<int> pr2(std::move(pr));
pr2.set_exception(std::make_exception_ptr(std::runtime_error("Booboo")));
}
return fut.get();
}
// throws the runtime_error exception
মামলা 7: ভাঙা প্রতিশ্রুতি
int test()
{
std::promise<int> pr;
auto fut = pr.get_future();
{
std::promise<int> pr2(std::move(pr));
} // Error: "broken promise"
return fut.get();
}