Android এর জন্য SMP প্রাইমার

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

ভূমিকা

SMP হল "সিমেট্রিক মাল্টি-প্রসেসর" এর সংক্ষিপ্ত রূপ। এটি একটি নকশা বর্ণনা করে যেখানে দুটি বা ততোধিক অভিন্ন CPU কোর প্রধান মেমরিতে অ্যাক্সেস ভাগ করে। কয়েক বছর আগে পর্যন্ত, সমস্ত অ্যান্ড্রয়েড ডিভাইস ইউপি (ইউনি-প্রসেসর) ছিল।

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

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

এই নথির বাকি অংশ ব্যাখ্যা করবে কেন, এবং আপনাকে বলবে যে আপনার কোড সঠিকভাবে আচরণ করছে তা নিশ্চিত করতে আপনাকে কী করতে হবে।

মেমরি সামঞ্জস্যের মডেল: কেন এসএমপিগুলি একটু আলাদা

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

বিষয়ের আরও পুঙ্খানুপুঙ্খ চিকিত্সার জন্য নির্দেশকের জন্য নথির শেষে আরও পড়া দেখুন।

মেমরি সঙ্গতি মডেল, বা প্রায়শই শুধুমাত্র "মেমরি মডেল", প্রোগ্রামিং ভাষা বা হার্ডওয়্যার আর্কিটেকচার মেমরি অ্যাক্সেস সম্পর্কে গ্যারান্টি দেয় তা বর্ণনা করে। উদাহরণস্বরূপ, যদি আপনি A ঠিকানার জন্য একটি মান লেখেন এবং তারপর B ঠিকানায় একটি মান লেখেন, মডেলটি গ্যারান্টি দিতে পারে যে প্রতিটি CPU কোর সেই ক্রমানুসারে লেখাগুলি দেখতে পাবে।

বেশিরভাগ প্রোগ্রামাররা যে মডেলটিতে অভ্যস্ত তা হল অনুক্রমিক সামঞ্জস্য , যা এইভাবে বর্ণনা করা হয়েছে ( Adve & Gharachorloo ) :

  • সমস্ত মেমরি ক্রিয়াকলাপগুলি একবারে এককভাবে কার্যকর হয় বলে মনে হচ্ছে
  • একটি একক থ্রেডের সমস্ত ক্রিয়াকলাপ সেই প্রসেসরের প্রোগ্রাম দ্বারা বর্ণিত ক্রমানুসারে কার্যকর হয়।

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

আপনি যদি কিছু কোড দেখেন এবং দেখেন যে এটি মেমরি থেকে কিছু রিড এবং রাইট করে, একটি ক্রমানুসারে-সামঞ্জস্যপূর্ণ CPU আর্কিটেকচারে আপনি জানেন যে কোডটি প্রত্যাশিত ক্রমে সেই পড়া এবং লেখাগুলি করবে। এটা সম্ভব যে CPU প্রকৃতপক্ষে নির্দেশাবলীর পুনর্বিন্যাস করছে এবং পড়তে এবং লিখতে দেরি করছে, কিন্তু ডিভাইসে চলমান কোডের জন্য এটি বলার কোন উপায় নেই যে সিপিইউ একটি সরল পদ্ধতিতে নির্দেশাবলী চালানো ছাড়া অন্য কিছু করছে। (আমরা মেমরি-ম্যাপ করা ডিভাইস ড্রাইভার I/O উপেক্ষা করব।)

এই পয়েন্টগুলি ব্যাখ্যা করার জন্য কোডের ছোট স্নিপেটগুলি বিবেচনা করা দরকারী, সাধারণত লিটমাস পরীক্ষা হিসাবে উল্লেখ করা হয়।

দুটি থ্রেডে চলমান কোড সহ এখানে একটি সাধারণ উদাহরণ:

থ্রেড 1 থ্রেড 2
A = 3
B = 5
reg0 = B
reg1 = A

এই এবং ভবিষ্যতের সমস্ত লিটমাস উদাহরণে, মেমরি অবস্থানগুলিকে বড় অক্ষর (A, B, C) দ্বারা উপস্থাপন করা হয় এবং CPU রেজিস্টারগুলি "reg" দিয়ে শুরু হয়। সমস্ত মেমরি প্রাথমিকভাবে শূন্য। নির্দেশাবলী উপর থেকে নিচ পর্যন্ত কার্যকর করা হয়। এখানে, থ্রেড 1 অবস্থান A-তে 3 মান সঞ্চয় করে এবং তারপর B অবস্থানে মান 5। থ্রেড 2 অবস্থান B থেকে reg0 তে মান লোড করে এবং তারপর অবস্থান A থেকে reg1 এ মান লোড করে। (মনে রাখবেন যে আমরা এক ক্রমে লিখছি এবং অন্য ক্রমে পড়ছি।)

থ্রেড 1 এবং থ্রেড 2 বিভিন্ন CPU কোরে চালানোর জন্য অনুমান করা হয়। মাল্টি-থ্রেডেড কোড সম্পর্কে চিন্তা করার সময় আপনার সর্বদা এই অনুমান করা উচিত।

অনুক্রমিক ধারাবাহিকতা গ্যারান্টি দেয় যে, উভয় থ্রেড কার্যকর করা শেষ হওয়ার পরে, নিবন্ধনগুলি নিম্নলিখিত রাজ্যগুলির মধ্যে একটিতে থাকবে:

নিবন্ধন করে রাজ্যগুলি
reg0=5, reg1=3 সম্ভব (থ্রেড 1 প্রথম দৌড়ে)
reg0=0, reg1=0 সম্ভব (থ্রেড 2 প্রথমে দৌড়েছিল)
reg0=0, reg1=3 সম্ভব (সমসাময়িক মৃত্যুদন্ড)
reg0=5, reg1=0 কখনই

এমন একটি পরিস্থিতিতে যেতে যেখানে আমরা B=5 দেখতে পাই স্টোর A-তে দেখার আগে, হয় রিড বা রাইটগুলি ক্রমবর্ধমান হতে হবে। একটি ক্রমানুসারে সামঞ্জস্যপূর্ণ মেশিনে, এটি ঘটতে পারে না।

x86 এবং ARM সহ ইউনি-প্রসেসরগুলি সাধারণত ক্রমানুসারে সামঞ্জস্যপূর্ণ। ওএস কার্নেল তাদের মধ্যে স্যুইচ করার সাথে সাথে থ্রেডগুলি ইন্টারলিভড ফ্যাশনে কার্যকর হয়। x86 এবং ARM সহ বেশিরভাগ SMP সিস্টেম ক্রমানুসারে সামঞ্জস্যপূর্ণ নয়। উদাহরণস্বরূপ, হার্ডওয়্যারের জন্য তাদের মেমরিতে যাওয়ার পথে বাফার স্টোরগুলি সাধারণ, যাতে তারা অবিলম্বে মেমরিতে না পৌঁছায় এবং অন্যান্য কোরগুলিতে দৃশ্যমান হয়।

বিবরণ যথেষ্ট পরিবর্তিত হয়. উদাহরণস্বরূপ, x86, যদিও ক্রমানুসারে সামঞ্জস্যপূর্ণ নয়, তবুও গ্যারান্টি দেয় যে reg0 = 5 এবং reg1 = 0 অসম্ভব। দোকান বাফার করা হয়, কিন্তু তাদের অর্ডার বজায় রাখা হয়. অন্যদিকে, এআরএম করে না। বাফার করা স্টোরের অর্ডার রক্ষণাবেক্ষণ করা হয় না, এবং স্টোরগুলি একই সময়ে অন্য সমস্ত কোরে পৌঁছাতে পারে না। এই পার্থক্যগুলি সমাবেশ প্রোগ্রামারদের জন্য গুরুত্বপূর্ণ। যাইহোক, আমরা নীচে দেখতে পাব, C, C++ বা জাভা প্রোগ্রামাররা এমনভাবে প্রোগ্রাম করতে পারে এবং করা উচিত যা এই ধরনের স্থাপত্যগত পার্থক্যগুলিকে লুকিয়ে রাখে।

এখনও অবধি, আমরা অবাস্তবভাবে ধরে নিয়েছি যে এটি কেবলমাত্র হার্ডওয়্যার যা নির্দেশাবলী পুনরায় সাজায়। বাস্তবে, কম্পাইলার কর্মক্ষমতা উন্নত করার জন্য নির্দেশাবলী পুনরায় সাজায়। আমাদের উদাহরণে, কম্পাইলার সিদ্ধান্ত নিতে পারে যে থ্রেড 2-এর পরবর্তী কিছু কোডের জন্য reg0 এর আগে reg1 এর মান প্রয়োজন ছিল এবং এইভাবে প্রথমে reg1 লোড করুন। অথবা কিছু পূর্ববর্তী কোড ইতিমধ্যে A লোড হয়ে থাকতে পারে এবং কম্পাইলার A আবার লোড করার পরিবর্তে সেই মানটি পুনরায় ব্যবহার করার সিদ্ধান্ত নিতে পারে। উভয় ক্ষেত্রেই, reg0 এবং reg1 লোডগুলি পুনরায় সাজানো হতে পারে।

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

যেহেতু কম্পাইলাররা মেমরি অ্যাক্সেসগুলি পুনরায় সাজাতে পারে, তাই এই সমস্যাটি আসলে SMP-তে নতুন নয়। এমনকি একটি ইউনিপ্রসেসরেও, একটি কম্পাইলার আমাদের উদাহরণে লোডগুলিকে reg0 এবং reg1 এ পুনরায় সাজাতে পারে এবং থ্রেড 1 পুনরায় সাজানো নির্দেশাবলীর মধ্যে নির্ধারিত হতে পারে। কিন্তু যদি আমাদের কম্পাইলার পুনরায় ক্রমানুসারে না ঘটে তবে আমরা এই সমস্যাটি কখনই পর্যবেক্ষণ করতে পারি না। বেশিরভাগ এআরএম এসএমপি-তে, এমনকি কম্পাইলার পুনর্বিন্যাস না করেও, পুনর্বিন্যাস সম্ভবত দেখা যাবে, সম্ভবত খুব বড় সংখ্যক সফল মৃত্যুদন্ডের পরে। আপনি অ্যাসেম্বলি ল্যাঙ্গুয়েজে প্রোগ্রামিং না করলে, এসএমপিগুলি সাধারণত এটিকে আরও বেশি করে তোলে যে আপনি সেখানে সমস্যাগুলি দেখতে পাবেন।

ডেটা রেস-মুক্ত প্রোগ্রামিং

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

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

একটি "ডেটা রেস" কি?

একটি ডেটা রেস ঘটে যখন কমপক্ষে দুটি থ্রেড একই সাথে একই সাধারণ ডেটা অ্যাক্সেস করে এবং তাদের মধ্যে অন্তত একটি এটি সংশোধন করে। "সাধারণ ডেটা" দ্বারা আমরা এমন কিছু বোঝাই যা থ্রেড যোগাযোগের জন্য বিশেষভাবে একটি সিঙ্ক্রোনাইজেশন অবজেক্ট নয়। মিউটেক্স, কন্ডিশন ভেরিয়েবল, জাভা উদ্বায়ী, বা C++ পারমাণবিক বস্তু সাধারণ ডেটা নয় এবং তাদের অ্যাক্সেস রেস করার অনুমতি দেওয়া হয়। প্রকৃতপক্ষে এগুলি অন্যান্য বস্তুর ডেটা রেস প্রতিরোধ করতে ব্যবহৃত হয়।

দুটি থ্রেড একই সাথে একই মেমরি অবস্থান অ্যাক্সেস করে কিনা তা নির্ধারণ করার জন্য, আমরা উপরে থেকে মেমরি-পুনঃক্রম আলোচনা উপেক্ষা করতে পারি এবং অনুক্রমিক সামঞ্জস্যতা অনুমান করতে পারি। A এবং B যদি সাধারণ বুলিয়ান ভেরিয়েবল হয় যা প্রাথমিকভাবে মিথ্যা হয় তাহলে নিম্নলিখিত প্রোগ্রামে ডেটা রেস নেই:

থ্রেড 1 থ্রেড 2
if (A) B = true if (B) A = true

যেহেতু ক্রিয়াকলাপগুলি পুনর্বিন্যাস করা হয় না, উভয় শর্তই মিথ্যা হিসাবে মূল্যায়ন করা হবে, এবং কোনও পরিবর্তনশীল কখনও আপডেট করা হয় না। সুতরাং একটি ডেটা রেস হতে পারে না। থ্রেড 1 এ A এবং স্টোর থেকে B পর্যন্ত লোডটি যদি কোনওভাবে পুনরায় সাজানো হয় তবে কী ঘটতে পারে তা নিয়ে ভাবার দরকার নেই। কম্পাইলারকে থ্রেড 1কে " B = true; if (!A) B = false " হিসাবে পুনরায় লেখার অনুমতি দেওয়া হয় না। এটি দিনের আলোতে শহরের মাঝখানে সসেজ তৈরির মতো হবে।

ডেটা রেসগুলি আনুষ্ঠানিকভাবে পূর্ণসংখ্যা এবং রেফারেন্স বা পয়েন্টারের মতো মৌলিক অন্তর্নির্মিত প্রকারগুলিতে সংজ্ঞায়িত করা হয়। একই সাথে অন্য থ্রেডে পড়ার সময় একটি int এ বরাদ্দ করা স্পষ্টতই একটি ডেটা রেস। কিন্তু C++ স্ট্যান্ডার্ড লাইব্রেরি এবং জাভা কালেকশন লাইব্রেরি উভয়ই লেখা হয়েছে যাতে আপনি লাইব্রেরি স্তরে ডেটা রেস সম্পর্কেও যুক্তি দিতে পারেন। তারা প্রতিশ্রুতি দেয় যে একই কন্টেইনারে একযোগে অ্যাক্সেস না থাকলে ডেটা রেস প্রবর্তন করবে না, যার মধ্যে অন্তত একটি এটি আপডেট করে। এক থ্রেডে একটি set<T> আপডেট করার সময় এটি একই সাথে অন্যটিতে পড়ার সময় লাইব্রেরি একটি ডেটা রেস প্রবর্তন করতে দেয় এবং এইভাবে এটিকে "লাইব্রেরি-স্তরের ডেটা রেস" হিসাবে অনানুষ্ঠানিকভাবে ভাবা যেতে পারে। বিপরীতভাবে, একটি থ্রেডে একটি set<T> আপডেট করার সময়, অন্য একটিতে অন্যটি পড়ার সময়, ডেটা রেসের ফলাফল হয় না, কারণ লাইব্রেরি সেই ক্ষেত্রে (নিম্ন-স্তরের) ডেটা রেস প্রবর্তন না করার প্রতিশ্রুতি দেয়।

সাধারনত একটি ডেটা স্ট্রাকচারের বিভিন্ন ক্ষেত্রগুলিতে একযোগে অ্যাক্সেসগুলি একটি ডেটা রেস প্রবর্তন করতে পারে না। তবে এই নিয়মের একটি গুরুত্বপূর্ণ ব্যতিক্রম রয়েছে: C বা C++-এ বিট-ক্ষেত্রগুলির ধারাবাহিক ক্রমগুলিকে একক "মেমরি অবস্থান" হিসাবে বিবেচনা করা হয়। এই ধরনের ক্রমানুসারে যেকোনো বিট-ফিল্ড অ্যাক্সেস করাকে ডেটা রেসের অস্তিত্ব নির্ধারণের উদ্দেশ্যে সেগুলির সমস্ত অ্যাক্সেস করা হিসাবে গণ্য করা হয়। এটি সংলগ্ন বিটগুলি পড়া এবং পুনরায় লেখা ছাড়াই পৃথক বিটগুলি আপডেট করতে সাধারণ হার্ডওয়্যারের অক্ষমতাকে প্রতিফলিত করে। জাভা প্রোগ্রামারদের কোন অনুরূপ উদ্বেগ নেই।

ডেটা রেস এড়ানো

আধুনিক প্রোগ্রামিং ভাষাগুলি ডেটা রেস এড়াতে অনেকগুলি সিঙ্ক্রোনাইজেশন প্রক্রিয়া সরবরাহ করে। সবচেয়ে মৌলিক সরঞ্জাম হল:

তালা বা Mutexes
Mutexes (C++11 std::mutex , অথবা pthread_mutex_t ), বা Java-তে synchronized ব্লক ব্যবহার করা যেতে পারে যে কোডের নির্দিষ্ট বিভাগ একই ডেটা অ্যাক্সেস করার কোডের অন্যান্য বিভাগের সাথে একযোগে চলবে না। আমরা এইগুলি এবং অন্যান্য অনুরূপ সুবিধাগুলিকে সাধারণভাবে "লক" হিসাবে উল্লেখ করব। একটি ভাগ করা ডেটা স্ট্রাকচার অ্যাক্সেস করার আগে ধারাবাহিকভাবে একটি নির্দিষ্ট লক অর্জন করা এবং পরে এটি প্রকাশ করা, ডেটা স্ট্রাকচার অ্যাক্সেস করার সময় ডেটা রেস প্রতিরোধ করে। এটি নিশ্চিত করে যে আপডেট এবং অ্যাক্সেসগুলি পারমাণবিক, অর্থাৎ ডেটা কাঠামোর অন্য কোনও আপডেট মাঝখানে চলতে পারে না। ডেটা রেস প্রতিরোধের জন্য এটি প্রাপ্যভাবে সবচেয়ে সাধারণ হাতিয়ার। জাভা synchronized ব্লক বা C++ lock_guard বা unique_lock ব্যবহার নিশ্চিত করে যে ব্যতিক্রমের ক্ষেত্রে লকগুলি সঠিকভাবে প্রকাশ করা হয়েছে।
উদ্বায়ী/পারমাণবিক ভেরিয়েবল
জাভা volatile ক্ষেত্রগুলি সরবরাহ করে যা ডেটা রেস প্রবর্তন না করে সমকালীন অ্যাক্সেস সমর্থন করে। 2011 সাল থেকে, C এবং C++ অনুরূপ শব্দার্থবিদ্যা সহ atomic ভেরিয়েবল এবং ক্ষেত্র সমর্থন করে। এগুলি সাধারণত লকগুলির চেয়ে ব্যবহার করা আরও কঠিন, কারণ তারা শুধুমাত্র নিশ্চিত করে যে একটি একক পরিবর্তনশীলের ব্যক্তিগত অ্যাক্সেস পারমাণবিক। (C++-এ এটি সাধারণত সাধারণ পঠন-সংশোধন-রাইট অপারেশনগুলিতে প্রসারিত হয়, যেমন ইনক্রিমেন্ট। জাভা এর জন্য বিশেষ পদ্ধতির কল প্রয়োজন।) লকগুলির বিপরীতে, volatile বা atomic ভেরিয়েবলগুলিকে অন্য থ্রেডগুলিকে দীর্ঘতর কোড ক্রমগুলিতে হস্তক্ষেপ করা থেকে রোধ করতে সরাসরি ব্যবহার করা যায় না। .

এটি লক্ষ্য করা গুরুত্বপূর্ণ যে C++ এবং জাভাতে volatile -এর খুব আলাদা অর্থ রয়েছে। C++-এ, volatile ডেটা রেস প্রতিরোধ করে না, যদিও পুরানো কোড প্রায়শই এটিকে atomic বস্তুর অভাবের জন্য একটি সমাধান হিসাবে ব্যবহার করে। এটি আর সুপারিশ করা হয় না; C++ এ, ভেরিয়েবলের জন্য atomic<T> ব্যবহার করুন যা একসাথে একাধিক থ্রেড দ্বারা অ্যাক্সেস করা যায়। C++ volatile ডিভাইস রেজিস্টার এবং এর মতন জন্য বোঝানো হয়।

C/C++ atomic ভেরিয়েবল বা জাভা volatile ভেরিয়েবলগুলি অন্যান্য ভেরিয়েবলে ডেটা রেস প্রতিরোধ করতে ব্যবহার করা যেতে পারে। যদি flag atomic<bool> বা atomic_bool (C/C++) বা volatile boolean (জাভা) টাইপ বলে ঘোষণা করা হয় এবং প্রাথমিকভাবে মিথ্যা হয় তবে নিম্নলিখিত স্নিপেটটি ডেটা-রেস-মুক্ত:

থ্রেড 1 থ্রেড 2
A = ...
flag = true
while (!flag) {}
... = A

যেহেতু থ্রেড 2 flag সেট করার জন্য অপেক্ষা করে, তাই থ্রেড 2-এ A তে অ্যাক্সেস থ্রেড 1-এ A এর অ্যাসাইনমেন্টের পরেই ঘটতে হবে এবং একই সাথে হবে না। সুতরাং A তে কোনো ডেটা রেস নেই। flag দৌড়কে ডেটা রেস হিসাবে গণনা করা হয় না, যেহেতু উদ্বায়ী/পারমাণবিক অ্যাক্সেসগুলি "সাধারণ মেমরি অ্যাক্সেস" নয়।

পূর্ববর্তী লিটমাস পরীক্ষার মতো কোডটি প্রত্যাশিতভাবে আচরণ করার জন্য যথেষ্ট পরিমাণে মেমরি পুনর্বিন্যাস প্রতিরোধ বা লুকানোর জন্য বাস্তবায়নের প্রয়োজন। এটি সাধারণত উদ্বায়ী/পারমাণবিক মেমরি অ্যাক্সেসকে সাধারণ অ্যাক্সেসের তুলনায় যথেষ্ট ব্যয়বহুল করে তোলে।

যদিও পূর্বের উদাহরণটি ডেটা-রেস-মুক্ত, তবে জাভাতে Object.wait() এর সাথে লক করা হয় বা C/C++-এ কন্ডিশন ভেরিয়েবল সাধারণত একটি ভাল সমাধান দেয় যা ব্যাটারির শক্তি নিষ্কাশন করার সময় লুপে অপেক্ষা করতে হয় না।

যখন মেমরি পুনর্বিন্যাস দৃশ্যমান হয়

ডেটা-রেস-মুক্ত প্রোগ্রামিং সাধারণত আমাদের মেমরি অ্যাক্সেস পুনঃক্রম সংক্রান্ত সমস্যাগুলির সাথে স্পষ্টভাবে মোকাবেলা করা থেকে বাঁচায়। যাইহোক, এমন বেশ কয়েকটি ক্ষেত্রে রয়েছে যেখানে পুনর্বিন্যাস দৃশ্যমান হয়:
  1. যদি আপনার প্রোগ্রামে একটি বাগ থাকে যার ফলে একটি অনিচ্ছাকৃত ডেটা রেস হয়, কম্পাইলার এবং হার্ডওয়্যার রূপান্তর দৃশ্যমান হতে পারে এবং আপনার প্রোগ্রামের আচরণ আশ্চর্যজনক হতে পারে। উদাহরণস্বরূপ, যদি আমরা পূর্ববর্তী উদাহরণে flag উদ্বায়ী ঘোষণা করতে ভুলে যাই, থ্রেড 2 একটি অপ্রবর্তিত A দেখতে পারে। অথবা কম্পাইলার সিদ্ধান্ত নিতে পারে যে থ্রেড 2 এর লুপের সময় পতাকাটি সম্ভবত পরিবর্তন করা যাবে না এবং প্রোগ্রামটিকে এতে রূপান্তরিত করবে
    থ্রেড 1 থ্রেড 2
    A = ...
    flag = true
    reg0 = পতাকা; যখন (!reg0) {}
    ... = ক
    আপনি যখন ডিবাগ করেন, আপনি দেখতে পারেন যে flag সত্য হওয়া সত্ত্বেও লুপটি চিরতরে চলতে থাকবে।
  2. C++ কোনো রেস না থাকলেও সুস্পষ্টভাবে অনুক্রমিক ধারাবাহিকতা শিথিল করার সুবিধা প্রদান করে। পারমাণবিক ক্রিয়াকলাপগুলি স্পষ্ট memory_order_ _... আর্গুমেন্ট নিতে পারে। একইভাবে, java.util.concurrent.atomic প্যাকেজ অনুরূপ সুবিধার আরো সীমাবদ্ধ সেট প্রদান করে, বিশেষ করে lazySet() । এবং জাভা প্রোগ্রামাররা মাঝে মাঝে একই ধরনের প্রভাবের জন্য ইচ্ছাকৃত ডেটা রেস ব্যবহার করে। এই সমস্ত প্রোগ্রামিং জটিলতা একটি বড় খরচে কর্মক্ষমতা উন্নতি প্রদান করে. আমরা নীচে শুধুমাত্র সংক্ষিপ্তভাবে তাদের আলোচনা.
  3. কিছু C এবং C++ কোড পুরানো স্টাইলে লেখা হয়, যা বর্তমান ভাষার মানগুলির সাথে সম্পূর্ণ সামঞ্জস্যপূর্ণ নয়, যেখানে atomic পরিবর্তে volatile ভেরিয়েবল ব্যবহার করা হয় এবং তথাকথিত বেড়া বা বাধা সন্নিবেশ করে মেমরি অর্ডারিং স্পষ্টভাবে অস্বীকৃত। এর জন্য অ্যাক্সেস পুনঃক্রম এবং হার্ডওয়্যার মেমরি মডেল বোঝার বিষয়ে স্পষ্ট যুক্তি প্রয়োজন। এই লাইনগুলির সাথে একটি কোডিং শৈলী এখনও লিনাক্স কার্নেলে ব্যবহৃত হয়। এটি নতুন অ্যান্ড্রয়েড অ্যাপ্লিকেশনগুলিতে ব্যবহার করা উচিত নয় এবং এখানে আরও আলোচনা করা হয়নি৷

অনুশীলন করা

মেমরির সামঞ্জস্যের সমস্যাগুলি ডিবাগ করা খুব কঠিন হতে পারে। যদি একটি অনুপস্থিত লক, atomic বা volatile ঘোষণার কারণে কিছু কোড বাসি ডেটা পড়তে পারে, তাহলে আপনি ডিবাগার দিয়ে মেমরি ডাম্প পরীক্ষা করে কেন তা বুঝতে পারবেন না। যখন আপনি একটি ডিবাগার ক্যোয়ারী ইস্যু করতে পারেন, তখন CPU কোরগুলি সমস্ত অ্যাক্সেসের সম্পূর্ণ সেট পর্যবেক্ষণ করেছে এবং মেমরির বিষয়বস্তু এবং CPU রেজিস্টারগুলি "অসম্ভব" অবস্থায় রয়েছে বলে মনে হবে।

সি-তে কী করা উচিত নয়

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

C/C++ এবং "অস্থির"

C এবং C++ volatile ঘোষণা একটি খুব বিশেষ উদ্দেশ্য টুল। তারা কম্পাইলারকে অস্থির অ্যাক্সেসগুলি পুনর্বিন্যাস বা অপসারণ থেকে বাধা দেয়। এটি হার্ডওয়্যার ডিভাইস রেজিস্টারে কোড অ্যাক্সেস করার জন্য, একাধিক স্থানে মেমরি ম্যাপ করা বা setjmp এর সাথে সংযোগের জন্য সহায়ক হতে পারে। কিন্তু C এবং C++ volatile , জাভা volatile থেকে ভিন্ন, থ্রেড যোগাযোগের জন্য ডিজাইন করা হয়নি।

C এবং C++-এ, volatile ডেটাতে অ্যাক্সেসগুলি অ-উদ্বায়ী ডেটাতে অ্যাক্সেসের সাথে পুনরায় সাজানো যেতে পারে এবং কোনও পারমাণবিক গ্যারান্টি নেই। এইভাবে volatile পোর্টেবল কোডে থ্রেডের মধ্যে ডেটা ভাগ করার জন্য ব্যবহার করা যাবে না, এমনকি একটি ইউনিপ্রসেসরেও। C volatile সাধারণত হার্ডওয়্যার দ্বারা অ্যাক্সেস পুনঃক্রম রোধ করে না, তাই এটি মাল্টি-থ্রেডেড SMP পরিবেশে এমনকি কম দরকারী। এই কারণেই C11 এবং C++11 atomic বস্তুকে সমর্থন করে। আপনি পরিবর্তে তাদের ব্যবহার করা উচিত.

অনেক পুরানো C এবং C++ কোড এখনও থ্রেড যোগাযোগের জন্য volatile অপব্যবহার করে। এটি প্রায়শই একটি মেশিন রেজিস্টারে ফিট করা ডেটার জন্য সঠিকভাবে কাজ করে, যদি এটি স্পষ্ট বেড়ার সাথে ব্যবহার করা হয় বা মেমরি অর্ডারিং গুরুত্বপূর্ণ নয় এমন ক্ষেত্রে। কিন্তু ভবিষ্যতের কম্পাইলারদের সাথে সঠিকভাবে কাজ করার নিশ্চয়তা নেই।

উদাহরণ

বেশিরভাগ ক্ষেত্রে আপনি একটি পারমাণবিক অপারেশনের পরিবর্তে একটি লক (যেমন একটি pthread_mutex_t বা C++11 std::mutex ) দিয়ে ভাল হবেন, তবে আমরা পরবর্তীটিকে ব্যবহার করব কীভাবে ব্যবহারিক পরিস্থিতিতে ব্যবহার করা হবে তা ব্যাখ্যা করার জন্য।

MyThing* gGlobalThing = NULL;  // Wrong!  See below.
void initGlobalThing()    // runs in Thread 1
{
    MyStruct* thing = malloc(sizeof(*thing));
    memset(thing, 0, sizeof(*thing));
    thing->x = 5;
    thing->y = 10;
    /* initialization complete, publish */
    gGlobalThing = thing;
}
void useGlobalThing()    // runs in Thread 2
{
    if (gGlobalThing != NULL) {
        int i = gGlobalThing->x;    // could be 5, 0, or uninitialized data
        ...
    }
}

এখানে ধারণাটি হল যে আমরা একটি কাঠামো বরাদ্দ করি, এর ক্ষেত্রগুলি শুরু করি এবং একেবারে শেষে আমরা এটিকে একটি গ্লোবাল ভেরিয়েবলে সংরক্ষণ করে "প্রকাশ করি"। সেই মুহুর্তে, অন্য কোন থ্রেড এটি দেখতে পারে, তবে এটি ঠিক আছে যেহেতু এটি সম্পূর্ণরূপে শুরু হয়েছে, তাই না?

সমস্যা হল যে ক্ষেত্রগুলি শুরু হওয়ার আগে gGlobalThing এ স্টোরটি পর্যবেক্ষণ করা যেতে পারে, সাধারণত কারণ হয় কম্পাইলার বা প্রসেসর স্টোরগুলিকে gGlobalThing এবং thing->x এ পুনরায় সাজিয়েছে। thing->x 5, 0, বা এমনকি অপ্রবর্তিত ডেটা দেখতে পারে।

এখানে মূল সমস্যা হল gGlobalThing এ ডেটা রেস। যদি থ্রেড 1 initGlobalThing() কল করে যখন থ্রেড 2 কল করে useGlobalThing() করে, gGlobalThing লেখার সময় পড়া যেতে পারে।

এটি gGlobalThing পরমাণু হিসাবে ঘোষণা করে ঠিক করা যেতে পারে। C++11-এ:

atomic<MyThing*> gGlobalThing(NULL);

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

জাভাতে কি করা উচিত নয়

আমরা কিছু প্রাসঙ্গিক জাভা ভাষার বৈশিষ্ট্য নিয়ে আলোচনা করিনি, তাই আমরা প্রথমে সেগুলিকে দ্রুত দেখে নেব।

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

আপাতত আমরা ডেটা-রেস-মুক্ত মডেল মেনে চলব, যার জন্য জাভা মূলত C এবং C++ এর মতো একই গ্যারান্টি প্রদান করে। আবার, ভাষা কিছু আদিম প্রদান করে যা স্পষ্টভাবে অনুক্রমিক সামঞ্জস্যতা শিথিল করে, উল্লেখযোগ্যভাবে lazySet() এবং weakCompareAndSet() কল java.util.concurrent.atomic . C এবং C++ এর মতো, আমরা আপাতত এগুলিকে উপেক্ষা করব।

জাভার "সিঙ্ক্রোনাইজড" এবং "অস্থির" কীওয়ার্ড

"সিঙ্ক্রোনাইজড" কীওয়ার্ডটি জাভা ভাষার অন্তর্নির্মিত লকিং মেকানিজম প্রদান করে। প্রতিটি বস্তুর একটি সম্পর্কিত "মনিটর" আছে যা পারস্পরিক একচেটিয়া অ্যাক্সেস প্রদান করতে ব্যবহার করা যেতে পারে। যদি দুটি থ্রেড একই বস্তুতে "সিঙ্ক্রোনাইজ" করার চেষ্টা করে, তাদের মধ্যে একটি অন্যটি সম্পূর্ণ না হওয়া পর্যন্ত অপেক্ষা করবে।

আমরা উপরে উল্লেখ করেছি, জাভার volatile T হল C++11 এর atomic<T>volatile ক্ষেত্রগুলিতে একযোগে অ্যাক্সেস অনুমোদিত, এবং এর ফলে ডেটা রেস হয় না। lazySet() et al উপেক্ষা করা। এবং ডেটা রেস, ফলাফল এখনও ধারাবাহিকভাবে সামঞ্জস্যপূর্ণ হয় তা নিশ্চিত করা জাভা ভিএম-এর কাজ।

বিশেষ করে, যদি থ্রেড 1 একটি volatile ক্ষেত্রে লেখে, এবং থ্রেড 2 পরবর্তীতে সেই একই ক্ষেত্র থেকে পড়ে এবং নতুন লিখিত মান দেখে, তাহলে থ্রেড 2 থ্রেড 1 দ্বারা পূর্বে করা সমস্ত লেখা দেখতে পাবে। মেমরি প্রভাবের পরিপ্রেক্ষিতে, একটি উদ্বায়ী লেখা একটি মনিটর রিলিজের অনুরূপ, এবং একটি উদ্বায়ী থেকে পড়া একটি মনিটর অর্জনের মত।

C++ এর atomic থেকে একটি উল্লেখযোগ্য পার্থক্য রয়েছে: আমরা যদি volatile int x; জাভাতে, তারপর x++ x = x + 1 এর মতই; এটি একটি পারমাণবিক লোড সঞ্চালন করে, ফলাফল বৃদ্ধি করে এবং তারপর একটি পারমাণবিক স্টোর সঞ্চালন করে। C++ এর বিপরীতে, সামগ্রিকভাবে বৃদ্ধি পারমাণবিক নয়। পারমাণবিক বৃদ্ধি অপারেশন পরিবর্তে java.util.concurrent.atomic দ্বারা প্রদান করা হয়।

উদাহরণ

এখানে একটি একঘেয়ে কাউন্টারের একটি সহজ, ভুল বাস্তবায়ন: ( জাভা তত্ত্ব এবং অনুশীলন: অস্থিরতা পরিচালনা )

class Counter {
    private int mValue;
    public int get() {
        return mValue;
    }
    public void incr() {
        mValue++;
    }
}

ধরে নিন get() এবং incr() একাধিক থ্রেড থেকে কল করা হয়েছে, এবং আমরা নিশ্চিত হতে চাই যে প্রতিটি থ্রেড যখন get() কল করা হয় তখন বর্তমান গণনা দেখতে পায়। সবচেয়ে স্পষ্ট সমস্যা হল যে mValue++ আসলে তিনটি অপারেশন:

  1. reg = mValue
  2. reg = reg + 1
  3. mValue = reg

যদি দুটি থ্রেড একযোগে incr() এ এক্সিকিউট হয়, তাহলে একটি আপডেট হারিয়ে যেতে পারে। ইনক্রিমেন্টকে পারমাণবিক করার জন্য, আমাদের incr() "সিঙ্ক্রোনাইজড" ঘোষণা করতে হবে।

এটি এখনও ভাঙা, বিশেষ করে SMP-তে। এখনও একটি ডেটা রেস রয়েছে, সেখানে get() incr() এর সাথে একযোগে mValue অ্যাক্সেস করতে পারে। জাভা নিয়মের অধীনে, get() কলটি অন্য কোডের সাথে সাপেক্ষে পুনরায় সাজানো হতে পারে। উদাহরণস্বরূপ, যদি আমরা একটি সারিতে দুটি কাউন্টার পড়ি, ফলাফলগুলি অসামঞ্জস্যপূর্ণ বলে মনে হতে পারে কারণ আমরা হার্ডওয়্যার বা কম্পাইলার দ্বারা যে get() কলগুলি পুনরায় সাজিয়েছি। আমরা get() কে সিঙ্ক্রোনাইজ করার ঘোষণা দিয়ে সমস্যাটি সংশোধন করতে পারি। এই পরিবর্তনের সাথে, কোডটি স্পষ্টতই সঠিক।

দুর্ভাগ্যবশত, আমরা লক বিতর্কের সম্ভাবনা চালু করেছি, যা কার্যক্ষমতাকে বাধাগ্রস্ত করতে পারে। get() কে সিঙ্ক্রোনাইজ করার জন্য ঘোষণা করার পরিবর্তে, আমরা mValue “volatile” দিয়ে ঘোষণা করতে পারি। (নোট incr() এখনও অবশ্যই synchronize ব্যবহার করতে হবে যেহেতু mValue++ অন্যথায় একটি একক পারমাণবিক ক্রিয়াকলাপ নয়।) এটি সমস্ত ডেটা রেস এড়িয়ে যায়, তাই অনুক্রমিক সামঞ্জস্য রক্ষা করা হয়। incr() কিছুটা ধীর হবে, যেহেতু এটি মনিটর এন্ট্রি/এক্সিট ওভারহেড এবং অস্থির স্টোরের সাথে যুক্ত ওভারহেড উভয়ই বহন করে, তবে get() দ্রুত হবে, তাই বিতর্কের অনুপস্থিতিতেও এটি একটি জয় যদি সংখ্যার চেয়ে বেশি হয় লেখে (এছাড়াও সিঙ্ক্রোনাইজড ব্লক সম্পূর্ণরূপে অপসারণের উপায়ের জন্য AtomicInteger দেখুন।)

এখানে আরেকটি উদাহরণ, আগের C উদাহরণগুলির অনুরূপ:

class MyGoodies {
    public int x, y;
}
class MyClass {
    static MyGoodies sGoodies;
    void initGoodies() {    // runs in thread 1
        MyGoodies goods = new MyGoodies();
        goods.x = 5;
        goods.y = 10;
        sGoodies = goods;
    }
    void useGoodies() {    // runs in thread 2
        if (sGoodies != null) {
            int i = sGoodies.x;    // could be 5 or 0
            ....
        }
    }
}

এটির সি কোডের মতো একই সমস্যা রয়েছে, যেমন sGoodies এ একটি ডেটা রেস রয়েছে। এইভাবে অ্যাসাইনমেন্ট sGoodies = goods goods ক্ষেত্রে ক্ষেত্রগুলি শুরু করার আগে লক্ষ্য করা যেতে পারে। আপনি যদি volatile কীওয়ার্ডের সাথে sGoodies ঘোষণা করেন, অনুক্রমিক সামঞ্জস্য পুনরুদ্ধার করা হয় এবং জিনিসগুলি প্রত্যাশা অনুযায়ী কাজ করবে।

মনে রাখবেন যে শুধুমাত্র sGoodies রেফারেন্স নিজেই উদ্বায়ী। এর ভিতরের ক্ষেত্রগুলিতে প্রবেশাধিকার নেই। একবার sGoodies volatile হয়, এবং মেমরি অর্ডার সঠিকভাবে সংরক্ষিত হয়, ক্ষেত্রগুলি একযোগে অ্যাক্সেস করা যাবে না। বিবৃতি z = sGoodies.x MyClass.sGoodies এর একটি উদ্বায়ী লোড সম্পাদন করবে এবং তারপরে sGoodies.x এর একটি অ-উদ্বায়ী লোড হবে। আপনি যদি একটি স্থানীয় রেফারেন্স MyGoodies localGoods = sGoodies তৈরি করেন, তাহলে পরবর্তী z = localGoods.x কোনো উদ্বায়ী লোড সম্পাদন করবে না।

জাভা প্রোগ্রামিং-এ একটি আরও সাধারণ বাগধারা হল কুখ্যাত "ডাবল-চেকড লকিং":

class MyClass {
    private Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            synchronized (this) {
                if (helper == null) {
                    helper = new Helper();
                }
            }
        }
        return helper;
    }
}

ধারণাটি হল যে আমরা MyClass এর একটি উদাহরণের সাথে যুক্ত Helper অবজেক্টের একটি একক দৃষ্টান্ত রাখতে চাই। আমাদের এটি শুধুমাত্র একবার তৈরি করতে হবে, তাই আমরা একটি ডেডিকেটেড getHelper() ফাংশনের মাধ্যমে এটি তৈরি করে ফেরত দিই। একটি রেস এড়াতে যেখানে দুটি থ্রেড দৃষ্টান্ত তৈরি করে, আমাদের অবজেক্ট তৈরিকে সিঙ্ক্রোনাইজ করতে হবে। যাইহোক, আমরা প্রতিটি কলে "সিঙ্ক্রোনাইজড" ব্লকের জন্য ওভারহেড দিতে চাই না, তাই আমরা শুধুমাত্র সেই অংশটি করি যদি helper বর্তমানে শূন্য থাকে।

এটি helper ক্ষেত্রে একটি ডেটা রেস আছে. এটি অন্য থ্রেডে helper == null দিয়ে একযোগে সেট করা যেতে পারে।

এটি কীভাবে ব্যর্থ হতে পারে তা দেখতে, একই কোডটি সামান্য পুনঃলিখিত বিবেচনা করুন, যেন এটি একটি সি-জাতীয় ভাষাতে সংকলিত হয়েছে ( Helper's কনস্ট্রাক্টর কার্যকলাপকে উপস্থাপন করার জন্য আমি কয়েকটি পূর্ণসংখ্যা ক্ষেত্র যুক্ত করেছি):

if (helper == null) {
    synchronized() {
        if (helper == null) {
            newHelper = malloc(sizeof(Helper));
            newHelper->x = 5;
            newHelper->y = 10;
            helper = newHelper;
        }
    }
    return helper;
}

হার্ডওয়্যার বা কম্পাইলারকে x / y ফিল্ডে helper জন্য স্টোরকে পুনরায় সাজাতে বাধা দেওয়ার কিছু নেই। আরেকটি থ্রেড helper নন-নাল খুঁজে পেতে পারে কিন্তু এর ক্ষেত্রগুলি এখনও সেট করা হয়নি এবং ব্যবহারের জন্য প্রস্তুত। আরও বিশদ বিবরণ এবং আরও ব্যর্থতার মোডের জন্য, আরও বিশদ বিবরণের জন্য পরিশিষ্টে "'ডাবল চেকড লকিং ব্রোকেন' ঘোষণা" লিঙ্কটি দেখুন, বা জোশ ব্লোচের কার্যকরী জাভা, 2য় সংস্করণে আইটেম 71 ("অলস প্রাথমিককরণটি যুক্তিযুক্তভাবে ব্যবহার করুন") দেখুন৷ .

এটি ঠিক করার দুটি উপায় আছে:

  1. সহজ জিনিসটি করুন এবং বাইরের চেকটি মুছুন। এটি নিশ্চিত করে যে আমরা কখনই একটি সিঙ্ক্রোনাইজড ব্লকের বাইরে helper মান পরীক্ষা করি না।
  2. helper উদ্বায়ী ঘোষণা করুন। এই একটি ছোট পরিবর্তনের সাথে, উদাহরণ J-3-এর কোড জাভা 1.5 এবং পরবর্তীতে সঠিকভাবে কাজ করবে। (আপনি নিজেকে বোঝাতে এক মিনিট সময় নিতে চাইতে পারেন যে এটি সত্য।)

এখানে volatile আচরণের আরেকটি দৃষ্টান্ত রয়েছে:

class MyClass {
    int data1, data2;
    volatile int vol1, vol2;
    void setValues() {    // runs in Thread 1
        data1 = 1;
        vol1 = 2;
        data2 = 3;
    }
    void useValues() {    // runs in Thread 2
        if (vol1 == 2) {
            int l1 = data1;    // okay
            int l2 = data2;    // wrong
        }
    }
}

useValues() এর দিকে তাকিয়ে, যদি থ্রেড 2 এখনও vol1 এ আপডেট না দেখে থাকে, তাহলে data1 বা data2 এখনও সেট করা হয়েছে কিনা তা জানা যাবে না। একবার এটি vol1 এ আপডেটটি দেখে, এটি জানে যে data1 নিরাপদে অ্যাক্সেস করা যেতে পারে এবং ডেটা রেস প্রবর্তন না করেই সঠিকভাবে পড়া যায়। যাইহোক, এটি data2 সম্পর্কে কোন অনুমান করতে পারে না, কারণ সেই স্টোরটি উদ্বায়ী স্টোরের পরে সঞ্চালিত হয়েছিল।

মনে রাখবেন যে একে অপরের সাথে রেস করে এমন অন্যান্য মেমরি অ্যাক্সেসের পুনর্বিন্যাস রোধ করতে volatile ব্যবহার করা যাবে না। এটি একটি মেশিন মেমরি বেড়া নির্দেশ উত্পন্ন নিশ্চিত করা হয় না. এটি শুধুমাত্র কোড চালানোর মাধ্যমে ডেটা রেস প্রতিরোধ করতে ব্যবহার করা যেতে পারে যখন অন্য থ্রেড একটি নির্দিষ্ট শর্ত পূরণ করে।

কি করো

C/C++ এ, C++11 সিঙ্ক্রোনাইজেশন ক্লাস পছন্দ করুন, যেমন std::mutex । যদি না হয়, সংশ্লিষ্ট pthread অপারেশন ব্যবহার করুন. এর মধ্যে রয়েছে যথাযথ মেমরির বেড়া, সমস্ত অ্যান্ড্রয়েড প্ল্যাটফর্ম সংস্করণে সঠিক (অন্যথায় নির্দিষ্ট না থাকলে ধারাবাহিকভাবে ধারাবাহিক) এবং দক্ষ আচরণ প্রদান। তাদের সঠিকভাবে ব্যবহার করতে ভুলবেন না। উদাহরণস্বরূপ, মনে রাখবেন যে কন্ডিশন ভেরিয়েবল ওয়েটগুলি সিগন্যাল না করেই প্রত্যাবর্তন করতে পারে এবং এইভাবে একটি লুপে উপস্থিত হওয়া উচিত।

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

আপনি যদি পারমাণবিক ক্রিয়াকলাপগুলি ব্যবহার করেন, memory_order ... বা lazySet() এর সাথে শিথিলকরণ ক্রম কর্মক্ষমতা সুবিধা প্রদান করতে পারে, তবে আমরা এখন পর্যন্ত যা জানিয়েছি তার চেয়ে গভীর বোঝার প্রয়োজন। এইগুলি ব্যবহার করে বিদ্যমান কোডের একটি বড় ভগ্নাংশ সত্যের পরে বাগ রয়েছে বলে আবিষ্কৃত হয়েছে। সম্ভব হলে এগুলো এড়িয়ে চলুন। যদি আপনার ব্যবহারের ক্ষেত্রে পরের বিভাগে সেগুলির মধ্যে একটির সাথে ঠিক খাপ খায় না, তবে নিশ্চিত করুন যে আপনি হয় একজন বিশেষজ্ঞ, অথবা একজনের সাথে পরামর্শ করেছেন।

C/C++ এ থ্রেড যোগাযোগের জন্য volatile ব্যবহার এড়িয়ে চলুন।

জাভাতে, java.util.concurrent প্যাকেজ থেকে একটি উপযুক্ত ইউটিলিটি ক্লাস ব্যবহার করে প্রায়শই একযোগে সমস্যা সমাধান করা হয়। কোডটি ভাল লেখা এবং এসএমপিতে ভালভাবে পরীক্ষা করা হয়েছে।

সম্ভবত আপনি সবচেয়ে নিরাপদ জিনিসটি আপনার বস্তুকে অপরিবর্তনীয় করে তুলতে পারেন। জাভা'স স্ট্রিং এবং ইন্টিজারের মত ক্লাসের অবজেক্টে ডেটা ধারণ করা হয় যেগুলি একবার একটি অবজেক্ট তৈরি হয়ে গেলে পরিবর্তন করা যায় না, সেই অবজেক্টে ডেটা রেসের সমস্ত সম্ভাবনা এড়িয়ে যায়। ইফেক্টিভ জাভা বই, ২য় এড। "আইটেম 15: পরিবর্তনশীলতা হ্রাস করুন"-এ নির্দিষ্ট নির্দেশাবলী রয়েছে। জাভা ক্ষেত্রগুলিকে "চূড়ান্ত" ঘোষণা করার গুরুত্ব বিশেষভাবে নোট করুন ( Bloch )

এমনকি যদি একটি বস্তু অপরিবর্তনীয় হয়, মনে রাখবেন যে কোনো ধরনের সিঙ্ক্রোনাইজেশন ছাড়াই এটিকে অন্য থ্রেডে যোগাযোগ করা একটি ডেটা রেস। এটি মাঝে মাঝে জাভাতে গ্রহণযোগ্য হতে পারে (নীচে দেখুন), তবে খুব যত্নের প্রয়োজন, এবং এর ফলে ভঙ্গুর কোড হতে পারে। যদি এটি অত্যন্ত কর্মক্ষমতা সমালোচনামূলক না হয়, একটি volatile ঘোষণা যোগ করুন। C++ এ, সঠিক সিঙ্ক্রোনাইজেশন ছাড়াই একটি পয়েন্টার বা একটি অপরিবর্তনীয় বস্তুর রেফারেন্স যোগাযোগ করা, যেকোনো ডেটা রেসের মতো, একটি বাগ। এই ক্ষেত্রে, এটি যুক্তিসঙ্গতভাবে বিরতিহীন ক্র্যাশের ফলে হওয়ার সম্ভাবনা রয়েছে, উদাহরণস্বরূপ, স্টোর রি-অর্ডারিংয়ের কারণে রিসিভিং থ্রেড একটি অপ্রবর্তিত পদ্ধতি টেবিল পয়েন্টার দেখতে পারে।

যদি একটি বিদ্যমান লাইব্রেরি ক্লাস বা অপরিবর্তনীয় ক্লাস উপযুক্ত না হয়, তাহলে জাভা synchronized স্টেটমেন্ট বা C++ lock_guard / unique_lock ব্যবহার করা উচিত যেকোন ক্ষেত্রের অ্যাক্সেস রক্ষা করার জন্য যা একাধিক থ্রেড দ্বারা অ্যাক্সেস করা যেতে পারে। যদি মিউটেক্সগুলি আপনার পরিস্থিতির জন্য কাজ না করে, আপনার শেয়ার করা ক্ষেত্রগুলিকে volatile বা atomic ঘোষণা করা উচিত, তবে থ্রেডগুলির মধ্যে মিথস্ক্রিয়াগুলি বোঝার জন্য আপনাকে অবশ্যই খুব যত্ন নিতে হবে। এই ঘোষণাগুলি আপনাকে সাধারণ সমসাময়িক প্রোগ্রামিং ভুলগুলি থেকে রক্ষা করবে না, তবে তারা আপনাকে কম্পাইলার এবং এসএমপি দুর্ঘটনাগুলি অপ্টিমাইজ করার সাথে সম্পর্কিত রহস্যময় ব্যর্থতা এড়াতে সহায়তা করবে।

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

দুর্বল মেমরি আদেশ সম্পর্কে একটু বেশি

C++11 এবং পরবর্তীতে ডেটা-রেস-মুক্ত প্রোগ্রামগুলির জন্য অনুক্রমিক সামঞ্জস্যতা গ্যারান্টি শিথিল করার জন্য সুস্পষ্ট প্রক্রিয়া প্রদান করে। স্পষ্ট memory_order_relaxed , memory_order_acquire (শুধুমাত্র লোড হয়), এবং memory_order_release (শুধুমাত্র স্টোর) পারমাণবিক ক্রিয়াকলাপের জন্য আর্গুমেন্ট প্রতিটি ডিফল্টের তুলনায় কঠোরভাবে দুর্বল গ্যারান্টি প্রদান করে, সাধারণত অন্তর্নিহিত, memory_order_seq_cstmemory_order_acq_rel উভয়ই memory_order_acquire এবং memory_order_release গ্যারান্টি প্রদান করে পারমাণবিক রিড-মডিফাই রাইট অপারেশনের জন্য। memory_order_consume এখনও যথেষ্ট সুনির্দিষ্টভাবে সুনির্দিষ্ট বা কার্যকর করার জন্য প্রয়োগ করা হয়নি, এবং আপাতত উপেক্ষা করা উচিত।

Java.util.concurrent.atomic এর lazySet পদ্ধতিগুলি C++ memory_order_release স্টোরের অনুরূপ। জাভার সাধারণ ভেরিয়েবলগুলি কখনও কখনও memory_order_relaxed অ্যাক্সেসের প্রতিস্থাপন হিসাবে ব্যবহৃত হয়, যদিও তারা আসলে আরও দুর্বল। C++ এর বিপরীতে, ভেরিয়েবলের অবাস্তব অ্যাক্সেসের জন্য কোন বাস্তব ব্যবস্থা নেই যা volatile হিসাবে ঘোষণা করা হয়।

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

দুর্বল অর্ডার করা পারমাণবিকগুলির সম্পূর্ণ শব্দার্থবিজ্ঞান জটিল। সাধারণভাবে তাদের ভাষার নিয়মগুলি সম্পর্কে সুনির্দিষ্ট বোঝার প্রয়োজন হয়, যা আমরা এখানে প্রবেশ করব না। উদাহরণ স্বরূপ:

  • সংকলক বা হার্ডওয়্যার memory_order_relaxed অ্যাক্সেসগুলিকে একটি লক অধিগ্রহণ এবং প্রকাশের দ্বারা সীমাবদ্ধ একটি সমালোচনামূলক বিভাগে (তবে বাইরে নয়) স্থানান্তরিত করতে পারে। এর অর্থ হ'ল দুটি memory_order_relaxed স্টোরগুলি কোনও সমালোচনামূলক বিভাগ দ্বারা পৃথক করা হলেও ক্রমের বাইরে দৃশ্যমান হতে পারে।
  • একটি সাধারণ জাভা ভেরিয়েবল, যখন ভাগ করা কাউন্টার হিসাবে গালি দেওয়া হয়, তখন হ্রাস পেতে অন্য থ্রেডে উপস্থিত হতে পারে, যদিও এটি কেবল অন্য একটি থ্রেড দ্বারা বর্ধিত হয়। তবে এটি সি ++ পারমাণবিক memory_order_relaxed জন্য সত্য নয়।

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

অ-রেসিং অ্যাক্সেস

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

জাভাতে এটিতে কোনও আসল অ্যানালগ নেই।

ফলাফল সঠিকতার জন্য নির্ভর করা হয় না

যখন আমরা কেবল একটি ইঙ্গিত তৈরি করতে রেসিং লোড ব্যবহার করি, তখন সাধারণত লোডের জন্য কোনও মেমরি অর্ডার প্রয়োগ না করাও ঠিক। যদি মানটি নির্ভরযোগ্য না হয় তবে আমরা অন্যান্য ভেরিয়েবলগুলি সম্পর্কে কোনও কিছু অনুমান করতেও নির্ভরযোগ্যভাবে ফলাফলটি ব্যবহার করতে পারি না। সুতরাং এটি ঠিক আছে যদি মেমরি অর্ডারিংয়ের গ্যারান্টিযুক্ত না হয় এবং লোডটি একটি memory_order_relaxed আর্গুমেন্ট সরবরাহ করা হয়।

এর একটি সাধারণ উদাহরণ হ'ল সি ++ compare_exchange ব্যবহারটি f(x) দ্বারা x পরমাণুভাবে প্রতিস্থাপন করতে। f(x) গণনা করতে x এর প্রাথমিক লোডটি নির্ভরযোগ্য হওয়ার দরকার নেই। যদি আমরা এটি ভুল হয়ে যাই তবে compare_exchange ব্যর্থ হবে এবং আমরা আবার চেষ্টা করব। x এর প্রাথমিক লোডের পক্ষে memory_order_relaxed যুক্তি ব্যবহার করা ভাল; কেবলমাত্র প্রকৃত compare_exchange বিষয়গুলির জন্য মেমরি অর্ডারিং।

পরমাণুভাবে পরিবর্তিত তবে অপঠিত ডেটা

মাঝেমধ্যে ডেটা একাধিক থ্রেড দ্বারা সমান্তরালভাবে সংশোধন করা হয়, তবে সমান্তরাল গণনা সম্পূর্ণ না হওয়া পর্যন্ত পরীক্ষা করা হয় না। এর একটি ভাল উদাহরণ হ'ল একটি কাউন্টার যা পারমাণবিকভাবে বর্ধিত হয় (যেমন সি ++ তে fetch_add() ব্যবহার করে বা সি -তে atomic_fetch_add_explicit() ব্যবহার করে) সমান্তরালভাবে একাধিক থ্রেড দ্বারা, তবে এই কলগুলির ফলাফল সর্বদা উপেক্ষা করা হয়। সমস্ত আপডেট সম্পূর্ণ হওয়ার পরে ফলাফলের মানটি কেবল শেষে পড়া হয়।

এই ক্ষেত্রে, এই ডেটাতে অ্যাক্সেসগুলি পুনরায় অর্ডার করা হয়েছিল কিনা তা বলার কোনও উপায় নেই এবং তাই সি ++ কোডটি memory_order_relaxed আর্গুমেন্ট ব্যবহার করতে পারে।

সাধারণ ইভেন্ট কাউন্টারগুলি এর একটি সাধারণ উদাহরণ। যেহেতু এটি এত সাধারণ, তাই এই কেস সম্পর্কে কিছু পর্যবেক্ষণ করা উপযুক্ত:

  • memory_order_relaxed ব্যবহার পারফরম্যান্সকে উন্নত করে, তবে সবচেয়ে গুরুত্বপূর্ণ পারফরম্যান্স ইস্যুটিকে সম্বোধন করতে পারে না: প্রতিটি আপডেটের জন্য কাউন্টারটি ধারণ করে ক্যাশে লাইনে একচেটিয়া অ্যাক্সেসের প্রয়োজন। এটি একটি ক্যাশে মিস করে প্রতিবার যখন কোনও নতুন থ্রেড কাউন্টারটি অ্যাক্সেস করে। যদি আপডেটগুলি ঘন ঘন এবং থ্রেডগুলির মধ্যে বিকল্প হয় তবে প্রতিবার ভাগ করে নেওয়া কাউন্টারটি আপডেট করা এড়াতে আরও দ্রুততর হয়, উদাহরণস্বরূপ, থ্রেড-স্থানীয় কাউন্টারগুলি ব্যবহার করে এবং শেষে তাদের সংক্ষিপ্ত করা।
  • এই কৌশলটি পূর্ববর্তী বিভাগের সাথে সম্মিলিত: memory_order_relaxed ব্যবহার করে সমস্ত অপারেশন সহ আপডেট হওয়ার সময় একই সাথে আনুমানিক এবং অবিশ্বাস্য মানগুলি পড়া সম্ভব। তবে ফলস্বরূপ মানগুলিকে সম্পূর্ণ অবিশ্বাস্য হিসাবে বিবেচনা করা গুরুত্বপূর্ণ। গণনাটি একবারে বাড়ানো হয়েছে বলে মনে হচ্ছে তার অর্থ এই নয় যে অন্য থ্রেডটি এমন পর্যায়ে পৌঁছেছে যেখানে ইনক্রিমেন্ট সম্পাদন করা হয়েছে। পরিবর্তে ইনক্রিমেন্টটি পূর্ববর্তী কোড দিয়ে পুনরায় অর্ডার করা যেতে পারে। (অনুরূপ কেসটি আমরা আগে উল্লেখ করেছি, সি ++ গ্যারান্টি দেয় যে এই জাতীয় কাউন্টারটির দ্বিতীয় লোড একই থ্রেডের আগের লোডের চেয়ে কম মান ফেরত দেবে না। অবশ্যই কাউন্টারটি উপচে পড়া না হলে))
  • কোডটি সন্ধান করা সাধারণ যা স্বতন্ত্র পারমাণবিক (বা না) পড়া এবং লেখার মাধ্যমে আনুমানিক কাউন্টার মানগুলি গণনা করার চেষ্টা করে, তবে পুরো পারমাণবিক হিসাবে ইনক্রিমেন্ট তৈরি না করে। স্বাভাবিক যুক্তিটি হ'ল এটি পারফরম্যান্স কাউন্টার বা এর মতো "যথেষ্ট কাছাকাছি"। এটা সাধারণত না। যখন আপডেটগুলি পর্যাপ্ত পরিমাণে ঘন ঘন হয় (এমন একটি কেস যা আপনি সম্ভবত যত্নবান হন), তখন গণনার একটি বড় অংশ সাধারণত হারিয়ে যায়। একটি কোয়াড কোর ডিভাইসে, অর্ধেকেরও বেশি গণনা সাধারণত হারিয়ে যেতে পারে। (সহজ অনুশীলন: একটি দুটি থ্রেড দৃশ্য তৈরি করুন যাতে কাউন্টারটি মিলিয়ন বার আপডেট করা হয় তবে চূড়ান্ত পাল্টা মানটি এক))

সাধারণ পতাকা যোগাযোগ

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

উদাহরণস্বরূপ, আমরা সি ++ এ হিসাবে উপরে থেকে ডাবল-চেক করা লকিং উদাহরণটি আবার লিখতে পারি

class MyClass {
  private:
    atomic<Helper*> helper {nullptr};
    mutex mtx;
  public:
    Helper* getHelper() {
      Helper* myHelper = helper.load(memory_order_acquire);
      if (myHelper == nullptr) {
        lock_guard<mutex> lg(mtx);
        myHelper = helper.load(memory_order_relaxed);
        if (myHelper == nullptr) {
          myHelper = new Helper();
          helper.store(myHelper, memory_order_release);
        }
      }
      return myHelper;
    }
};

লোড এবং রিলিজ স্টোর অর্জন নিশ্চিত করে যে আমরা যদি কোনও নন-নাল helper দেখি তবে আমরা এর ক্ষেত্রগুলিও সঠিকভাবে শুরু করতে দেখব। আমরা পূর্বের পর্যবেক্ষণকেও অন্তর্ভুক্ত করেছি যে নন-রেসিং লোডগুলি memory_order_relaxed ব্যবহার করতে পারে।

একজন জাভা প্রোগ্রামার java.util.concurrent.atomic.AtomicReference<Helper> হিসাবে helper প্রতিনিধিত্ব করতে পারে এবং রিলিজ স্টোর হিসাবে lazySet() ব্যবহার করে। লোড অপারেশনগুলি প্লেইন get() কলগুলি ব্যবহার করতে থাকবে।

উভয় ক্ষেত্রেই, আমাদের পারফরম্যান্স টুইটটি ইনিশিয়ালাইজেশন পাথের দিকে মনোনিবেশ করেছে, যা পারফরম্যান্স সমালোচনামূলক হওয়ার সম্ভাবনা কম। আরও পঠনযোগ্য আপস হতে পারে:

    Helper* getHelper() {
      Helper* myHelper = helper.load(memory_order_acquire);
      if (myHelper != nullptr) {
        return myHelper;
      }
      lock_guard&ltmutex> lg(mtx);
      if (helper == nullptr) {
        helper = new Helper();
      }
      return helper;
    }

এটি একই দ্রুত পথ সরবরাহ করে, তবে অ-পারফরম্যান্স-সমালোচনামূলক ধীর পথের উপর ডিফল্ট, ক্রমানুসারে সামঞ্জস্যপূর্ণ, অপারেশনগুলিতে রিসর্ট করে।

এমনকি এখানে, helper.load(memory_order_acquire) helper সত্যিই এখানে সবচেয়ে উপকারী অপ্টিমাইজেশন হ'ল দ্বিতীয় লোড অপসারণের জন্য myHelper পরিচিতি হতে পারে, যদিও ভবিষ্যতের সংকলক এটি স্বয়ংক্রিয়ভাবে এটি করতে পারে।

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

অপরিবর্তনীয় ক্ষেত্র

যদি কোনও অবজেক্ট ক্ষেত্রটি প্রথমে ব্যবহারে শুরু করা হয় এবং তারপরে কখনই পরিবর্তন করা হয় না, তবে এটি শুরু করা সম্ভব হতে পারে এবং পরবর্তীকালে এটি দুর্বলভাবে আদেশযুক্ত অ্যাক্সেসগুলি ব্যবহার করে পড়া সম্ভব। সি ++ তে, এটি atomic হিসাবে ঘোষণা করা যেতে পারে এবং memory_order_relaxed বা জাভাতে ব্যবহার করে অ্যাক্সেস করা যেতে পারে, এটি volatile ছাড়াই ঘোষণা করা যেতে পারে এবং বিশেষ ব্যবস্থা ছাড়াই অ্যাক্সেস করা যেতে পারে। এর জন্য নিম্নলিখিত সমস্ত হোল্ড প্রয়োজন:

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

ক্লোজিং নোট

যদিও এই দস্তাবেজটি কেবল পৃষ্ঠটি স্ক্র্যাচ করার চেয়ে আরও বেশি কিছু করে তবে এটি অগভীর গেজের চেয়ে বেশি পরিচালনা করে না। এটি একটি খুব বিস্তৃত এবং গভীর বিষয়। আরও অনুসন্ধানের জন্য কিছু অঞ্চল:

  • প্রকৃত জাভা এবং সি ++ মেমরি মডেলগুলি ঘটনার আগে সম্পর্কের ক্ষেত্রে প্রকাশ করা হয় যা নির্দিষ্ট ক্রমে দুটি ক্রিয়া হওয়ার গ্যারান্টিযুক্ত হলে নির্দিষ্ট করে। যখন আমরা কোনও ডেটা রেস সংজ্ঞায়িত করি, আমরা "একই সাথে" ঘটে যাওয়া দুটি মেমরি অ্যাক্সেস সম্পর্কে অনানুষ্ঠানিকভাবে কথা বললাম। সরকারীভাবে এটি অন্যের আগে ঘটছে না হিসাবে সংজ্ঞায়িত করা হয়। জাভা বা সি ++ মেমরি মডেলের সাথে ঘটনার আগে এবং সিঙ্ক্রোনাইজগুলির প্রকৃত সংজ্ঞাগুলি শিখতে শিক্ষণীয়। যদিও "একই সাথে" এর স্বজ্ঞাত ধারণাটি সাধারণত যথেষ্ট ভাল, তবে এই সংজ্ঞাগুলি শিক্ষণীয়, বিশেষত যদি আপনি সি ++ তে দুর্বল অর্ডারযুক্ত পারমাণবিক ক্রিয়াকলাপ ব্যবহার করে চিন্তাভাবনা করছেন। (বর্তমান জাভা স্পেসিফিকেশনটি কেবল lazySet() খুব অনানুষ্ঠানিকভাবে সংজ্ঞায়িত করে))
  • কোড পুনরায় অর্ডার করার সময় সংকলকগুলি কী এবং কী করার অনুমতি নেই তা অন্বেষণ করুন। (জেএসআর -133 স্পেকের আইনী রূপান্তরগুলির কয়েকটি দুর্দান্ত উদাহরণ রয়েছে যা অপ্রত্যাশিত ফলাফলের দিকে নিয়ে যায়))
  • জাভা এবং সি ++ তে কীভাবে অপরিবর্তনীয় ক্লাস লিখতে হয় তা সন্ধান করুন। (এর চেয়ে আরও অনেক কিছুই আছে কেবল "নির্মাণের পরে কিছু পরিবর্তন করবেন না"))
  • কার্যকর জাভা, দ্বিতীয় সংস্করণটির সম্মতি বিভাগে সুপারিশগুলিকে অভ্যন্তরীণ করুন। (উদাহরণস্বরূপ, আপনার কলিং পদ্ধতিগুলি এড়ানো উচিত যা সিঙ্ক্রোনাইজড ব্লকের ভিতরে থাকা অবস্থায় ওভাররাইড করা বোঝানো হয়))
  • java.util.concurrent এবং java.util.concurrent.atomic এপিআইগুলির মাধ্যমে কী পাওয়া যায় তা দেখতে পড়ুন। @ThreadSafe এবং @GuardedBy (নেট.জেসিপ.অ্যানোটেশনস থেকে) এর মতো সম্মতি টীকাগুলি ব্যবহার করার বিষয়টি বিবেচনা করুন।

পরিশিষ্টের আরও পড়ার বিভাগে নথি এবং ওয়েব সাইটগুলির লিঙ্ক রয়েছে যা এই বিষয়গুলি আরও ভালভাবে আলোকিত করবে।

পরিশিষ্ট

সিঙ্ক্রোনাইজেশন স্টোরগুলি বাস্তবায়ন করা

(এটি এমন কিছু নয় যা বেশিরভাগ প্রোগ্রামাররা নিজেকে বাস্তবায়ন করতে দেখবে, তবে আলোচনাটি আলোকিত করছে))

int , এবং অ্যান্ড্রয়েড দ্বারা সমর্থিত হার্ডওয়্যারগুলির মতো ছোট অন্তর্নির্মিত প্রকারের জন্য, সাধারণ লোড এবং স্টোর নির্দেশাবলী নিশ্চিত করে যে কোনও স্টোরটি সম্পূর্ণরূপে দৃশ্যমান করা হবে, বা মোটেও নয়, অন্য কোনও প্রসেসরের কাছে একই অবস্থান লোড করছে। সুতরাং "পারমাণবিকতা" এর কিছু প্রাথমিক ধারণা বিনামূল্যে সরবরাহ করা হয়।

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

মেমরি অপারেশনগুলির ক্রম উভয়ই সংকলক দ্বারা পুনঃব্যবহার প্রতিরোধ এবং হার্ডওয়্যার দ্বারা পুনরায় অর্ডার করা প্রতিরোধ দ্বারা সংরক্ষণ করা হয়। এখানে আমরা পরবর্তীতে ফোকাস করি।

এআরএমভি 7, x86, এবং এমআইপিগুলিতে মেমরি অর্ডারিং "বেড়া" নির্দেশাবলী দিয়ে প্রয়োগ করা হয় যা বেড়ার পূর্ববর্তী নির্দেশাবলীর আগে বেড়া অনুসরণ করে নির্দেশাবলী দৃশ্যমান হতে বাধা দেয়। (এগুলিকে সাধারণত "বাধা" নির্দেশাবলীও বলা হয়, তবে এটি pthread_barrier -স্টাইল বাধাগুলির সাথে বিভ্রান্তির ঝুঁকি নিয়ে থাকে, যা এর চেয়ে অনেক বেশি করে)) বেড়া নির্দেশাবলীর সুনির্দিষ্ট অর্থটি মোটামুটি জটিল বিষয় যা প্রদত্ত গ্যারান্টিগুলিকে সম্বোধন করতে হয় একাধিক বিভিন্ন ধরণের বেড়া দ্বারা ইন্টারঅ্যাক্ট করে এবং কীভাবে এগুলি অন্যান্য অর্ডারিং গ্যারান্টিগুলির সাথে সাধারণত হার্ডওয়্যার দ্বারা সরবরাহ করা হয়। এটি একটি উচ্চ স্তরের ওভারভিউ, তাই আমরা এই বিশদগুলি নিয়ে চকচকে করব।

অর্ডারিং গ্যারান্টির সর্বাধিক প্রাথমিক ধরণের হ'ল সি ++ memory_order_acquire এবং memory_order_release পারমাণবিক ক্রিয়াকলাপ দ্বারা সরবরাহ করা: একটি রিলিজ স্টোরের পূর্ববর্তী মেমরি অপারেশনগুলি একটি অর্জনের লোডের পরে দৃশ্যমান হওয়া উচিত। এআরএমভি 7 এ, এটি দ্বারা প্রয়োগ করা হয়:

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

একসাথে সি ++ অর্জন/প্রকাশের ক্রমের জন্য এই যথেষ্ট। জাভা volatile বা সি ++ ক্রমগতভাবে সামঞ্জস্যপূর্ণ atomic জন্য এগুলি প্রয়োজনীয়, তবে পর্যাপ্ত নয়।

আমাদের আর কী দরকার তা দেখার জন্য, ডেকারের অ্যালগরিদমের খণ্ডটি আমরা সংক্ষেপে আগে উল্লেখ করেছি। flag1 এবং flag2 হ'ল সি ++ atomic বা জাভা volatile ভেরিয়েবল, উভয়ই প্রাথমিকভাবে মিথ্যা।

থ্রেড 1 থ্রেড 2
flag1 = true
if (flag2 == false)
critical-stuff
flag2 = true
if (flag1 == false)
critical-stuff

ক্রমিক ধারাবাহিকতা বোঝায় যে flag এন -এর একটি অ্যাসাইনমেন্ট অবশ্যই প্রথমে কার্যকর করা উচিত, এবং অন্য থ্রেডে পরীক্ষার মাধ্যমে দেখা উচিত। সুতরাং, আমরা এই থ্রেডগুলি একসাথে "সমালোচনামূলক-স্টাফ" সম্পাদন করতে দেখব না।

তবে অর্জনের জন্য প্রয়োজনীয় বেড়াটি কেবল প্রতিটি থ্রেডের শুরু এবং শেষে বেড়া যুক্ত করে, যা এখানে সহায়তা করে না। আমাদের অতিরিক্তভাবে নিশ্চিত করতে হবে যে যদি কোনও volatile / atomic স্টোরকে একটি volatile / atomic লোড অনুসরণ করা হয় তবে দুটিটি পুনরায় অর্ডার করা হয় না। এটি সাধারণত ধারাবাহিকভাবে ধারাবাহিক স্টোরের আগে নয়, এটির পরেও একটি বেড়া যুক্ত করে প্রয়োগ করা হয়। (এটি আবার প্রয়োজনের চেয়ে অনেক বেশি শক্তিশালী, যেহেতু এই বেড়াটি সাধারণত সমস্ত পূর্ববর্তী মেমরিটি পরবর্তী সমস্তগুলির সাথে সম্মতি দিয়ে অ্যাক্সেস করে))

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

যেমনটি আমরা আগের বিভাগে দেখেছি, আমাদের দুটি অপারেশনের মধ্যে একটি স্টোর/লোড বাধা সন্নিবেশ করতে হবে। অস্থির অ্যাক্সেসের জন্য ভিএম -তে কার্যকর করা কোডটি এর মতো কিছু দেখাবে:

উদ্বায়ী লোড অস্থির দোকান
reg = A
fence for "acquire" (1)
fence for "release" (2)
A = reg
fence for later atomic load (3)

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

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

এআরএমভি 8 এলডিএআর এবং এসটিএলআর নির্দেশাবলী সরবরাহ করে যা জাভা উদ্বায়ী বা সি ++ এর প্রয়োজনীয়তাগুলি সরাসরি প্রয়োগ করে ধারাবাহিকভাবে ধারাবাহিক লোড এবং স্টোরগুলির প্রয়োজনীয়তা প্রয়োগ করে। এগুলি আমরা উপরে উল্লিখিত অপ্রয়োজনীয় পুনর্নির্মাণের সীমাবদ্ধতাগুলি এড়িয়ে চলি। বাহুতে 64-বিট অ্যান্ড্রয়েড কোড এগুলি ব্যবহার করে; আমরা এখানে এআরএমভি 7 বেড়া প্লেসমেন্টে ফোকাস করা বেছে নিয়েছি কারণ এটি প্রকৃত প্রয়োজনীয়তার উপর আরও আলোকপাত করে।

আরও পড়া

ওয়েব পৃষ্ঠা এবং নথি যা আরও গভীরতা বা প্রস্থ সরবরাহ করে। আরও সাধারণভাবে দরকারী নিবন্ধগুলি তালিকার শীর্ষের কাছাকাছি।

ভাগ করা মেমরি ধারাবাহিকতা মডেল: একটি টিউটোরিয়াল
অ্যাডভ এবং ঘারাচোরলু 1995 সালে লিখিত, আপনি যদি মেমরির ধারাবাহিকতা মডেলগুলিতে আরও গভীরভাবে ডুব দিতে চান তবে এটি শুরু করার জন্য এটি একটি ভাল জায়গা।
http://www.hpl.hp.com/techreports/compaq-dec/wrl-95-7.pdf
স্মৃতি বাধা
সমস্যাগুলির সংক্ষিপ্তসারটি দুর্দান্ত ছোট্ট নিবন্ধ।
https://en.wikedia.org/wiki/memory_barrier
থ্রেড মৌলিক
সি ++ এবং জাভাতে মাল্টি-থ্রেডড প্রোগ্রামিংয়ের একটি ভূমিকা, হান্স বোহেম দ্বারা। ডেটা রেস এবং বেসিক সিঙ্ক্রোনাইজেশন পদ্ধতিগুলির আলোচনা।
http://www.hboehm.info/c++mm/threadsintro.html
অনুশীলনে জাভা সম্মতি
2006 সালে প্রকাশিত, এই বইটি দুর্দান্ত বিস্তারিতভাবে বিস্তৃত বিষয়গুলিকে কভার করে। জাভাতে মাল্টি-থ্রেডেড কোড লেখার জন্য উচ্চ প্রস্তাবিত।
http://www.javaconcurrencyinpractice.com
জেএসআর -133 (জাভা মেমরি মডেল) FAQ
সিঙ্ক্রোনাইজেশন, অস্থির ভেরিয়েবল এবং চূড়ান্ত ক্ষেত্রগুলি নির্মাণের ব্যাখ্যা সহ জাভা মেমরি মডেলের একটি মৃদু পরিচয়। (কিছুটা তারিখযুক্ত, বিশেষত যখন এটি অন্যান্য ভাষা নিয়ে আলোচনা করে))
http://www.cs.umd.edu/~pugh/java/memorymodel/jsr-133-faq.html
জাভা মেমরি মডেলটিতে প্রোগ্রামের রূপান্তরগুলির বৈধতা
জাভা মেমরি মডেলটির সাথে বাকী সমস্যাগুলির একটি বরং প্রযুক্তিগত ব্যাখ্যা। এই সমস্যাগুলি ডেটা-রেস-মুক্ত প্রোগ্রামগুলিতে প্রযোজ্য নয়।
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.112.1790&rep=rep1&type=pdf
প্যাকেজ java.util.concurrent এর ওভারভিউ
java.util.concurrent প্যাকেজের জন্য ডকুমেন্টেশন। পৃষ্ঠার নীচের অংশে "মেমরি ধারাবাহিকতা বৈশিষ্ট্য" শিরোনামের একটি বিভাগ রয়েছে যা বিভিন্ন শ্রেণীর দ্বারা তৈরি গ্যারান্টিগুলি ব্যাখ্যা করে।
java.util.concurrent প্যাকেজ সংক্ষিপ্তসার
জাভা তত্ত্ব এবং অনুশীলন: জাভাতে নিরাপদ নির্মাণ কৌশল
এই নিবন্ধটি অবজেক্ট নির্মাণের সময় পালিয়ে যাওয়া রেফারেন্সগুলির বিপদগুলি বিশদভাবে পরীক্ষা করে এবং থ্রেড-নিরাপদ নির্মাতাদের জন্য গাইডলাইন সরবরাহ করে।
http://www.ibm.com/developerworks/java/library/j-jtp0618.html
জাভা তত্ত্ব এবং অনুশীলন: অস্থিরতা পরিচালনা করা
জাভাতে অস্থির ক্ষেত্রগুলি দিয়ে আপনি কী করতে পারেন এবং কী অর্জন করতে পারবেন না তা বর্ণনা করে একটি দুর্দান্ত নিবন্ধ।
http://www.ibm.com/developerworks/java/library/j-jtp06197.html
"ডাবল-চেকড লকিংটি ভেঙে গেছে" ঘোষণা
বিল পুগের বিভিন্ন উপায়ে ডাবল-চেক করা লকিং volatile বা atomic ছাড়াই ভেঙে গেছে তার বিশদ ব্যাখ্যা। সি/সি ++ এবং জাভা অন্তর্ভুক্ত।
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
[এআরএম] বাধা লিটমাস পরীক্ষা এবং কুকবুক
আর্ম এসএমপি ইস্যুগুলির একটি আলোচনা, এআরএম কোডের সংক্ষিপ্ত স্নিপেটগুলি দিয়ে আলোকিত। আপনি যদি এই পৃষ্ঠায় উদাহরণগুলি খুব অ-নির্দিষ্ট খুঁজে পান, বা ডিএমবি নির্দেশের আনুষ্ঠানিক বিবরণটি পড়তে চান তবে এটি পড়ুন। এক্সিকিউটেবল কোডে মেমরি বাধাগুলির জন্য ব্যবহৃত নির্দেশাবলীও বর্ণনা করে (আপনি যদি ফ্লাইতে কোড তৈরি করেন তবে সম্ভবত কার্যকর)। নোট করুন যে এটি এআরএমভি 8 এর পূর্বাভাস দেয়, যা অতিরিক্ত মেমরি অর্ডারিং নির্দেশাবলী সমর্থন করে এবং কিছুটা শক্তিশালী মেমরি মডেলটিতে স্থানান্তরিত করে। (বিশদগুলির জন্য "আর্মি আর্কিটেকচার রেফারেন্স ম্যানুয়াল আর্মভি 8, এআরএমভি 8-এ আর্কিটেকচার প্রোফাইলের জন্য" দেখুন))
http://infocenter.arm.com/help/topic/com.arm.doc.genc007826/barrier_litmus_tests_and_cookbook_a08.pdf
লিনাক্স কার্নেল মেমরি বাধা
লিনাক্স কার্নেল মেমরি বাধা জন্য ডকুমেন্টেশন। কিছু দরকারী উদাহরণ এবং ASCII শিল্প অন্তর্ভুক্ত।
http://www.kernel.org/doc/docamentation/memory-rrieriers.txt
আইএসও/আইইসি জেটিসি 1 এসসি 22 ডাব্লুজি 21 (সি ++ স্ট্যান্ডার্ড) 14882 (সি ++ প্রোগ্রামিং ল্যাঙ্গুয়েজ), বিভাগ 1.10 এবং ধারা 29 ("পারমাণবিক অপারেশন লাইব্রেরি")
সি ++ পারমাণবিক অপারেশন বৈশিষ্ট্যগুলির জন্য খসড়া স্ট্যান্ডার্ড। এই সংস্করণটি সি ++ 14 স্ট্যান্ডার্ডের কাছাকাছি, যা সি ++ 11 থেকে এই অঞ্চলে সামান্য পরিবর্তন অন্তর্ভুক্ত করে।
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4527.pdf
(পরিচয়: http://www.hpl.hp.com/techreports/2008/hpl-2008-56.pdf )
আইএসও/আইইসি জেটিসি 1 এসসি 22 ডাব্লুজি 14 (সি স্ট্যান্ডার্ডস) 9899 (সি প্রোগ্রামিং ল্যাঙ্গুয়েজ) অধ্যায় 7.16 ("পরমাণু <স্টাড্যাটমিক.এইচ>"))
আইএসও/আইইসি 9899-201x সি পারমাণবিক অপারেশন বৈশিষ্ট্যগুলির জন্য খসড়া স্ট্যান্ডার্ড। বিশদের জন্য, পরে ত্রুটিযুক্ত প্রতিবেদনগুলিও পরীক্ষা করুন।
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
সি/সি ++ 11 প্রসেসরগুলিতে ম্যাপিংস (কেমব্রিজ বিশ্ববিদ্যালয়)
জারোস্লাভ সেভিক এবং পিটার সিওয়েলের বিভিন্ন সাধারণ প্রসেসর নির্দেশিকা সেটগুলিতে সি ++ পারমাণবিক অনুবাদগুলির সংগ্রহ সংগ্রহ।
http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html
ডেকারের অ্যালগরিদম
"একযোগে প্রোগ্রামিংয়ে পারস্পরিক বর্জন সমস্যার প্রথম পরিচিত সঠিক সমাধান"। উইকিপিডিয়া নিবন্ধে পুরো অ্যালগরিদম রয়েছে, এটি আধুনিক অপ্টিমাইজিং সংকলক এবং এসএমপি হার্ডওয়্যার দিয়ে কীভাবে কাজ করার জন্য এটি আপডেট করা দরকার তা নিয়ে আলোচনা করে।
https://en.wikedia.org/wiki/dekker's_algorithm
বাহু বনাম আলফা এবং ঠিকানা নির্ভরতা সম্পর্কে মন্তব্য
কাতালিন মেরিনাস থেকে আর্ম-কার্নেল মেলিং তালিকার একটি ইমেল। ঠিকানা এবং নিয়ন্ত্রণ নির্ভরতাগুলির একটি দুর্দান্ত সংক্ষিপ্তসার অন্তর্ভুক্ত।
http://linux.derkeiler.com/mailing-lists/kernel/2009-05/msg11811.html
প্রতিটি প্রোগ্রামার স্মৃতি সম্পর্কে কী জানা উচিত
উলরিচ ড্রেপারের বিভিন্ন ধরণের মেমরি, বিশেষত সিপিইউ ক্যাশে সম্পর্কে একটি খুব দীর্ঘ এবং বিস্তারিত নিবন্ধ।
http://www.akkadia.org/dreper/cpumemory.pdf
বাহু সম্পর্কে যুক্তিযুক্ত সামঞ্জস্যপূর্ণ মেমরি মডেল
এই কাগজটি আর্ম, লিমিটেডের চং অ্যান্ড ইশতিয়াক লিখেছেন এটি একটি কঠোর তবে অ্যাক্সেসযোগ্য ফ্যাশনে আর্ম এসএমপি মেমরি মডেলটি বর্ণনা করার চেষ্টা করে। এখানে ব্যবহৃত "পর্যবেক্ষণ" সংজ্ঞাটি এই কাগজ থেকে আসে। আবার, এটি এআরএমভি 8 এর পূর্বাভাস দেয়।
http://portal.acm.org/ft_gateway.cfm?id=1353528&type=pdf&coll=&dl=&cfid=96099715&cftokend=57505711
সংকলক লেখকদের জন্য জেএসআর -133 কুকবুক
ডগ লিয়া এটি জেএসআর -133 (জাভা মেমরি মডেল) ডকুমেন্টেশনের সহযোগী হিসাবে লিখেছিলেন। এটিতে জাভা মেমরি মডেলের জন্য বাস্তবায়ন নির্দেশিকাগুলির প্রাথমিক সেট রয়েছে যা অনেক সংকলক লেখক দ্বারা ব্যবহৃত হয়েছিল এবং এখনও এটি ব্যাপকভাবে উদ্ধৃত এবং অন্তর্দৃষ্টি দেওয়ার সম্ভাবনা রয়েছে। দুর্ভাগ্যক্রমে, এখানে আলোচিত চারটি বেড়া জাতগুলি অ্যান্ড্রয়েড-সমর্থিত আর্কিটেকচারের জন্য ভাল মিল নয় এবং উপরের সি ++ 11 ম্যাপিংগুলি এখন জাভার জন্যও সুনির্দিষ্ট রেসিপিগুলির আরও ভাল উত্স।
http://g.oswego.edu/dl/jmm/cookbook.html
x86-TSO: x86 মাল্টিপ্রসেসরগুলির জন্য একটি কঠোর এবং ব্যবহারযোগ্য প্রোগ্রামারের মডেল
X86 মেমরি মডেলের একটি সুনির্দিষ্ট বিবরণ। এআরএম মেমরি মডেলের সুনির্দিষ্ট বিবরণ দুর্ভাগ্যক্রমে উল্লেখযোগ্যভাবে আরও জটিল।
http://www.cl.cam.ac.uk/~pes20/weakmemory/cacm.pdf