কন্ডিশন_ওয়্যারিয়েবল.নোটাইফাইএনে () কল করার আগে আমাকে কী লক অর্জন করতে হবে?


90

এর ব্যবহার সম্পর্কে আমি কিছুটা বিভ্রান্ত std::condition_variable। আমি বুঝতে পারছি আমি তৈরি করতে unique_lockএকটি উপর mutexকল করার আগে condition_variable.wait()। আমি কি খুঁজে পাচ্ছি না কিনা আমিও কল করার আগে একটি অনন্য লক অর্জন উচিত notify_one()বা notify_all()

Cppreferences.com- এর উদাহরণগুলি পরস্পরবিরোধী। উদাহরণস্বরূপ, notify_one পৃষ্ঠাটি এই উদাহরণ দেয়:

#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>

std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;

void waits()
{
    std::unique_lock<std::mutex> lk(cv_m);
    std::cout << "Waiting... \n";
    cv.wait(lk, []{return i == 1;});
    std::cout << "...finished waiting. i == 1\n";
    done = true;
}

void signals()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Notifying...\n";
    cv.notify_one();

    std::unique_lock<std::mutex> lk(cv_m);
    i = 1;
    while (!done) {
        lk.unlock();
        std::this_thread::sleep_for(std::chrono::seconds(1));
        lk.lock();
        std::cerr << "Notifying again...\n";
        cv.notify_one();
    }
}

int main()
{
    std::thread t1(waits), t2(signals);
    t1.join(); t2.join();
}

এখানে প্রথমটির জন্য লকটি অর্জিত হয় না notify_one(), তবে দ্বিতীয়টির জন্য অধিগ্রহণ করা হয় notify_one()। উদাহরণ সহ অন্যান্য পৃষ্ঠাগুলি সন্ধান করা হলেও আমি বিভিন্ন জিনিস দেখতে পাই, বেশিরভাগটি লকটি অর্জন করে না।

  • ফোন করার আগে notify_one()আমি কী মুটেক্সকে লক করার জন্য নিজেকে বেছে নিতে পারি এবং কেন আমি এটি লক করা বেছে নেব?
  • প্রদত্ত উদাহরণে, কেন প্রথমটির জন্য কোনও লক নেই notify_one(), তবে পরবর্তী কলগুলির জন্য রয়েছে। এই উদাহরণটি ভুল বা কিছু যুক্তি আছে?

উত্তর:


77

কল করার সময় আপনার কোনও লক ধরে রাখার দরকার নেই condition_variable::notify_one(), তবে এটি এই অর্থে ভুল নয় যে এটি এখনও সঠিকভাবে সংজ্ঞায়িত আচরণ এবং ত্রুটি নয়।

যাইহোক, এটি একটি "হতাশা" হতে পারে যেহেতু অপেক্ষার থ্রেডটি রান্নেবল হয়ে যায় (যদি থাকে) অবিলম্বে নোটিফিং থ্রেডটি যে লকটি ধারণ করে তা লকটি অর্জন করার চেষ্টা করবে। আমি মনে করি কল করার সময় notify_one()বা শর্ত পরিবর্তনের সাথে লকটি যুক্ত হওয়া এড়াতে এটি থাম্বের একটি ভাল নিয়ম notify_all()প্যাথ্রেড মেকেক্স দেখুন : pthread_mutex_unlock () একটি উদাহরণের জন্য প্রচুর সময় ব্যয় করে যেখানে পরিমিতরূপে notify_one()উন্নত পারফরম্যান্সের সমতুল্য কল করার আগে একটি লক প্রকাশ করে ।

মনে রাখবেন যে কোনও সময়ে লুপে lock()কল whileকরা প্রয়োজনীয়, কারণ while (!done)লুপ শর্ত পরীক্ষা করার সময় লকটি ধরে রাখা দরকার । তবে কল করার জন্য এটি ধরে রাখার দরকার নেই notify_one()


2016-02-27 : রেসের শর্ত আছে কিনা তা সম্পর্কে মন্তব্যগুলিতে কিছু প্রশ্নের সমাধানের জন্য বড় আপডেট notify_one()কলটির জন্য সহায়তা নয় । আমি জানি এই আপডেটটি দেরিতে হয়েছে কারণ প্রায় দুই বছর আগে প্রশ্নটি জিজ্ঞাসা করা হয়েছিল, তবে আমি যদি @ কুকির একটি সম্ভাব্য জাতি অবস্থার বিষয়ে প্রশ্নটি signals()সম্বোধন করতে চাই তবে যদি প্রযোজক ( এই উদাহরণস্বরূপ) notify_one()গ্রাহকের ঠিক আগে ফোন করেন ( waits()উদাহরণস্বরূপ) কল করতে সক্ষম wait()

মূল কীটি হয় তা i- এটি সেই বস্তু যা প্রকৃতপক্ষে নির্দেশ করে যে ভোক্তার "কাজ" করতে হবে কিনা। condition_variableশুধু একটি ভোক্তা দক্ষতার কোন পরিবর্তন জন্য অপেক্ষা যাক প্রক্রিয়া i

আপডেট করার সময় প্রযোজককে লকটি iধরে রাখতে হবে এবং চেক করা iএবং কল করার সময় গ্রাহককে অবশ্যই লকটি ধরে রাখতে হবে condition_variable::wait()(যদি এটি কিছুক্ষণ অপেক্ষা করার প্রয়োজন হয়)। এই ক্ষেত্রে, কীটি হ'ল মূল বিষয়টি হ'ল লকটি ধরে রাখার ক্ষেত্রে একই অবস্থা হওয়া উচিত (প্রায়শই একটি সমালোচনামূলক বিভাগ বলা হয়) যখন গ্রাহক এই চেক-অ্যান্ড ওয়েট করেন। যেহেতু সমালোচক বিভাগটি অনুষ্ঠিত হয় যখন উত্পাদক আপডেট হয় iএবং যখন গ্রাহক চেক-ও-ওয়েট করেন i, iকখন ভোক্তা চেক করে iএবং কখন এটি কল করে তার মধ্যে কোনও পরিবর্তন করার সুযোগ নেই condition_variable::wait()। কন্ডিশন ভেরিয়েবলের সঠিক ব্যবহারের জন্য এটি ক্রুজ।

সি ++ স্ট্যান্ডার্ড বলছে যে শর্ত_ পরিবর্তনশীল :: অপেক্ষা () যখন একটি প্রিকেট (যেমন এই ক্ষেত্রে) হিসাবে ডাকা হয় তখন নীচের মত আচরণ করে:

while (!pred())
    wait(lock);

গ্রাহকরা পরীক্ষা করে এমন দুটি পরিস্থিতি দেখা দিতে পারে i:

  • যদি i0 হয় তবে গ্রাহক কল করেন cv.wait(), তারপরেও বাস্তবায়নের অংশটি বলা iহলে 0 হবে wait(lock)- লকের সঠিক ব্যবহার নিশ্চিত করে যে। এক্ষেত্রে প্রযোজক condition_variable::notify_one()তার whileলুপটিতে কল করার কোনও সুযোগ নেই যতক্ষণ না ভোক্তা কল করার পরে cv.wait(lk, []{return i == 1;})(এবং wait()কলটি সঠিকভাবে একটি নোটিফিকেশন 'ধরার জন্য যা কিছু করা দরকার তা করেছে - wait()এটি না করা পর্যন্ত লকটি প্রকাশ করবে না) )। সুতরাং এই ক্ষেত্রে, গ্রাহক বিজ্ঞপ্তিটি মিস করতে পারবেন না।

  • যদি iভোক্তা কল করে ইতিমধ্যে 1 হয় cv.wait(), wait(lock)বাস্তবায়নের অংশটি কখনই ডাকা হবে না কারণ while (!pred())পরীক্ষার ফলে অভ্যন্তরীণ লুপটি শেষ হয়ে যাবে। এই পরিস্থিতিতে যখন notify_one () এ কল আসে তখন কিছু যায় আসে না - গ্রাহক অবরোধ করবেন না।

এখানে উদাহরণটিতে doneভেরিয়েবলটি প্রযোজক থ্রেডে ফিরে সিগন্যাল করতে অতিরিক্ত জটিলতা রয়েছে যা ভোক্তা তা স্বীকার করেছেন i == 1, তবে আমি মনে করি না এটি বিশ্লেষণকে মোটেই বদলে দেয় কারণ সমস্ত প্রবেশাধিকার done(পড়া এবং সংশোধন উভয়ের জন্য) ) একই সমালোচনামূলক বিভাগগুলির সাথে জড়িত রয়েছে iএবং condition_variable

আপনি প্রশ্ন তাকান যে @ eh9 তীক্ষ্ন পারেন, সিঙ্ক ব্যবহার এসটিডি :: পারমাণবিক এবং এসটিডি :: condition_variable উপরে ভরসা করা যায় , আপনি হবে একটি জাতি শর্ত দেখুন। তবে, এই প্রশ্নে পোস্ট করা কোডটি শর্ত পরিবর্তনশীল ব্যবহারের অন্যতম মৌলিক নিয়ম লঙ্ঘন করে: চেক-ওয়েট করার সময় এটি কোনও একক সমালোচনামূলক বিভাগ ধারণ করে না।

এই উদাহরণে, কোডটি দেখতে এমন দেখাচ্ছে:

if (--f->counter == 0)      // (1)
    // we have zeroed this fence's counter, wake up everyone that waits
    f->resume.notify_all(); // (2)
else
{
    unique_lock<mutex> lock(f->resume_mutex);
    f->resume.wait(lock);   // (3)
}

আপনি খেয়াল করবেন যে wait()# 3 এ রাখা যখন সঞ্চালিত হয় f->resume_mutex। তবে এই লকটি আটকে রাখার সময় wait()# 1 ধাপে প্রয়োজনীয় হওয়া প্রয়োজন কিনা তা যাচাই করা হয় না (চেক-ইন-ওয়েটের জন্য অনেক কম ধারাবাহিকভাবে), এটি শর্ত ভেরিয়েবলগুলির যথাযথ ব্যবহারের জন্য প্রয়োজনীয়তা)। আমি বিশ্বাস করি যে কোডটির স্নিপেটের সাথে যার সমস্যা আছে সে ভেবেছিল যেহেতু f->counterএটি std::atomicপ্রকারের তাই এটি প্রয়োজনীয়তা পূরণ করবে। তবে প্রদত্ত পারমাণবিকতা std::atomicপরবর্তী কলগুলিতে প্রসারিত হয় না f->resume.wait(lock)। এই উদাহরণে, যখন f->counterপরীক্ষা করা হয় (পদক্ষেপ # 1) এবং যখন wait()ডাকা হয় (পদক্ষেপ # 3) এর মধ্যে একটি প্রতিযোগিতা রয়েছে ।

এই প্রশ্নের উদাহরণটিতে সেই জাতিটির অস্তিত্ব নেই।


4
এর আরও গভীর নিদর্শন রয়েছে: ডোমাইগন / ব্লগ / কমপুটিং / Not উল্লেখযোগ্যভাবে, আপনি যে pthread সমস্যাটি উল্লেখ করেছেন তা হ'ল একটি সাম্প্রতিক সংস্করণ বা সঠিক পতাকাগুলি দ্বারা নির্মিত একটি সংস্করণ দ্বারা সমাধান করা উচিত। ( wait morphingঅপ্টিমাইজেশান সক্ষম করতে ) এই লিঙ্কটিতে থাম্বের বিধিটি ব্যাখ্যা করা হয়েছে: আরও অনুমানযোগ্য ফলাফলের জন্য 2 টিরও বেশি থ্রেড সহ পরিস্থিতিগুলিতে লকটি দিয়ে জানান দেওয়া ভাল।
v.oddou

6
@ মিশেল: আমার বোঝার জন্য ভোক্তাকে শেষ পর্যন্ত কল করা দরকার the_condition_variable.wait(lock);। যদি উত্পাদক এবং ভোক্তাকে সিঙ্ক্রোনাইজ করার জন্য কোনও লক প্রয়োজন না হয় (অন্তর্নিহিতটি একটি লক ফ্রি এসএসসিপি সারি বলুন), তবে সেই লকটি কোনও উদ্দেশ্য করে না যদি প্রযোজক লক না করে। আমার দ্বারা ভাল। তবে বিরল প্রতিযোগিতার ঝুঁকি নেই? প্রযোজক যদি লকটি ধরে না রাখেন, তবে গ্রাহক অপেক্ষা করার ঠিক আগে থাকা অবস্থায় তিনি কি বিজ্ঞপ্তি কল করতে পারবেন না? তারপরে ভোক্তা অপেক্ষাটি হিট করে এবং জাগবে না ...
কুকি

4
উদাহরণস্বরূপ উপরের কোডে বলুন ভোক্তা std::cout << "Waiting... \n";প্রযোজক যখন আছেন cv.notify_one();, তখন জাগ্রত কলটি নিখোঁজ হয়ে যায় ... বা আমি এখানে কিছু অনুপস্থিত করছি?
কুকি

4
@ কুকি হ্যাঁ, সেখানে একটি দৌড়ের অবস্থা রয়েছে। দেখুন stackoverflow.com/questions/20982270/...
eh9

4
@ এহ 9: ধিক্কার, আমি আমার মন্তব্যে বার বার জমা হওয়া একটি বাগের কারণ খুঁজে পেয়েছি আপনার মন্তব্যের জন্য ধন্যবাদ। এটি জাতি শর্তের এই সঠিক কেসের কারণে হয়েছিল। বিজ্ঞপ্তির পরে মিটেক্সটি আনলক করা পুরোপুরি সমস্যাটি সমাধান হয়েছে ... অনেক অনেক ধন্যবাদ!
গ্যালিনেট

10

অবস্থা

ভিসি 10 এবং বুস্ট 1.56 ব্যবহার করে আমি এই ব্লগ পোস্টের পরামর্শ মতো বেশ কিছুটা একই সাথে একযোগে সারি প্রয়োগ করেছি । বিতর্কটি হ্রাস করতে লেখক মিউটেক্সকে আনলক করে, অর্থাত্, notify_one()মিউটেক্সকে আনলক করে বলা হয়:

void push(const T& item)
{
  std::unique_lock<std::mutex> mlock(mutex_);
  queue_.push(item);
  mlock.unlock();     // unlock before notificiation to minimize mutex contention
  cond_.notify_one(); // notify one waiting thread
}

বুট ডকুমেন্টেশনের একটি উদাহরণ দ্বারা মুটেক্স আনলক করা সমর্থন করে :

void prepare_data_for_processing()
{
    retrieve_data();
    prepare_data();
    {
        boost::lock_guard<boost::mutex> lock(mut);
        data_ready=true;
    }
    cond.notify_one();
}

সমস্যা

তবুও এর ফলে নিম্নলিখিত ত্রুটিযুক্ত আচরণ দেখা দিয়েছে:

  • যখন notify_one()হয়েছে না বলা এখনো cond_.wait()এখনও মাধ্যমে বিঘ্নিত হতে পারেboost::thread::interrupt()
  • একবার notify_one()প্রথমবারের জন্য cond_.wait()অচলাবস্থার জন্য ডেকে আনা হয়েছিল ; অপেক্ষা boost::thread::interrupt()বা boost::condition_variable::notify_*()আর শেষ করা যাবে না।

সমাধান

লাইনটি সরিয়ে mlock.unlock()কোডটি প্রত্যাশার মতো কাজ করেছে (বিজ্ঞপ্তি এবং বিঘ্নগুলি অপেক্ষাটি শেষ করে)। নোটটি যে notify_one()মুটেক্সকে এখনও লক করা আছে তার সাথে ডাকা হয়, সুযোগ ছাড়ার পরে এটি আনলক করা হয়:

void push(const T& item)
{
  std::lock_guard<std::mutex> mlock(mutex_);
  queue_.push(item);
  cond_.notify_one(); // notify one waiting thread
}

এর অর্থ হ'ল কমপক্ষে আমার নির্দিষ্ট থ্রেড প্রয়োগের সাথে মিটেক্সকে কল করার আগে অবশ্যই আনলক করা উচিত নয় boost::condition_variable::notify_one(), যদিও উভয় পদ্ধতিই সঠিক বলে মনে হচ্ছে।


আপনি কি বুস্ট.ট্রেডকে এই সমস্যাটির কথা জানিয়েছেন? আমি সেখানে অনুরূপ কাজ খুঁজে পাচ্ছি না svn.boost.org/trac/boost/…
ম্যাগরাস

@ ম্যাগরাস দুঃখের সাথে আমি জানিনা, কেন আমি এটি বিবেচনা করিনি no এবং দুর্ভাগ্যক্রমে আমি উল্লিখিত সারিটি ব্যবহার করে এই ত্রুটিটি পুনরুত্পাদন করতে সফল হই না।
ম্যাথিউস ব্র্যান্ডেল

আমি নিশ্চিত না যে আমি কীভাবে তাড়াতাড়ি ঘুম থেকে উঠার ফলে কোনও অচলাবস্থা দেখা দিতে পারে। বিশেষত, আপনি যদি পপ () এর পরে পপ () এ কনড_.উইট () থেকে বেরিয়ে আসে তবে কুইট মুটেক্স প্রকাশ করে তবে নোটিফাই_ন () নামক ডাকার আগে - পপ () সারিটি খালি দেখতে পাবে, এবং নতুন প্রবেশদ্বার পরিবর্তে গ্রাস করবে অপেক্ষা () ing। আপনি যদি কনড_.ওয়েট () থেকে বেরিয়ে আসেন যখন ধাক্কা () সারিটি আপডেট করার সময়, লকটি পুশ () ধরে রাখা উচিত, সুতরাং পপ () লকটি প্রকাশের অপেক্ষায় অবরুদ্ধ হওয়া উচিত। অন্য যে কোনও প্রারম্ভিক ওয়েকআপগুলি পপ () পরবর্তী অপেক্ষা () বলার আগে কাতারে পরিবর্তন করা থেকে () ধাক্কা দিয়ে, লকটি ধরে রাখে। আমি কি করতে পারেননি?
কেভিন

4

অন্যরা যেমন উল্লেখ করেছে, notify_one()রেস শর্ত এবং থ্রেডিং-সম্পর্কিত সমস্যার ক্ষেত্রে আপনাকে কল করার সময় লকটি ধরে রাখার দরকার নেই । যাইহোক, কিছু ক্ষেত্রে, লকটি ধরে রাখার condition_variableআগে notify_one()ডাকা যাওয়ার আগে ধ্বংস হওয়া থেকে রোধ করা প্রয়োজন । নিম্নলিখিত উদাহরণ বিবেচনা করুন:

thread t;

void foo() {
    std::mutex m;
    std::condition_variable cv;
    bool done = false;

    t = std::thread([&]() {
        {
            std::lock_guard<std::mutex> l(m);  // (1)
            done = true;  // (2)
        }  // (3)
        cv.notify_one();  // (4)
    });  // (5)

    std::unique_lock<std::mutex> lock(m);  // (6)
    cv.wait(lock, [&done]() { return done; });  // (7)
}

void main() {
    foo();  // (8)
    t.join();  // (9)
}

ধরে tনিই আমরা নতুন তৈরি থ্রেড তৈরি করার পরে একটি কনটেক্সট স্যুইচ আছে তবে কন্ডিশন ভেরিয়েবল (কোথাও (5) এবং (6) এর মধ্যে অপেক্ষা করা শুরু করার আগে। থ্রেডটি tলকটি (1) অর্জন করে, প্রিডিকেট ভেরিয়েবল (2) সেট করে এবং তারপরে লকটি প্রকাশ করে (3)। notify_one()(4) মৃত্যুদন্ড কার্যকর হওয়ার আগে এই মুহূর্তে আরও একটি প্রসঙ্গ সুইচ রয়েছে বলে ধরে নিন । মূল থ্রেডটি লকটি অর্জন করে (6) এবং লাইনটি কার্যকর করে (7), যার বিন্দুতে ভবিষ্যদ্বাণী ফিরে আসে trueএবং অপেক্ষা করার কোনও কারণ নেই, তাই এটি লকটি প্রকাশ করে এবং অবিরত থাকে। fooরিটার্ন (8) এবং এর স্কোপের ভেরিয়েবলগুলি (সহ cv) বিনষ্ট হয়। থ্রেড tমূল থ্রেডে যোগদানের আগে (9), এটির কার্য সম্পাদন শেষ করতে হবে, সুতরাং এটি যেখানে চালানো ছেড়েছিল সেখান থেকে এটি চালিয়ে যায়cv.notify_one()(4), cvইতিমধ্যে বিন্দু কোন মুহূর্তে !

এই ক্ষেত্রে সম্ভাব্য সংশোধন হ'ল কল করার সময় লকটি ধরে রাখা notify_one(অর্থাত্ লাইনে শেষ হওয়া সুযোগটি সরিয়ে ফেলুন (3))। এটি করার মাধ্যমে, আমরা নিশ্চিত করেছিলাম যে থ্রেড tকলগুলি notify_oneআগে cv.waitনতুন সেট করা প্রিকিকেট ভেরিয়েবলটি পরীক্ষা করতে পারে এবং চালিয়ে যেতে পারে, যেহেতু চেকটি করার জন্য এটি t বর্তমানে থাকা লকটি অর্জন করতে হবে। সুতরাং, আমরা নিশ্চিত করি যে রিটার্নের পরে cvথ্রেড দ্বারা অ্যাক্সেস করা হয়নি ।tfoo

সংক্ষিপ্তসার হিসাবে, এই নির্দিষ্ট ক্ষেত্রে সমস্যাটি আসলে থ্রেডিংয়ের ক্ষেত্রে নয়, তবে রেফারেন্সের সাহায্যে ধারণ করা ভেরিয়েবলগুলির জীবনকাল সম্পর্কে। cvথ্রেডের মাধ্যমে রেফারেন্স দ্বারা ক্যাপচার করা হয় t, সুতরাং আপনাকে cvথ্রেডের সম্পাদনার সময়কালের জন্য বেঁচে থাকতে হবে তা নিশ্চিত করতে হবে । এখানে উপস্থাপিত অন্যান্য উদাহরণগুলি এই সমস্যাটিতে ভোগেনা, কারণ condition_variableএবং mutexবিশ্বব্যাপী ক্ষেত্রগুলিতে অবজেক্টগুলি সংজ্ঞায়িত করা হয়, সুতরাং প্রোগ্রামটি শেষ না হওয়া পর্যন্ত তাদের বেঁচে থাকার গ্যারান্টিযুক্ত।


1

@ মিশেল বুড় সঠিক। condition_variable::notify_oneচলকটিতে একটি লক প্রয়োজন হয় না। উদাহরণস্বরূপ যেমনটি বোঝানো হয়েছে তেমন কিছুই আপনাকে সেই পরিস্থিতিতে লক ব্যবহার করতে বাধা দেয় না।

প্রদত্ত উদাহরণে লকটি ভেরিয়েবলের একযোগে ব্যবহারের মাধ্যমে প্রেরণা পায় i। যেহেতু signalsথ্রেডটি ভেরিয়েবলকে সংশোধন করে, এটি নিশ্চিত করা দরকার যে সেই সময়কালে অন্য কোনও থ্রেড এটি অ্যাক্সেস করতে পারে না।

সিঙ্ক্রোনাইজেশন প্রয়োজন যে কোনও পরিস্থিতিতে লকগুলি ব্যবহৃত হয় , আমি মনে করি না আমরা আরও সাধারণ উপায়ে এটি বর্ণনা করতে পারি।


অবশ্যই, তবে সর্বোপরি এগুলি কন্ডিশন ভেরিয়েবলের সাথেও ব্যবহার করা দরকার যাতে পুরো প্যাটার্নটি আসলে কাজ করে। উল্লেখযোগ্যভাবে কন্ডিশনের পরিবর্তনশীল waitফাংশনটি কলটির অভ্যন্তরে লকটি মুক্তি দিচ্ছে এবং লকটির পুনঃসংশোধন করার পরেই তা ফিরে আসে। কোন বিন্দু পরে আপনি নিরাপদে আপনার অবস্থার জন্য পরীক্ষা করতে পারেন কারণ আপনি "পড়ার অধিকারগুলি" অর্জন করেছেন say যদি এটি এখনও না অপেক্ষা করে থাকে তবে আপনি আবার ফিরে যেতে পারেন wait। এই নিদর্শন। বিটিডব্লিউ, এই উদাহরণটি এটি সম্মান করে না।
v.oddou

1

কিছু ক্ষেত্রে, যখন সিভিটি অন্য থ্রেড দ্বারা দখল করা (লক করা) হতে পারে। আপনাকে __ (() অবহিত করার আগে লক করা এবং এটি ছেড়ে দেওয়া দরকার।
যদি তা না হয় তবে বিজ্ঞপ্তি _ * () সম্ভবত নির্বাহ করা হবে না।


1

কেবল এই উত্তরটি যুক্ত করা কারণ আমি মনে করি গৃহীত উত্তরগুলি বিভ্রান্তিমূলক হতে পারে। সব ক্ষেত্রে আপনার কোডটি থ্রেড-সেফ হওয়ার জন্য কোথাও 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) // Reached -1000 ?
    cv.notify_one();
}

bool start()
{
  if (count.fetch_add(1) >= 0)
    return true;
  // Failure.
  stop();
  return false;
}

void cancel()
{
  if (count.fetch_sub(1000) == 0)  // Reached -1000?
    return;
  // Wait till count reached -1000.
  std::unique_lock<std::mutex> lk(cancel_mutex);
  cancel_cv.wait(lk);
}

সতর্কতা : এই কোডটিতে একটি বাগ রয়েছে।

ধারণাটি হ'ল: থ্রেডগুলি জোড়ায় স্টার্ট () এবং স্টপ () থামায়, তবে যতক্ষণ না শুরু () সত্য ফিরে আসে ততক্ষণ। উদাহরণ স্বরূপ:

if (start())
{
  // Do stuff
  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) // Reached -1000 ?
  {
    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 আনলক করতে: _ * () যে ক্ষেত্রে।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.