পরমাণু: ঠিক কী?


174

আমি বুঝতে পারছি এটি std::atomic<>একটি পারমাণবিক বস্তু। তবে কী পরিমাণে পারমাণবিক? আমার বোঝার জন্য একটি অপারেশন পারমাণবিক হতে পারে। কোনও বস্তুর পরমাণু তৈরি করে কী বোঝানো হয়? উদাহরণস্বরূপ যদি দুটি থ্রেড একই সাথে নিম্নলিখিত কোডটি কার্যকর করে:

a = a + 12;

তাহলে পুরো অপারেশন (বলুন add_twelve_to(int)) পারমাণবিক? বা পরিবর্তনশীল পারমাণবিক (তাই operator=()) পরিবর্তন করা হয় ?


9
আপনি a.fetch_add(12)যদি কোনও পারমাণবিক আরএমডাব্লু চান তবে আপনাকে এমন কিছু ব্যবহার করতে হবে ।
কেরেরেক এসবি

হ্যাঁ এটাই আমি বুঝতে পারি না। কোনও বস্তুর পরমাণু তৈরি করে কী বোঝানো হয়। যদি কোনও ইন্টারফেস থাকে তবে এটি কেবল কোনও মিউটেক্স বা মনিটরের সাহায্যে পারমাণবিক তৈরি করা যেতে পারে।

2
@ আরায়মনসাগর এটি দক্ষতার একটি সমস্যা সমাধান করে। নিরীক্ষক এবং মনিটরের গণ্য ওভারহেড বহন করে। ব্যবহারের std::atomicফলে প্রমিত লাইব্রেরিটি নির্ধারণ করতে দেয় যে পারমাণবিকতা অর্জনের জন্য কী প্রয়োজন।
ড্রু ডোরম্যান

1
@ আরায়মনসাগর: std::atomic<T>এমন এক ধরণের যা পারমাণবিক ক্রিয়াকলাপের অনুমতি দেয় । এটি যাদুকরীভাবে আপনার জীবনকে আরও উন্নত করে না, আপনি এটির সাথে কী করতে চান তা আপনাকে এখনও জানতে হবে। এটি খুব নির্দিষ্ট ব্যবহারের ক্ষেত্রে, এবং পারমাণবিক ক্রিয়াকলাপগুলির (বস্তুটিতে) সাধারণত খুব সূক্ষ্ম হয় এবং অ-স্থানীয় দৃষ্টিকোণ থেকে এটি চিন্তা করা প্রয়োজন। সুতরাং আপনি যদি এটি ইতিমধ্যে না জানেন এবং কেন আপনি পারমাণবিক ক্রিয়াকলাপ চান না, তবে টাইপটি সম্ভবত আপনার পক্ষে খুব বেশি কার্যকর নয়।
কেরেক এসবি

উত্তর:


188

প্রতিটি তাত্পর্য এবং স্ট্যান্ড :: পারমাণবিক <> এর সম্পূর্ণ বিশেষজ্ঞীকরণ এমন এক ধরণের প্রতিনিধিত্ব করে যা বিভিন্ন থ্রেড একযোগে (তাদের উদাহরণগুলিতে) অপরিজ্ঞাত আচরণ না করেই পরিচালনা করতে পারে:

পারমাণবিক ধরণের অবজেক্টগুলি কেবলমাত্র সি ++ অবজেক্ট যা ডেটা রেস থেকে মুক্ত; এটি হ'ল, যদি একটি থ্রেড পারমাণবিক অবজেক্টে লিখতে থাকে এবং অন্য থ্রেড এটি থেকে পড়ে, আচরণটি ভালভাবে সংজ্ঞায়িত হয়।

এছাড়াও, পারমাণবিক অবজেক্টে অ্যাক্সেসগুলি আন্ত-থ্রেড সিঙ্ক্রোনাইজেশন স্থাপন করে এবং অ-পারমাণবিক মেমরি অ্যাক্সেসগুলি নির্দিষ্ট করে হিসাবে অর্ডার করতে পারে std::memory_order

std::atomic<>জড়িত অপারেশনগুলি, প্রাক-সি ++ এ 11 বার, জিসিসির ক্ষেত্রে এমএসভিসি বা পারমাণবিক বুলটিনের সাথে ইন্টারলকড ফাংশন ব্যবহার করে (উদাহরণস্বরূপ) ব্যবহার করতে হয়েছিল।

এছাড়াও, সিঙ্ক্রোনাইজেশন এবং অর্ডার সীমাবদ্ধতাগুলি নির্দিষ্ট করে এমন std::atomic<>বিভিন্ন মেমরি অর্ডারকে মঞ্জুরি দিয়ে আপনাকে আরও নিয়ন্ত্রণ দেয় । আপনি যদি সি ++ 11 পরমাণু এবং মেমরি মডেল সম্পর্কে আরও পড়তে চান তবে এই লিঙ্কগুলি কার্যকর হতে পারে:

মনে রাখবেন, সাধারণ ব্যবহারের ক্ষেত্রে আপনি সম্ভবত অতিরিক্ত লোড পাটিগণিত অপারেটর বা তাদের অন্য সেট ব্যবহার করবেন :

std::atomic<long> value(0);
value++; //This is an atomic op
value += 5; //And so is this

অপারেটর সিনট্যাক্স আপনাকে মেমোরি ক্রম নির্দিষ্ট করার অনুমতি দেয় না বিধায় এই অপারেশনগুলি সম্পাদন করা হবে std::memory_order_seq_cst, কারণ এটি সি ++ ১১ এর সমস্ত পারমাণবিক ক্রিয়াকলাপের জন্য ডিফল্ট আদেশ It

কিছু ক্ষেত্রে, তবে এটির প্রয়োজন নাও হতে পারে (এবং কোনও কিছুই নিখরচায় আসে না), তাই আপনি আরও স্পষ্ট রূপটি ব্যবহার করতে চাইতে পারেন:

std::atomic<long> value {0};
value.fetch_add(1, std::memory_order_relaxed); // Atomic, but there are no synchronization or ordering constraints
value.fetch_add(5, std::memory_order_release); // Atomic, performs 'release' operation

এখন, আপনার উদাহরণ:

a = a + 12;

একটি একক পারমাণবিক বিকল্পকে মূল্যায়ন করবে না: এর ফলে a.load()(যা পরমাণু নিজেই) ফলস্বরূপ হবে , তারপরে এই মান এবং 12এবং a.store()চূড়ান্ত ফলাফলের (এছাড়াও পারমাণবিক) মধ্যে সংযোজন হবে । যেমনটি আমি আগে উল্লেখ করেছি, std::memory_order_seq_cstএখানে ব্যবহৃত হবে।

তবে আপনি যদি লিখেন তবে a += 12এটি একটি পারমাণবিক অপারেশন হবে (যেমন আমি আগে উল্লেখ করেছি) এবং মোটামুটি সমান a.fetch_add(12, std::memory_order_seq_cst)

আপনার মন্তব্য হিসাবে:

নিয়মিত intপারমাণবিক বোঝা এবং স্টোর থাকে। এটাকে মোড়ানোর কী লাভ atomic<>?

আপনার বক্তব্য কেবলমাত্র সেই আর্কিটেকচারের ক্ষেত্রেই সত্য যা স্টোর এবং / বা বোঝার জন্য যেমন পারমাণবিকতার গ্যারান্টি সরবরাহ করে। এমন আর্কিটেকচার রয়েছে যা এটি করে না। এছাড়াও, সাধারণত এটি প্রয়োজন হয় যে শব্দগুলি / ডাউড-সারিবদ্ধ ঠিকানায় অপারেশনগুলি অবশ্যই পারমাণবিক হতে হবে std::atomic<>এমন একটি জিনিস যা প্রতিটি প্ল্যাটফর্মে অতিরিক্ত প্রয়োজনীয়তা ছাড়াই পরমাণু হওয়ার নিশ্চয়তাযুক্ত is তদতিরিক্ত, এটি আপনাকে এই জাতীয় কোড লিখতে দেয়:

void* sharedData = nullptr;
std::atomic<int> ready_flag = 0;

// Thread 1
void produce()
{
    sharedData = generateData();
    ready_flag.store(1, std::memory_order_release);
}

// Thread 2
void consume()
{
    while (ready_flag.load(std::memory_order_acquire) == 0)
    {
        std::this_thread::yield();
    }

    assert(sharedData != nullptr); // will never trigger
    processData(sharedData);
}

মনে রাখবেন যে দাবি শর্তটি সর্বদা সত্য হবে (এবং এইভাবে কখনই ট্রিগার হবে না), তাই আপনি সর্বদা নিশ্চিত হতে পারেন যে whileলুপটি প্রস্থান করার পরে ডেটা প্রস্তুত । এই কারণে:

  • store()পতাকাটি sharedDataসেট হওয়ার পরে সঞ্চালিত হয় (আমরা ধরে নিই যে generateData()সর্বদা দরকারী কিছু ফিরিয়ে দেয়, বিশেষত, কখনই প্রত্যাবর্তন হয় না NULL) এবং std::memory_order_releaseঅর্ডার ব্যবহার করে :

memory_order_release

এই মেমোরি অর্ডার সহ একটি স্টোর অপারেশন রিলিজ অপারেশন সম্পাদন করে : বর্তমান থ্রেডে কোনও পঠিত বা লিখন এই স্টোরের পরে পুনরায় সাজানো যায় না । বর্তমান থ্রেডে সমস্ত লেখক অন্যান্য থ্রেডে দৃশ্যমান যা একই পারমাণবিক পরিবর্তনশীল অর্জন করে

  • sharedDatawhileলুপটি প্রস্থান করার পরে ব্যবহৃত হয়, এবং load()পতাকা থেকে এর পরে একটি শূন্যের মান ফিরে আসবে। অর্ডার load()ব্যবহার করে std::memory_order_acquire:

std::memory_order_acquire

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

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


2
বাস্তবে কি এমন আর্কিটেকচার রয়েছে যেগুলিতে ints এর মতো আদিমদের জন্য পারমাণবিক বোঝা এবং স্টোর নেই ?

7
এটি কেবল পারমাণবিকতা নিয়েই নয়। এটি অর্ডার করা, মাল্টি-কোর সিস্টেমগুলিতে আচরণ ইত্যাদি সম্পর্কেও আপনি এই নিবন্ধটি পড়তে চাইতে পারেন ।
ম্যাটিউজ গ্রজেজেক

4
@ আর্যমনসাগর যদি আমার ভুল না হয় তবে x86 এ এমনকি পড়ার এবং লেখাগুলি কেবলমাত্র শব্দ সীমানায় একত্রিত হলে পারমাণবিক।
ভি.শ্যাশঙ্কো

@ ম্যাটিউজগ্রজেজেক আমি একটি পারমাণবিক ধরণের রেফারেন্স নিয়েছি। নিম্নলিখিতটি এখনও অবজেক্ট অ্যাসাইনমেন্ট আদর্শের
এইচপিএসকিউ

3
@ টিএমএমবি হ্যাঁ, সাধারণত, আপনার দুটি (কমপক্ষে) দুটি পরিস্থিতি থাকতে পারে, যেখানে মৃত্যুদন্ড কার্যকর করার ক্রম পরিবর্তন করা যেতে পারে: (1) সংকলক আউটপুট কোডের আরও ভাল পারফরম্যান্স সরবরাহের জন্য নির্দেশিকাগুলিকে (যতটা মান মঞ্জুরি দেয়) পুনরায় অর্ডার করতে পারে (সিপিইউ রেজিস্টার, পূর্বাভাস ইত্যাদি ব্যবহারের উপর ভিত্তি করে) এবং (২) সিপিইউ একটি পৃথক ক্রমে নির্দেশাবলী কার্যকর করতে পারে, উদাহরণস্বরূপ, ক্যাশে সিঙ্ক পয়েন্টগুলির সংখ্যা হ্রাস করুন। std::atomic( std::memory_order) এর জন্য সরবরাহিত শৃঙ্খলাবদ্ধতাগুলি ঘটতে দেওয়া মঞ্জুরিপ্রাপ্ত সীমানাগুলি সীমাবদ্ধ করার ঠিক উদ্দেশ্যে কাজ করে।
ম্যাটিউজ গ্রজেজেক

20

আমি বুঝতে পারি যে std::atomic<> একটি বস্তুকে পারমাণবিক করে তোলে।

এটি দৃষ্টিভঙ্গির বিষয় ... আপনি এটি স্বেচ্ছাচারিত বস্তুগুলিতে প্রয়োগ করতে পারবেন না এবং তাদের ক্রিয়াকলাপগুলি পারমাণবিক হয়ে উঠতে পারবেন না, তবে (বেশিরভাগ) অবিচ্ছেদ্য ধরণের এবং পয়েন্টারগুলির জন্য সরবরাহিত বিশেষত্ব ব্যবহার করা যেতে পারে।

a = a + 12;

std::atomic<>এটি একক পরমাণু অপারেশনে সরল করে না (এতে টেমপ্লেট এক্সপ্রেশন ব্যবহার করে), পরিবর্তে operator T() const volatile noexceptসদস্যটি একটি পরমাণু load()করে a, তারপরে বারোটি যোগ করা হয় এবং operator=(T t) noexceptএকটি করে store(t)


এটাই আমি চেয়েছিলাম। একটি নিয়মিত int এ পারমাণবিক বোঝা এবং স্টোর থাকে। এটিকে পারমাণবিক <>

8
@ আরায়মানসাগর কেবলমাত্র একটি সাধারণ intপরিবর্তন করার ফলে পরিবর্তনগুলি অন্য থ্রেড থেকে দৃশ্যমান হয় তা নিশ্চিত হয় না এবং এটি পড়ার ফলে আপনি অন্যান্য থ্রেডের পরিবর্তনগুলি দেখতে নিশ্চিত করেন না এবং কিছু জিনিস যেমন my_int += 3আপনি ব্যবহার না করেন তত্ক্ষণিকভাবে সম্পন্ন করার গ্যারান্টিযুক্ত হয় না std::atomic<>- এতে জড়িত থাকতে পারে একটি আনয়ন, তারপরে যুক্ত করুন, তারপরে স্টোর সিকোয়েন্স, যাতে একই মানটি আপডেট করার চেষ্টা করছে এমন আরও কিছু থ্রেড আসার পরে এবং স্টোরের আগে আসতে পারে এবং আপনার থ্রেডের আপডেট ক্লোবার করে।
টনি ডেলরয়

" কেবলমাত্র সাধারণ ইনটকে সংশোধন করা পরিবর্তনটি অন্য থ্রেড থেকে দৃশ্যমান তা নিশ্চিত করে না " এটি এর চেয়েও খারাপ: দৃশ্যমানতার পরিমাপের যে কোনও প্রয়াসই ইউবিতে পরিণত হবে
কৌতূহলী

8

std::atomic উপস্থিত রয়েছে কারণ অনেক আইএসএর পক্ষে এটির জন্য সরাসরি হার্ডওয়্যার সমর্থন রয়েছে

সি ++ স্ট্যান্ডার্ড যা বলে std::atomicতা অন্যান্য উত্তরে বিশ্লেষণ করা হয়েছে।

সুতরাং এখন আসুন দেখুন কি std::atomicঅন্যরকম অন্তর্দৃষ্টি পেতে কি সংকলন।

এই পরীক্ষার মূল অবলম্বন হ'ল আধুনিক সিপিইউগুলির পারমাণবিক পূর্ণসংখ্যার ক্রিয়াকলাপের জন্য সরাসরি সমর্থন রয়েছে, উদাহরণস্বরূপ x86-এ লক উপসর্গ, এবং std::atomicমূলত সেই অনুবর্তনের পোর্টেবল ইন্টারফেস হিসাবে উপস্থিত রয়েছে: "লক" নির্দেশটি x86 সমাবেশে কী বোঝায়? আর্চ 6464 এ, এলডিএডিডি ব্যবহার করা হত।

এই সমর্থনটি আরও সাধারণ পদ্ধতির যেমন দ্রুততর বিকল্পগুলির জন্য অনুমোদন দেয় std::mutex, যা আরও জটিল মাল্টি-ইনস্ট্রাকশন বিভাগকে পারমাণবিক করতে পারে, তার চেয়ে ধীর গতিতে হওয়া std::atomicকারণ std::mutexএটি futexলিনাক্সে সিস্টেম কল করে, যা ইউজারল্যান্ডের নির্দেশাবলী থেকে নির্গতভাবে নির্ধারিত পদ্ধতি থেকে ধীরতর হয় std::atomic, আরও দেখুন: std :: mutex একটি বেড়া তৈরি করে?

আসুন নিম্নলিখিত মাল্টি-থ্রেডেড প্রোগ্রামটি বিবেচনা করুন যা একাধিক থ্রেড জুড়ে একটি বৈশ্বিক পরিবর্তনশীলকে বাড়িয়ে দেয়, বিভিন্ন সিঙ্ক্রোনাইজেশন প্রক্রিয়া যার উপর নির্ভর করে প্রাক প্রসেসর ডিফাইন ব্যবহৃত হয়।

main.cpp

#include <atomic>
#include <iostream>
#include <thread>
#include <vector>

size_t niters;

#if STD_ATOMIC
std::atomic_ulong global(0);
#else
uint64_t global = 0;
#endif

void threadMain() {
    for (size_t i = 0; i < niters; ++i) {
#if LOCK
        __asm__ __volatile__ (
            "lock incq %0;"
            : "+m" (global),
              "+g" (i) // to prevent loop unrolling
            :
            :
        );
#else
        __asm__ __volatile__ (
            ""
            : "+g" (i) // to prevent he loop from being optimized to a single add
            : "g" (global)
            :
        );
        global++;
#endif
    }
}

int main(int argc, char **argv) {
    size_t nthreads;
    if (argc > 1) {
        nthreads = std::stoull(argv[1], NULL, 0);
    } else {
        nthreads = 2;
    }
    if (argc > 2) {
        niters = std::stoull(argv[2], NULL, 0);
    } else {
        niters = 10;
    }
    std::vector<std::thread> threads(nthreads);
    for (size_t i = 0; i < nthreads; ++i)
        threads[i] = std::thread(threadMain);
    for (size_t i = 0; i < nthreads; ++i)
        threads[i].join();
    uint64_t expect = nthreads * niters;
    std::cout << "expect " << expect << std::endl;
    std::cout << "global " << global << std::endl;
}

গিটহাব উজানের দিকে

সংকলন, চালান এবং বিচ্ছিন্ন:

comon="-ggdb3 -O3 -std=c++11 -Wall -Wextra -pedantic main.cpp -pthread"
g++ -o main_fail.out                    $common
g++ -o main_std_atomic.out -DSTD_ATOMIC $common
g++ -o main_lock.out       -DLOCK       $common

./main_fail.out       4 100000
./main_std_atomic.out 4 100000
./main_lock.out       4 100000

gdb -batch -ex "disassemble threadMain" main_fail.out
gdb -batch -ex "disassemble threadMain" main_std_atomic.out
gdb -batch -ex "disassemble threadMain" main_lock.out

অত্যন্ত সম্ভবত "ভুল" রেসের শর্তের ফলাফলের জন্য main_fail.out:

expect 400000
global 100000

এবং অন্যের নির্দোষ "ডান" আউটপুট:

expect 400000
global 400000

এর বিযুক্তি main_fail.out:

   0x0000000000002780 <+0>:     endbr64 
   0x0000000000002784 <+4>:     mov    0x29b5(%rip),%rcx        # 0x5140 <niters>
   0x000000000000278b <+11>:    test   %rcx,%rcx
   0x000000000000278e <+14>:    je     0x27b4 <threadMain()+52>
   0x0000000000002790 <+16>:    mov    0x29a1(%rip),%rdx        # 0x5138 <global>
   0x0000000000002797 <+23>:    xor    %eax,%eax
   0x0000000000002799 <+25>:    nopl   0x0(%rax)
   0x00000000000027a0 <+32>:    add    $0x1,%rax
   0x00000000000027a4 <+36>:    add    $0x1,%rdx
   0x00000000000027a8 <+40>:    cmp    %rcx,%rax
   0x00000000000027ab <+43>:    jb     0x27a0 <threadMain()+32>
   0x00000000000027ad <+45>:    mov    %rdx,0x2984(%rip)        # 0x5138 <global>
   0x00000000000027b4 <+52>:    retq

এর বিযুক্তি main_std_atomic.out:

   0x0000000000002780 <+0>:     endbr64 
   0x0000000000002784 <+4>:     cmpq   $0x0,0x29b4(%rip)        # 0x5140 <niters>
   0x000000000000278c <+12>:    je     0x27a6 <threadMain()+38>
   0x000000000000278e <+14>:    xor    %eax,%eax
   0x0000000000002790 <+16>:    lock addq $0x1,0x299f(%rip)        # 0x5138 <global>
   0x0000000000002799 <+25>:    add    $0x1,%rax
   0x000000000000279d <+29>:    cmp    %rax,0x299c(%rip)        # 0x5140 <niters>
   0x00000000000027a4 <+36>:    ja     0x2790 <threadMain()+16>
   0x00000000000027a6 <+38>:    retq   

এর বিযুক্তি main_lock.out:

Dump of assembler code for function threadMain():
   0x0000000000002780 <+0>:     endbr64 
   0x0000000000002784 <+4>:     cmpq   $0x0,0x29b4(%rip)        # 0x5140 <niters>
   0x000000000000278c <+12>:    je     0x27a5 <threadMain()+37>
   0x000000000000278e <+14>:    xor    %eax,%eax
   0x0000000000002790 <+16>:    lock incq 0x29a0(%rip)        # 0x5138 <global>
   0x0000000000002798 <+24>:    add    $0x1,%rax
   0x000000000000279c <+28>:    cmp    %rax,0x299d(%rip)        # 0x5140 <niters>
   0x00000000000027a3 <+35>:    ja     0x2790 <threadMain()+16>
   0x00000000000027a5 <+37>:    retq

উপসংহার:

  • অ-পারমাণবিক সংস্করণ বিশ্বব্যাপী একটি নিবন্ধকে সংরক্ষণ করে এবং নিবন্ধকে বৃদ্ধি করে।

    অতএব, শেষে খুব সম্ভবত চার লিখেছেন ফিরে একই "ভুল" মান সঙ্গে বিশ্বব্যাপী ঘটতে 100000

  • std::atomicসংকলন lock addq। লক উপসর্গটি incপরমাণু অনুসারে নিম্নলিখিত আনয়ন, পরিবর্তন এবং মেমরি আপডেট করে।

  • আমাদের সুস্পষ্ট ইনলাইন অ্যাসেমব্লির লক উপসর্গটি প্রায় একই জিনিসটির সাথে সংকলন করে std::atomic, আমাদের incপরিবর্তে ব্যবহৃত হয় add। জিসিসি কেন বেছে নিল তা নিশ্চিত নয় add, আমাদের আইএনসি 1 বাইট আরও ছোট করে ডিকোডিং করেছে generated

এআরএমভি 8 নতুন সিপিইউতে এলডিএএক্সআর + এসটিএলএক্সআর বা এলডিএডিডি ব্যবহার করতে পারে: আমি কীভাবে প্লেইন সি-তে থ্রেড শুরু করব?

উবুন্টু 19.10 এএমডি 64, জিসিসি 9.2.1, লেনভো থিঙ্কপ্যাড P51 তে পরীক্ষিত।

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