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 তে পরীক্ষিত।
a.fetch_add(12)
যদি কোনও পারমাণবিক আরএমডাব্লু চান তবে আপনাকে এমন কিছু ব্যবহার করতে হবে ।