মাল্টিথ্রেডিং প্রোগ্রামটি অনুকূলিত মোডে আটকে গেছে তবে -O0 এ সাধারণত চলে


68

আমি নীচে একটি সাধারণ মাল্টিথ্রেডিং প্রোগ্রাম লিখেছি:

static bool finished = false;

int func()
{
    size_t i = 0;
    while (!finished)
        ++i;
    return i;
}

int main()
{
    auto result=std::async(std::launch::async, func);
    std::this_thread::sleep_for(std::chrono::seconds(1));
    finished=true;
    std::cout<<"result ="<<result.get();
    std::cout<<"\nmain thread id="<<std::this_thread::get_id()<<std::endl;
}

এটা ডিবাগ মোড স্বাভাবিকভাবে আচরণ করবে ভিসুয়াল স্টুডিও বা -O0মধ্যে জিসি সি এবং ফলাফলের প্রিন্ট আউট পর 1সেকেন্ড। তবে এটি আটকে এবং রিলিজ মোডে বা মুদ্রণ করে না -O1 -O2 -O3


মন্তব্যগুলি বর্ধিত আলোচনার জন্য নয়; এই কথোপকথন চ্যাটে সরানো হয়েছে ।
স্যামুয়েল লিউ

উত্তর:


100

দুটি থ্রেড, অ-পরমাণু, অ-রক্ষিত ভেরিয়েবল অ্যাক্সেস করা ইউবি এই উদ্বেগের বিষয় finished। এটি ঠিক করতে আপনি finishedটাইপ করতে পারেন std::atomic<bool>

আমার ফিক্স:

#include <iostream>
#include <future>
#include <atomic>

static std::atomic<bool> finished = false;

int func()
{
    size_t i = 0;
    while (!finished)
        ++i;
    return i;
}

int main()
{
    auto result=std::async(std::launch::async, func);
    std::this_thread::sleep_for(std::chrono::seconds(1));
    finished=true;
    std::cout<<"result ="<<result.get();
    std::cout<<"\nmain thread id="<<std::this_thread::get_id()<<std::endl;
}

আউটপুট:

result =1023045342
main thread id=140147660588864

কলিরুতে লাইভ ডেমো


কেউ ভাবতে পারেন 'এটি একটি bool- সম্ভবত কিছুটা। এটি কীভাবে পারমাণবিক হতে পারে? ' (আমি নিজে যখন মাল্টি-থ্রেডিং শুরু করেছি তখনই করেছি))

তবে মনে রাখবেন যে অভাব ছিটিয়ে থাকা কেবল std::atomicআপনাকে দেয় এমন জিনিস নয় । এটি একাধিক থ্রেড থেকে ভালভাবে সংজ্ঞায়িত পঠন + লেখার অ্যাক্সেসও করে তোলে, ভেরিয়েবলটি পুনরায় পড়াটি সর্বদা একই মান দেখতে পাবে এমন ধারণা থেকে সংকলককে থামিয়ে দেয়।

একটি boolনিরক্ষিত, অ-পরমাণু তৈরি করা অতিরিক্ত সমস্যার কারণ হতে পারে:

  • সংকলকটি কোনও রেজিস্টার বা সিএসইর একাধিক অ্যাক্সেসকে একটিতে রূপান্তর করতে এবং একটি লুপের বাইরে লোড উত্তোলনের সিদ্ধান্ত নিতে পারে।
  • ভেরিয়েবলটি সিপিইউ কোরের জন্য ক্যাশে করা হতে পারে। (বাস্তব জীবনে, সিপিইউগুলিতে সুসংগত ক্যাশে রয়েছে This এটি আসল সমস্যা নয়, তবে সি ++ স্ট্যান্ডার্ডটি অবিচ্ছিন্ন শেয়ার্ড মেমরির অনুমানকৃত সি ++ বাস্তবায়নগুলি কভার করতে যথেষ্ট আলগা যেখানে স্টোর / লোড atomic<bool>সহ memory_order_relaxedকাজ করবে, কিন্তু কোথায় volatileহবে না Using এটি আসল সি ++ বাস্তবায়নের ক্ষেত্রে অনুশীলনে কাজ করলেও এটির জন্য উদ্বায়ী হওয়া ইউবি হবে)

এটি যাতে না ঘটে তার জন্য সংকলককে অবশ্যই না করতে হবে তা স্পষ্টভাবে জানিয়ে দিতে হবে।


আমি volatileএই সমস্যার সম্ভাব্য সম্পর্ক সম্পর্কিত বিবর্তিত আলোচনা সম্পর্কে কিছুটা অবাক হয়েছি । সুতরাং, আমি আমার দুটি সেন্ট ব্যয় করতে চাই:


4
আমি একবার দেখেছিলাম func()এবং ভেবেছিলাম " আমি এটিকে অপ্টিমাইজ করতে পারি" অপটিমাইজারটি থ্রেডের মোটেই যত্ন করে না, এবং অসীম লুপটি সনাক্ত করে, এবং আনন্দের সাথে এটিকে "যখন (সত্য)" রূপান্তরিত করে যদি আমরা গডবোল্টের দিকে তাকাই তবে .org / z / Tl44iN আমরা এটি দেখতে পারি। শেষ হলে Trueতা ফিরে আসে। যদি তা না হয় তবে এটি শর্তাবলীতে নিজেকে ফিরে (এক অনন্ত লুপ) লেবেলে চলে যায়.L5
বালড্রিক 18


2
@ ওভাল: volatileসি ++ 11 এ মূলত গালি দেওয়ার কোনও কারণ নেই কারণ আপনি atomic<T>এবং এর সাথে অভিন্ন অ্যাসেম পেতে পারেন std::memory_order_relaxed। এটি সত্যিকারের হার্ডওয়্যারগুলিতে হলেও কাজ করে: ক্যাশেগুলি সুসংগত তাই কোনও লোড নির্দেশনা কোনও বাসি মান পড়তে পারে না যখন একবার অন্য কোনও কোর স্টোর সেখানে ক্যাশে চলে আসে। (এমইএসআই)
পিটার

5
@ পিটারকর্ডস ব্যবহার volatileকরা এখনও ইউবি। আপনার সত্যিকার অর্থে কখনই এমনটি অনুমান করা উচিত নয় যে স্পষ্টভাবে এবং স্পষ্টতই ইউবি নিরাপদ কারণ আপনি এটি ভুল হতে পারে এমন কোনও উপায়ের কথা ভাবতে পারেন না এবং এটি চেষ্টা করার সময় এটি কার্যকর হয়েছিল। এতে মানুষ বার বার জ্বলতে থাকে।
ডেভিড শোয়ার্টজ

2
@ ড্যামন মুটেক্সেস রিলিজ / অর্ন্তবিদ্যার অর্জন করেছে। সংকলকটি যদি কোনও মুটেক্সকে আগে লক করা থাকে তবে সেগুলি পড়ার অপ্টিমাইজ করার অনুমতি নেই, সুতরাং finishedকোনও std::mutexকাজের সাথে ( volatileবা ছাড়াই atomic) সুরক্ষিত । প্রকৃতপক্ষে, আপনি সমস্ত পরমাণুকে একটি "সাধারণ" মান + মুটেক্স স্কিম দিয়ে প্রতিস্থাপন করতে পারেন; এটি এখনও কাজ করবে এবং কেবল ধীর হবে। atomic<T>একটি অভ্যন্তরীণ মিটেক্স ব্যবহার করার অনুমতি দেওয়া হয়; শুধুমাত্র atomic_flagগ্যারান্টিযুক্ত লক-মুক্ত।
এরলকোনিগ

42

শেফের উত্তর আপনার কোড ঠিক করতে কীভাবে বর্ণনা করে। আমি ভেবেছিলাম এই ক্ষেত্রে আসলে কী ঘটছে সে সম্পর্কে আমি একটু তথ্য যুক্ত করব।

আমি আপনার কোডটি অপ্টিমাইজেশন স্তর 1 ( ) ব্যবহার করে গডবোল্টে সংকলন করেছি -O1। আপনার ফাংশনটি এরকম সংকলন করে:

func():
  cmp BYTE PTR finished[rip], 0
  jne .L4
.L5:
  jmp .L5
.L4:
  mov eax, 0
  ret

তাই এখানে কী ঘটছে? প্রথমত, আমাদের একটি তুলনা রয়েছে: cmp BYTE PTR finished[rip], 0- finishedএটি মিথ্যা কিনা না তা পরীক্ষা করে দেখুন ।

যদি এটি মিথ্যা না হয় (ওরফে সত্য) আমাদের প্রথম রান করার সময় লুপটি প্রস্থান করা উচিত। এই সম্পন্ন দ্বারা jne .L4যা umps যখন এন OT লেবেলে গণদেবতা .L4যেখানে মান i( 0) পরে ব্যবহার করার এবং ফাংশন আয় জন্য একটি রেজিস্টার সংরক্ষণ করা হয়।

যদি হয় তবে মিথ্যা, আমরা সরাতে

.L5:
  jmp .L5

এটি একটি নিঃশর্ত জাম্প, লেবেল করা .L5যা ঠিক তাই লাফ কমান্ড নিজেই ঘটে।

অন্য কথায়, থ্রেডটি অসীম ব্যস্ত লুপে রাখা হয়েছে।

তাহলে কেন এমন হয়েছে?

অপ্টিমাইজারের দিক থেকে থ্রেডগুলি এর পরিদর্শনের বাইরে। এটি ধরে নিয়েছে যে অন্যান্য থ্রেডগুলি একই সাথে ভেরিয়েবলগুলি পড়ছে না বা লেখছে না (কারণ এটি ডেটা-রেস ইউবি হবে)। আপনাকে এটি বলতে হবে যে এটি অ্যাক্সেসগুলি দূরে সরিয়ে নিতে পারে না। এখানেই শেফের উত্তর আসে I আমি তাকে পুনরাবৃত্তি করতে বিরক্ত করব না।

কারণ অপ্টিমাইজারকে বলা হয় না যে finishedভেরিয়েবলটি ফাংশন সম্পাদনের সময় সম্ভাব্যভাবে পরিবর্তিত হতে পারে, এটি finishedএটি ফাংশনটি নিজেই সংশোধিত হয়নি এবং ধরে নিয়েছে যে এটি স্থির।

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

-O0কম্পাইলার (প্রত্যাশিত হিসাবে) লুপ শরীর ও তুলনা দূরে নিখুত করে না:

func():
  push rbp
  mov rbp, rsp
  mov QWORD PTR [rbp-8], 0
.L148:
  movzx eax, BYTE PTR finished[rip]
  test al, al
  jne .L147
  add QWORD PTR [rbp-8], 1
  jmp .L148
.L147:
  mov rax, QWORD PTR [rbp-8]
  pop rbp
  ret

সুতরাং ফাংশন, যখন বিনা প্রতিশ্রুতিবদ্ধ কাজ করে, এখানে পারমাণবিকতার অভাব সাধারণত কোনও সমস্যা হয় না, কারণ কোড এবং ডেটা-টাইপ সহজ। সম্ভবত আমরা এখানে সবচেয়ে খারাপভাবে দৌড়াতে পারি তার মানটি iযা হওয়া উচিত তা একে একে বন্ধ ।

ডেটা-স্ট্রাকচার সহ আরও জটিল সিস্টেমের ফলে দূষিত ডেটা বা ভুল প্রয়োগের ফলস্বরূপ অনেক বেশি সম্ভাবনা রয়েছে।


3
সি ++ 11 থ্রেড এবং একটি থ্রেড-সচেতন মেমরি মডেলটিকে নিজেরাই ভাষার অংশ হিসাবে তৈরি করে। এর অর্থ কম্পাইলাররা atomicকোডে অ- ভেরিয়েবলগুলিতে এমনকি লিখতে পারে না those উদাহরণস্বরূপ এএসমে if (cond) foo=1;রূপান্তরিত হতে পারে না foo = cond ? 1 : foo;কারণ লোড + স্টোর (কোনও পারমাণবিক আরএমডাব্লু নয়) অন্য থ্রেড থেকে কোনও লেখার পদক্ষেপ নিতে পারে। সংকলকগণ ইতিমধ্যে এ জাতীয় সামগ্রী এড়িয়ে যাচ্ছিলেন কারণ তারা মাল্টি-থ্রেড প্রোগ্রাম লেখার জন্য দরকারী হতে চেয়েছিলেন, তবে সি ++ 11 এটিকে অফিসিয়াল করে তুলেছে যে কম্পাইলাররা 2 টি থ্রেড লিখতে হবে এমন কোডটি ভাঙ্গতে হবে না a[1]এবংa[2]
পিটার

2
তবে হ্যাঁ, কীভাবে কম্পাইলার থ্রেডের সচেতন নয় যে অতিরঞ্জন ছাড়া অন্য সব সময়ে , আপনার উত্তর সঠিক। ডেটা-রেস ইউবি হ'ল গ্লোবালগুলি সহ অ-পারমাণবিক ভেরিয়েবলগুলির ভার বহন করার অনুমতি দেয় এবং একক-থ্রেড কোডের জন্য আমরা চাই এমন অন্যান্য আক্রমণাত্মক অপ্টিমাইজেশান। এমসিইউ প্রোগ্রামিং - ইলেকট্রনিক্সে লুপ থাকা অবস্থায় সি ++ ও 2 অপটিমাইজেশন বিরতি .এসই এই ব্যাখ্যাটির আমার সংস্করণ।
পিটার

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

1
হ্যাঁ, আমি জানতাম আপনি যা বলার চেষ্টা করছেন তা কিন্তু আমি মনে করি না আপনার শব্দের 100% এর অর্থ। অপ্টিমাইজারটি বলতে "এগুলিকে সম্পূর্ণ উপেক্ষা করে।" একেবারেই সঠিক নয়: এটি সুপরিচিত যে যখন অনুকূলকরণের সময় থ্রেডিং উপেক্ষা করে শব্দ লোড করা / শব্দ / শব্দ স্টোরের একটি বাইট পরিবর্তন করতে পারে যা বাস্তবে বাগের কারণ হয়ে থাকে যেখানে একটি থ্রেডের একটি চর বা বিটফিল্ডে প্রবেশ করা হয় সংলগ্ন স্ট্রাক্ট সদস্যকে লিখুন। সম্পূর্ণ গল্পের জন্য lwn.net/Articles/478657 দেখুন , এবং কেবলমাত্র C11 / C ++ 11 মেমরি মডেল কীভাবে কেবল এমন অনুশীলনকে অবৈধ করে তোলে, কেবল অনুশীলনে অনাকাঙ্ক্ষিত নয়।
পিটার কর্ডস

1
না, এটি ভাল .. ধন্যবাদ @ পিটারকর্ডস। আমি উন্নতির প্রশংসা করি।
বাল্ড্রিক 24

5

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

এখানে একটি উদাহরণ:

class ST {
public:
    int func()
    {
        size_t i = 0;
        while (!finished)
            ++i;
        return i;
    }
    void setFinished(bool val)
    {
        finished = val;
    }
private:
    std::atomic<bool> finished = false;
};

int main()
{
    ST st;
    auto result=std::async(std::launch::async, &ST::func, std::ref(st));
    std::this_thread::sleep_for(std::chrono::seconds(1));
    st.setFinished(true);
    std::cout<<"result ="<<result.get();
    std::cout<<"\nmain thread id="<<std::this_thread::get_id()<<std::endl;
}

ভ্যান্ডবক্সে লাইভ


1
এছাড়াও ফাংশন ব্লকের মধ্যে finishedহিসাবে ঘোষণা staticকরতে পারে। এটি কেবলমাত্র একবারে আরম্ভ করা হবে এবং যদি এটি একটি ধ্রুবককে আরম্ভ করা হয় তবে এটি লকিংয়ের প্রয়োজন হয় না।
ডেভিস্লোর

অ্যাক্সেসগুলিও finishedসস্তা ব্যবহার করতে পারেstd::memory_order_relaxed লোড এবং ; আর্টিংয়ের দরকার নেই উভয় থ্রেডে অন্যান্য ভেরিয়েবল। আমি নিশ্চিত না যে @ ডেভিস্লোরের পরামর্শটি সার্থক হয়েছে static, যদিও; আপনার যদি একাধিক স্পিন-কাউন্ট থ্রেড থাকে তবে আপনার প্রয়োজন হবে না যে সমস্ত পতাকাটি একই পতাকা দিয়ে থামানো উচিত। আপনি finishedকোনও সূচনাটি এমনভাবে লিখতে চান যা কোনও পারমাণবিক স্টোর নয়, কেবল সূচনাতে সংকলন করে। (যেমন আপনি finished = false;ডিফল্ট ইনিশিয়ালাইজার সি ++ 17 সিনট্যাক্স দিয়ে করছেন god Godbolt.org/z/EjoKgq )।
পিটার কর্ডেস

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