থ্রেড-সুরক্ষা বিধি দ্বারা প্রস্তাবিত নন-কনস্ট্যান্ট আর্গুমেন্ট সহ অনুলিপি নির্মাণকারী?


9

লিগ্যাসি কোডের কয়েকটি টুকরোতে আমার একটি মোড়ক রয়েছে।

class A{
   L* impl_; // the legacy object has to be in the heap, could be also unique_ptr
   A(A const&) = delete;
   L* duplicate(){L* ret; legacy_duplicate(impl_, &L); return ret;}
   ... // proper resource management here
};

এই লিগ্যাসি কোডে, কোন ফাংশনটি "অনুলিপি" দেয় কোনও বস্তুটি থ্রেড নিরাপদ নয় (একই প্রথম যুক্তির দিকে আহ্বান করার সময়), সুতরাং এটি constমোড়কে চিহ্নিত করা হয়নি । আমি আধুনিক বিধিগুলি অনুসরণ করে অনুমান করি: https://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/

এটি duplicateঅনুলিপি ব্যতীত অন্য কোনও অনুলিপি নির্ধারণকারীকে কার্যকর করার ভাল উপায় বলে মনে হচ্ছে const। সুতরাং আমি সরাসরি এটি করতে পারি না:

class A{
   L* impl_; // the legacy object has to be in the heap
   A(A const& other) : L{other.duplicate()}{} // error calling a non-const function
   L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};

তাহলে এই বিপরীতমুখী পরিস্থিতিটি কীভাবে বেরিয়ে আসার উপায়?

(আসুন এটিও legacy_duplicateথ্রেড-সেফ নয় তবে আমি জানি যে অবজেক্টটি যখন প্রস্থান করে তখন মূল অবস্থায় ফেলে দেয় a সি-ফাংশন হওয়ায় আচরণটি কেবল নথিভুক্ত থাকে তবে দৃ const়তার কোনও ধারণা নেই))

আমি অনেকগুলি সম্ভাব্য পরিস্থিতি সম্পর্কে ভাবতে পারি:

(1) একটি সম্ভাবনা হ'ল স্বাভাবিক শব্দার্থবিজ্ঞানের সাথে কোনও অনুলিপি নির্মাণকারীকে কার্যকর করার কোনও উপায় নেই। (হ্যাঁ, আমি বস্তুটি সরিয়ে ফেলতে পারি এবং এটি আমার প্রয়োজন হয় না))

(২) অন্যদিকে, কোনও বস্তুর অনুলিপি সহজাতভাবে অ-থ্রেড-নিরাপদ এই অর্থে যে একটি সাধারণ প্রকার অনুলিপি করার ফলে উত্সটি অর্ধ-সংশোধিত অবস্থায় খুঁজে পেতে পারে, তাই আমি কেবল এগিয়ে যেতে পারি এবং সম্ভবত এটি করতে পারি,

class A{
   L* impl_;
   A(A const& other) : L{const_cast<A&>(other).duplicate()}{} // error calling a non-const function
   L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};

(3) বা এমনকি duplicateকনস্ট্রাকটি ঘোষণা করুন এবং সমস্ত প্রসঙ্গে থ্রেড সুরক্ষা সম্পর্কে মিথ্যা বলুন। (সমস্ত উত্তরাধিকারের ফাংশনটির পরেও কোনও চিন্তা নেই constতাই সংকলক এমনকি অভিযোগও করবে না))

class A{
   L* impl_;
   A(A const& other) : L{other.duplicate()}{}
   L* duplicate() const{L* ret; legacy_duplicate(impl_, &ret); return ret;}
};

(4) অবশেষে, আমি যুক্তিটি অনুসরণ করতে পারি এবং একটি অনুলিপি যুক্তি গ্রহণকারী একটি অনুলিপি তৈরি করতে পারি ।

class A{
   L* impl_;
   A(A const&) = delete;
   A(A& other) : L{other.duplicate()}{}
   L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};

দেখা যাচ্ছে যে এটি অনেকগুলি প্রসঙ্গে কাজ করে, কারণ এই জিনিসগুলি সাধারণত হয় না const

প্রশ্নটি হচ্ছে, এটি কি বৈধ বা সাধারণ রুট?

আমি তাদের নাম রাখতে পারি না, তবে আমি অজ্ঞাতসারে কন্ট্রাক্টর নন কনস্ট্রাক্টর থাকার কারণে প্রচুর সমস্যা আশা করি। সম্ভবত এই সূক্ষ্মতার কারণে এটি কোনও মান-ধরণের হিসাবে যোগ্যতা অর্জন করবে না।

(5) অবশেষে, যদিও এটি একটি ওভারকিল বলে মনে হচ্ছে এবং খাড়া রানটাইম ব্যয় হতে পারে, আমি একটি মিটেক্স যুক্ত করতে পারি:

class A{
   L* impl_;
   A(A const& other) : L{other.duplicate_locked()}{}
   L* duplicate(){
      L* ret; legacy_duplicate(impl_, &ret); return ret;
   }
   L* duplicate_locked() const{
      std::lock_guard<std::mutex> lk(mut);
      L* ret; legacy_duplicate(impl_, &ret); return ret;
   }
   mutable std::mutex mut;
};

তবে এটি করতে বাধ্য হওয়া হতাশার মতো দেখায় এবং শ্রেণিটিকে আরও বড় করে তোলে। আমি নিশ্চিত না. আমি বর্তমানে (4) , বা (5) বা উভয়ের সংমিশ্রনের দিকে ঝুঁকছি।


সম্পাদনা 1:

অন্য বিকল্প:

()) সদৃশ সদস্য ফাংশনের সমস্ত অজ্ঞানতা সম্পর্কে ভুলে যান এবং কেবল legacy_duplicateকনস্ট্রাক্টরের কাছ থেকে কল করুন এবং ঘোষণা করুন যে অনুলিপিটি নির্মাণকারী নিরাপদ নয়। (এবং প্রয়োজনে আরও একটি থ্রেড-সেফটি ভার্চুয়ান তৈরি করুন, A_mt)

class A{
   L* impl_;
   A(A const& other){legacy_duplicate(other.impl_, &impl_);}
};

সম্পাদনা 2:

উত্তরাধিকার ফাংশনটি যা করে তার জন্য এটি একটি ভাল মডেল হতে পারে। নোট করুন যে ইনপুটটি স্পর্শ করে কলটি প্রথম যুক্তি দ্বারা উপস্থাপিত মানের সাথে সুরক্ষিত নয়।

void legacy_duplicate(L* in, L** out){
   *out = new L{};
   char tmp = in[0];
   in[0] = tmp; 
   std::memcpy(*out, in, sizeof *in); return; 
}

সম্পাদনা 3: আমি ইদানীং শিখেছি std::auto_ptrনন-কনস্ট্যান্ট "অনুলিপি" নির্মাণকারী একইরকম সমস্যা ছিল। প্রভাবটি auto_ptrএকটি ধারকটির ভিতরে ব্যবহার করা যায়নি। https://www.quantstart.com/articles/STL-Containers-and-Auto_ptrs-Why-They-Dont-Mix/


1
" এই লিগ্যাসি কোডটিতে যে ফাংশনটি কোনও বস্তুর সদৃশ হয় সেগুলি থ্রেড নিরাপদ নয় (একই প্রথম যুক্তিতে ফোন করার সময়) " আপনি কি সে সম্পর্কে নিশ্চিত? এমন কোনও রাজ্য নেই Lযার মধ্যে একটি নতুন Lউদাহরণ তৈরি করে সংশোধন করা হয়েছে ? যদি তা না হয় তবে কেন আপনি বিশ্বাস করেন যে এই অপারেশনটি থ্রেড-নিরাপদ নয়?
নিকোল বোলাস

হ্যাঁ, এটাই অবস্থা। দেখে মনে হচ্ছে প্রথম আর্গুমেন্টের অভ্যন্তরীণ অবস্থা পরিস্রাবণের সময় পরিবর্তিত হয়েছে। কোনও কারণে (কিছু "অপ্টিমাইজেশন" বা খারাপ নকশা বা কেবল নির্দিষ্টকরণের দ্বারা) legacy_duplicateদুটি পৃথক থ্রেড থেকে একই প্রথম যুক্তি দিয়ে ফাংশনটি কল করা যায় না।
ALFC

@ টেডলিঙ্গমো ঠিক আছে আমি করেছি। যদিও টেকনিক্যালি সি ++ প্রে 11 কনস্ট্রে থ্রেডের উপস্থিতিতে আরও ঝাপসা অর্থ রয়েছে।
alfC

@ টেডলিঙ্গমো হ্যাঁ, এটি একটি দুর্দান্ত ভিডিও। এটি অত্যন্ত দুঃখের বিষয় যে ভিডিওটি সঠিকভাবে সঠিক সদস্যদের সাথে কাজ করে এবং নির্মাণের সমস্যাটিকে স্পর্শ করে না (এই দৃ the়তাটি "অন্যান্য" অবজেক্টেও ছিল)। দৃষ্টিকোণে বিমূর্ততার অন্য স্তর (এবং একটি কংক্রিট মিটেক্স) যুক্ত না করে অনুলিপি করার পরে এই মোড়কের থ্রেডটিকে নিরাপদ করার কোনও অভ্যন্তরীণ উপায় নেই।
alfC

হ্যাঁ, ভাল, এটি আমাকে বিভ্রান্ত করেছে এবং আমি সম্ভবত সেই ব্যক্তিদের মধ্যে একজন যারা constসত্যিকারের অর্থ জানে না । :-) আমি const&যতক্ষণ না সংশোধন করি ততক্ষণ আমার কপি কর্টারে নিয়ে যাওয়া নিয়ে দু'বার চিন্তা করব না other। আমি সর্বদা থ্রেড সুরক্ষার কথা ভাবি কারণ একাধিক থ্রেড থেকে এনক্যাপসুলেশনের মাধ্যমে অ্যাক্সেস করা দরকার তার উপরে কিছু যুক্ত করে এবং আমি উত্তরগুলির অপেক্ষায় আছি।
টেড লিঙ্গমো

উত্তর:


0

আমি কেবল আপনার বিকল্পগুলি (4) এবং (5) উভয়ই অন্তর্ভুক্ত করব, তবে আপনি যখন পারফরম্যান্সের জন্য এটি প্রয়োজনীয় বলে মনে করেন তবে সুস্পষ্টভাবে থ্রেড-অনিরাপদ আচরণকে বেছে নেবেন।

এখানে একটি সম্পূর্ণ উদাহরণ।

#include <cstdlib>
#include <thread>

struct L {
  int val;
};

void legacy_duplicate(const L* in, L** out) {
  *out = new L{};
  std::memcpy(*out, in, sizeof *in);
  return;
}

class A {
 public:
  A(L* l) : impl_{l} {}
  A(A const& other) : impl_{other.duplicate_locked()} {}

  A copy_unsafe_for_multithreading() { return {duplicate()}; }

  L* impl_;

  L* duplicate() {
    printf("in duplicate\n");
    L* ret;
    legacy_duplicate(impl_, &ret);
    return ret;
  }
  L* duplicate_locked() const {
    std::lock_guard<std::mutex> lk(mut);
    printf("in duplicate_locked\n");
    L* ret;
    legacy_duplicate(impl_, &ret);
    return ret;
  }
  mutable std::mutex mut;
};

int main() {
  A a(new L{1});
  const A b(new L{2});

  A c = a;
  A d = b;

  A e = a.copy_unsafe_for_multithreading();
  A f = const_cast<A&>(b).copy_unsafe_for_multithreading();

  printf("\npointers:\na=%p\nb=%p\nc=%p\nc=%p\nd=%p\nf=%p\n\n", a.impl_,
     b.impl_, c.impl_, d.impl_, e.impl_, f.impl_);

  printf("vals:\na=%d\nb=%d\nc=%d\nc=%d\nd=%d\nf=%d\n", a.impl_->val,
     b.impl_->val, c.impl_->val, d.impl_->val, e.impl_->val, f.impl_->val);
}

আউটপুট:

in duplicate_locked
in duplicate_locked
in duplicate
in duplicate

pointers:
a=0x7f85e8c01840
b=0x7f85e8c01850
c=0x7f85e8c01860
c=0x7f85e8c01870
d=0x7f85e8c01880
f=0x7f85e8c01890

vals:
a=1
b=2
c=1
c=2
d=1
f=2

এটি গুগল স্টাইল গাইড অনুসরণ করে যেখানে constথ্রেড সুরক্ষা যোগাযোগ করে তবে আপনার এপিআই কলিং কোডটি ব্যবহার করে অপ্ট-আউট করতে পারেconst_cast


উত্তরের জন্য আপনাকে ধন্যবাদ, আমি মনে করি এটি আপনার asnwer পরিবর্তন করে না এবং আমি নিশ্চিত নই তবে এর চেয়ে ভাল একটি মডেল legacy_duplicateহতে পারে void legacy_duplicate(L* in, L** out) { *out = new L{}; char tmp = in[0]; /*some weird call here*/; in[0] = tmp; std::memcpy(*out, in, sizeof *in); return; }(অর্থাত্ নন- in
কনস্ট্যান্ট

আপনার উত্তরটি অত্যন্ত আকর্ষণীয় কারণ এটি বিকল্প (4) এবং বিকল্পের স্পষ্ট সংস্করণ (2) এর সাথে একত্রিত হতে পারে। এটি, A a2(a1)থ্রেড নিরাপদ হওয়ার চেষ্টা করতে পারে (বা মুছে ফেলা) এবং A a2(const_cast<A&>(a1))থ্রেডটি মোটেও নিরাপদ হওয়ার চেষ্টা করবে না।
alfC

2
হ্যাঁ, আপনি যদি Aথ্রেড-সেফ এবং থ্রেড-অনিরাপদ প্রসঙ্গে উভয়টি ব্যবহারের পরিকল্পনা করেন তবে আপনার const_castকলিং কোডটি টানতে হবে যাতে থ্রেড-সুরক্ষা লঙ্ঘিত বলে জানা যায় clear এপিআই (মিটেক্স) এর পিছনে অতিরিক্ত সুরক্ষা চাপানো ঠিক আছে তবে অসতর্কতা (কনস্ট_কাস্ট) লুকিয়ে রাখা ঠিক নয়।
মাইকেল গ্র্যাসেক

0

TLDR: হয় আপনার অনুলিপি ফাংশন বাস্তবায়ন ঠিক, অথবা একটি mutex পরিচয় করিয়ে (অথবা কিছু আরো উপযুক্ত লকিং ডিভাইস, সম্ভবত একটি spinlock, অথবা গুরুতর কিছু করছেন সামনে নিশ্চিত করুন যে আপনার mutex স্পিন করার জন্য কনফিগার তা নিশ্চিত করুন) এখন জন্য , তারপর অনুলিপি বাস্তবায়ন ঠিক এবং লকটি আসলে কোনও সমস্যা হয়ে উঠলে লকটি সরিয়ে ফেলুন।

আমি মনে করি যে মুখ্য বিষয়টি লক্ষ্য করার জন্য হ'ল আপনি এমন একটি বৈশিষ্ট্য যুক্ত করছেন যা এর আগে বিদ্যমান ছিল না: একই সাথে একাধিক থ্রেড থেকে কোনও বস্তুর সদৃশ করার ক্ষমতা।

স্পষ্টতই, আপনি যে শর্ত সাজাতে পেরেছেন সেগুলির অধীনে এটি একটি বাগ - দৌড়ের শর্ত ছিল, যদি আপনি আগে কোনওরকম বাহ্যিক সমন্বয় না করে এই কাজটি করছিলেন।

অতএব, নতুন বৈশিষ্ট্যটির কোনও ব্যবহার হ'ল এমন কিছু হবে যা আপনি আপনার কোডটিতে যুক্ত করবেন, বিদ্যমান কার্যকারিতা হিসাবে উত্তরাধিকারসূত্রে নয়। আপনি এক যারা হওয়া উচিত জানে কত ঘন ঘন আপনি এই নতুন বৈশিষ্ট্য ব্যবহার করা করতে যাচ্ছি উপর নির্ভর করে - কিনা অতিরিক্ত লকিং যোগ আসলে ব্যয়বহুল হতে হবে।

এছাড়াও, অবজেক্টটির অনুভূত জটিলতার উপর ভিত্তি করে - আপনি যে বিশেষ চিকিত্সা দিচ্ছেন তার দ্বারা, আমি ধরে নিতে চলেছি যে সদৃশ প্রক্রিয়াটি তুচ্ছ নয়, সুতরাং ইতিমধ্যে পারফরম্যান্সের ক্ষেত্রে বেশ ব্যয়বহুল।

উপরের ভিত্তিতে, আপনার দুটি পথ অনুসরণ করা যেতে পারে:

ক) আপনি জানেন যে একাধিক থ্রেড থেকে এই বস্তুটি অনুলিপি করা প্রায়শই অতিরিক্ত লকিংয়ের ওভারহেড ব্যয়বহুল হয়ে উঠবে না - পারফাসাসগুলি তুচ্ছভাবে সস্তা, অন্তত বিদ্যমান নকল প্রক্রিয়াটি তার নিজের জন্য ব্যয়বহুল যে আপনি যদি ব্যবহার করেন তবে স্পিনলক / প্রাক-স্পিনিং মিটেক্স এবং এতে কোনও বিতর্ক নেই।

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

আমার সন্দেহ হয় যে আপনি সত্যই পরিস্থিতি A তে রয়েছেন, এবং কেবল একটি স্পিনলক / স্পিনিং মিটেক্স যুক্ত করুন যা অবিযুক্তিযুক্ত যখন কোনও পারফরম্যান্স পেনাল্টির কাছাকাছি থাকে, ঠিক কাজ করবে (যদিও এটি মানদণ্ড মনে রাখবেন)।

তত্ত্বগতভাবে, আরও একটি পরিস্থিতি রয়েছে:

গ) সদৃশ ফাংশনের আপাতদৃষ্টিতে জটিলতার বিপরীতে, এটি আসলে তুচ্ছ, তবে কোনও কারণে স্থির করা যায় না; এটি এত তুচ্ছ যে এমনকি একটি অবিস্মৃত স্পিনলকও নকলকরণের জন্য একটি অগ্রহণযোগ্য পারফরম্যান্স অবক্ষয়ের পরিচয় দেয়; সমান্তরাল থ্রেডে অনুলিপি খুব কম ব্যবহৃত হয়; একক থ্রেডে অনুলিপি সমস্ত সময় ব্যবহৃত হয়, পারফরম্যান্সের অবক্ষয়কে একেবারেই অগ্রহণযোগ্য করে তোলে।

এই ক্ষেত্রে, আমি নিম্নলিখিতটি পরামর্শ দিচ্ছি: যে কেউ দুর্ঘটনাক্রমে তাদের ব্যবহার থেকে বিরত রাখতে ডিফল্ট অনুলিপি নির্মাণকারী / অপারেটরগুলি মুছে ফেলা ঘোষণা করুন। দুটি সুস্পষ্টভাবে কলযোগ্য ডুপ্লিকেশন পদ্ধতি তৈরি করুন, একটি থ্রেড নিরাপদ এবং একটি থ্রেড অনিরাপদ; প্রসঙ্গের উপর নির্ভর করে আপনার ব্যবহারকারীদের স্পষ্টভাবে তাদের কল করুন। আবার, গ্রহণযোগ্য একক থ্রেড কর্মক্ষমতা এবং নিরাপদ মাল্টি থ্রেডিং অর্জনের অন্য কোনও উপায় নেই, যদি আপনি সত্যিই এই পরিস্থিতিতে থাকেন এবং আপনি কেবল বিদ্যমান নকলকরণ বাস্তবায়ন ঠিক করতে পারেন না । তবে আমি অনুভব করি যে এটি সত্যই আপনার পক্ষে সম্ভব নয়।

কেবল সেই মিটেক্স / স্পিনলক এবং বেঞ্চমার্ক যুক্ত করুন।


আপনি কি আমাকে স্পিনলক / সি -+ এ প্রাক-স্পিনিং মিটেক্সের উপাদানগুলিতে নির্দেশ করতে পারেন? এটি কি আরও জটিল কিছু যা সরবরাহ করে std::mutex? সদৃশ ফাংশনটি কোনও গোপন বিষয় নয়, আমি সমস্যাটি উচ্চ স্তরে রাখতে এবং এমপিআই সম্পর্কে উত্তর না পাওয়ার জন্য এটি উল্লেখ করিনি। তবে যেহেতু আপনি এত গভীর গেছেন আমি আপনাকে আরও বিশদ দিতে পারি। উত্তরাধিকারের ফাংশনটি হ'ল MPI_Comm_dupএবং কার্যকর নন-থ্রেড নিরাপদতা এখানে বর্ণিত হয়েছে (আমি এটি নিশ্চিত করেছি) github.com/pmodels/mpich/issues/3234 । এই কারণেই আমি সদৃশ ঠিক করতে পারি না। (এছাড়াও, আমি যদি একটি মিটেক্স যুক্ত করি তবে সমস্ত এমপিআই কলগুলি থ্রেড-নিরাপদ করার জন্য প্রলুব্ধ হব))
alfC

দুঃখজনকভাবে আমি খুব বেশি স্ট্যান্ড :: ম্যুটেক্স জানি না, তবে আমার ধারণা প্রক্রিয়াটি ঘুমিয়ে যাওয়ার আগে এটি কিছুটা ঘুরেছে। একটি সুপরিচিত সিঙ্ক্রোনাইজেশন ডিভাইস যেখানে আপনি এটি ম্যানুয়ালি নিয়ন্ত্রণ করতে পারবেন তা হ'ল: ডকস.মাইক্রোসফট / en-us / windows / win32 / api / synchapi/… আমি পারফরম্যান্সের সাথে তুলনা করি নি, তবে মনে হয় যে std :: mutex এখন উচ্চতর: stackoverflow.com/questions/9997473/... এবং ব্যবহার করে বাস্তবায়ন করা: docs.microsoft.com/en-us/windows/win32/sync/...
DeducibleSteak

মনে হচ্ছে এই অ্যাকাউন্টে নেওয়া সাধারণ বিবেচ্য ভাল বিবরণ: stackoverflow.com/questions/5869825/...
DeducibleSteak

আবার ধন্যবাদ, আমি যদি লিনাক্সে আছি তবে তা গুরুত্বপূর্ণ।
alfC

এখানে কিছুটা বিস্তারিত পারফরম্যান্স তুলনা করা হয়েছে (ভিন্ন ভাষার জন্য, তবে আমি অনুমান করি যে এটি তথ্যমূলক এবং কী প্রত্যাশা করা উচিত তার সূচক): matklad.github.io/2020/01/04/… টিএলডিআর হ'ল - স্পিনলকস একটি খুব ছোট দ্বারা জিতেছে কোন বিতর্ক নেই যখন মার্জিন, বিতর্ক আছে যখন খারাপভাবে হারাতে পারেন।
ডিসিউসিবলস্টেক
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.