কেবল এই উত্তরটি যুক্ত করা কারণ আমি মনে করি গৃহীত উত্তরগুলি বিভ্রান্তিমূলক হতে পারে। সব ক্ষেত্রে আপনার কোডটি থ্রেড-সেফ হওয়ার জন্য কোথাও notify_one () কল করার আগে আপনাকে মুইটেক্সটিকে লক করতে হবে , যদিও আপনি অবশ্যই বিজ্ঞপ্তি _ * () কে কল করার আগে এটিকে আবার আনলক করতে পারেন।
স্পষ্ট করার জন্য, আপনাকে অপেক্ষা (এল কে) প্রবেশের আগে লকটি নেওয়া উচিত কারণ অপেক্ষা () এলকে আনলক করে এবং লকটি লক না করা থাকলে এটি অপরিজ্ঞাত আচরণ হবে। এই notify_one () সঙ্গে কেস নয়, কিন্তু আপনি কি নিশ্চিতরূপে কল করা হবে না করতে হবে অবহিত: _ * () অপেক্ষার () প্রবেশের আগে এবং mutex আনলক যে কল থাকার; যা আপনি কেবল _ _ (() কে কল করার আগে কেবল একই মুটেক্সটিকে লক করেই করা যেতে পারে।
উদাহরণস্বরূপ, নিম্নলিখিত কেসটি বিবেচনা করুন:
std::atomic_int count;
std::mutex cancel_mutex;
std::condition_variable cancel_cv;
void stop()
{
if (count.fetch_sub(1) == -999)
cv.notify_one();
}
bool start()
{
if (count.fetch_add(1) >= 0)
return true;
stop();
return false;
}
void cancel()
{
if (count.fetch_sub(1000) == 0)
return;
std::unique_lock<std::mutex> lk(cancel_mutex);
cancel_cv.wait(lk);
}
সতর্কতা : এই কোডটিতে একটি বাগ রয়েছে।
ধারণাটি হ'ল: থ্রেডগুলি জোড়ায় স্টার্ট () এবং স্টপ () থামায়, তবে যতক্ষণ না শুরু () সত্য ফিরে আসে ততক্ষণ। উদাহরণ স্বরূপ:
if (start())
{
stop();
}
এক (অন্য) থ্রেডটি এক পর্যায়ে বাতিলকে কল করবে () এবং বাতিল () থেকে ফিরে আসার পরে 'ডু স্টাফ' এ প্রয়োজনীয় বস্তু ধ্বংস করবে। যাইহোক, বাতিল () স্টার্ট () এবং থামার () এর মধ্যে থ্রেড থাকা অবস্থায় ফিরে আসার কথা নয় এবং একবার () বাতিল করলে) এর প্রথম লাইনটি কার্যকর করা, শুরু () সর্বদা মিথ্যা ফিরে আসবে, সুতরাং কোনও নতুন থ্রেড 'do' এ প্রবেশ করবে না স্টাফ 'অঞ্চল।
ঠিক কাজ করে?
যুক্তিটি নিম্নরূপ:
1) যদি কোনও থ্রেড সফলভাবে শুরু () এর প্রথম লাইনটি কার্যকর করে (এবং সুতরাং এটি সত্য হয়ে যাবে) তবে কোনও থ্রেড বাতিল করার প্রথম লাইনটি কার্যকর করেনি () এখনও (আমরা ধরে নিই যে থ্রেডের মোট সংখ্যা 1000 এর চেয়ে অনেক কম ছোট উপায়)।
২) এছাড়াও, যখন কোনও থ্রেড সফলভাবে শুরু () এর প্রথম লাইনটি কার্যকর করেছে, তবে স্টপের প্রথম লাইনটি এখনও নয়) তবে এটি অসম্ভব যে কোনও থ্রেড সফলভাবে বাতিলকরণের প্রথম লাইনটি সম্পাদন করবে () কেবলমাত্র একটি থ্রেড নোট করুন সর্বদা কল বাতিল ()): ফেচ_সুব (1000) দ্বারা ফেরত মান 0 এর চেয়ে বড় হবে।
3) একবার কোনও থ্রেড বাতিল () এর প্রথম লাইনটি কার্যকর করা হলে, শুরুর প্রথম লাইনটি () সর্বদা মিথ্যা ফিরে আসবে এবং একটি থ্রেড কলিং শুরু () আর 'ডু স্টাফ' অঞ্চলে প্রবেশ করবে না।
৪) () শুরু করতে () থামাতে (এবং থামানোর) সংখ্যা সর্বদা ভারসাম্যপূর্ণ, সুতরাং বাতিল করার প্রথম লাইনের () অসফলভাবে কার্যকর হওয়ার পরে, সর্বদা একটি মুহূর্ত থাকবে যেখানে কোনও (শেষ) কলটি থামিয়ে দেবে () কারণ গণনা করে -1000 পৌঁছাতে এবং অতএব নোটিফাই_আন () কল করতে হবে। দ্রষ্টব্য যে কেবল তখনই ঘটতে পারে যখন বাতিল করার প্রথম লাইনের ফলে সেই থ্রেডটি পড়ে যায়।
অনাহারজনিত সমস্যা বাদে যেখানে এতগুলি থ্রেড কল (স্টার্ট) (কল / স্টপ) কল করে যা গণনা কখনও -1000-এ পৌঁছায় না এবং বাতিল () কখনই ফিরে আসে না, এটি সম্ভবত "সম্ভাব্য এবং দীর্ঘস্থায়ী কখনও নয়" হিসাবে গ্রহণযোগ্য, অন্য একটি বাগ রয়েছে:
এটি সম্ভব যে 'ডু স্টাফ' অঞ্চলের ভিতরে একটি থ্রেড রয়েছে, যাক এটি কেবল স্টপ () বলা হচ্ছে; এই মুহুর্তে একটি থ্রেড বাতিল করার প্রথম লাইনটি কার্যকর করে () ফিচার_সুব (1000) এর সাথে মান 1 পড়ার এবং এর মধ্য দিয়ে পড়া। তবে এটি মুটেক্স নেওয়ার আগে এবং / অথবা কলটি অপেক্ষা করার (এল কে) করার আগে, প্রথম থ্রেডটি স্টপ () এর প্রথম লাইনটি সম্পাদন করে, -999 পড়ে এবং সিভি.নোটাইফ_োন () কল করে!
তারপরে অবহিত করার জন্য এই কলটি করা হবে () অপেক্ষা করার আগে আমরা অপেক্ষা করছি () - কন্ডিশনে পরিবর্তনশীল! এবং প্রোগ্রামটি অনির্দিষ্টকালের জন্য ডেড-লক করে।
এই কারণে আমরা অপেক্ষা () না বলা পর্যন্ত আমাদের notify_one () কল করতে সক্ষম হবে না । নোট করুন যে কন্ডিশন ভেরিয়েবলের শক্তিটি এতে নিহিত রয়েছে যে এটি পারমাণবিকভাবে মুটেক্সকে আনলক করতে সক্ষম হয়, notif_one () তে কোনও কল এসেছে কিনা এবং ঘুমাতে যেতে হবে কিনা তা পরীক্ষা করে দেখুন। আপনি এটা গাধা না পারেন, কিন্তু আপনি কি mutex লক রাখা প্রয়োজন যখনই আপনি ভেরিয়েবল পরিবর্তন যে সত্য মিথ্যা থেকে শর্ত পরিবর্তন এবং পারে করা রাখা যখন কারণ জাতি অবস্থার notify_one () কলিং এখানে বর্ণিত মত এটি লক।
এই উদাহরণে তবে কোনও শর্ত নেই। আমি কেন শর্ত হিসাবে 'গণনা == -1000' ব্যবহার করিনি? কারণ এটি এখানে মোটেও আকর্ষণীয় নয়: -১০০০ আদৌ পৌঁছানোর সাথে সাথে আমরা নিশ্চিত যে কোনও নতুন থ্রেড 'ডু স্টাফ' অঞ্চলে প্রবেশ করবে না। তদ্ব্যতীত, থ্রেডগুলি এখনও স্টার্ট () কল করতে পারে এবং বর্ধিত গণনা (-৯৯৯ এবং -৯৯৮ ইত্যাদি) করতে পারে তবে আমরা এটির যত্ন নিই না। কেবলমাত্র গুরুত্বপূর্ণ বিষয়টি হ'ল -1000 পৌঁছেছিল - যাতে আমরা নিশ্চিতভাবে জানতে পারি যে 'ডু স্টাফ' অঞ্চলে আর কোনও থ্রেড নেই। আমরা নিশ্চিত যে নোটিফাই_আোন () কল করার সময় এটিই ঘটেছে, তবে কীভাবে নিশ্চিত করা হবে যে আমরা নোটিফাই_ন () কে তার ম্যুটেক্সটিকে লক করে দেওয়ার আগে কল করব না? Notify_one () এর খুব শীঘ্রই কেবল বাতিল_মুটেক্সকে লক করা অবশ্যই সহায়তা করবে না।
সমস্যা হলো, সত্ত্বেও আমরা একটি শর্ত জন্য অপেক্ষা করছি না যে, এখনও হয় একটি শর্ত, এবং আমরা mutex লক করার প্রয়োজন
1) শর্তটি পৌঁছানোর আগে 2) আমরা notif_one কল করার আগে।
সঠিক কোডটি তাই হয়ে যায়:
void stop()
{
if (count.fetch_sub(1) == -999)
{
cancel_mutex.lock();
cancel_mutex.unlock();
cv.notify_one();
}
}
[... একই শুরু () ...]
void cancel()
{
std::unique_lock<std::mutex> lk(cancel_mutex);
if (count.fetch_sub(1000) == 0)
return;
cancel_cv.wait(lk);
}
অবশ্যই এটি কেবল একটি উদাহরণ তবে অন্যান্য ক্ষেত্রেও অনেকটা একই রকম; প্রায় সব ক্ষেত্রেই আপনি শর্তযুক্ত ভেরিয়েবল ব্যবহার করেন যেখানে আপনাকে notify_one () কল করার আগে সেই শীঘ্রই (শীঘ্রই) লক থাকা দরকার , অথবা অন্যথায় অপেক্ষা করা কল করার আগে আপনি এটি কল করতে পারেন।
নোট করুন যে আমি এই ক্ষেত্রে notify_one () কল করার আগে মুটেক্সকে আনলক করেছি, কারণ অন্যথায় নোটিফাইফোন () নামক কলটি কন্ডিশন ভেরিয়েবলের অপেক্ষায় থ্রেডটি জাগিয়ে তোলে যা পরে মুটেক্সকে নেওয়ার চেষ্টা করবে এবং ব্লক, আমরা আবার মিটেক্স প্রকাশ করার আগে। এটি প্রয়োজনের তুলনায় সামান্য ধীর।
এই উদাহরণটি বিশেষভাবে বিশেষ ছিল যে লাইনটি শর্তটি পরিবর্তন করে একই থ্রেড দ্বারা চালিত হয় যা অপেক্ষা () বলে।
আরও স্বাভাবিক ক্ষেত্রে এটি হয় যে কোনও থ্রেড কেবল শর্তটি সত্য হওয়ার জন্য অপেক্ষা করে এবং অন্য থ্রেডটি সেই অবস্থার সাথে জড়িত ভেরিয়েবলগুলি পরিবর্তন করার আগে লকটি নেয় (সম্ভবত এটি সত্য হয়ে যায়) to যে ক্ষেত্রে mutex হয় অব্যবহিত পূর্বে (এবং পরে) লক শর্ত সত্য হয়ে ওঠে - তাই এটি সম্পূর্ণই ঠিক আছে শুধু অবহিত কল করার আগে mutex আনলক করতে: _ * () যে ক্ষেত্রে।
wait morphing
অপ্টিমাইজেশান সক্ষম করতে ) এই লিঙ্কটিতে থাম্বের বিধিটি ব্যাখ্যা করা হয়েছে: আরও অনুমানযোগ্য ফলাফলের জন্য 2 টিরও বেশি থ্রেড সহ পরিস্থিতিগুলিতে লকটি দিয়ে জানান দেওয়া ভাল।