iOS-এ কনকারেন্সি একটি ব্যাপক টপিক। তাই এই আর্টিকেলে আমি কিউ এবং Grand Central Dispatch (GCD) ফ্রেমওয়ার্ক সম্পর্কে একটি সাব-টপিকে ফোকাস করতে চাই।
বিশেষভাবে, আমি সিরিয়াল এবং কনকারেন্ট কিউ-এর মধ্যে পার্থক্য, সেইসাথে সিনক্রোনাস এবং অ্যাসিনক্রোনাস এক্সিকিউশনের মধ্যে পার্থক্য অন্বেষণ করতে চাই।
আপনি যদি কখনো GCD ব্যবহার না করে থাকেন, তবে এই আর্টিকেল শুরু করার জন্য একটি দুর্দান্ত জায়গা। আপনার যদি GCD-এ কিছু অভিজ্ঞতা থাকে, কিন্তু উপরে উল্লিখিত টপিকগুলো সম্পর্কে কৌতুহলী হন, তবে আমি মনে করি এটি আপনার জন্য উপকারী হবে। এবং আশা করি আপনি এক বা দুটি নতুন জিনিস শিখবেন।
এই আর্টিকেলের কনসেপ্টগুলো ভিজুয়ালি ডেমো করার জন্য আমি একটি SwiftUI কম্প্যানিয়ন অ্যাপ তৈরি করেছি। অ্যাপটিতে একটি ছোট কুইজও আছে যা আমি আপনাকে এই আর্টিকেল পড়ার আগে এবং পরে ট্রাই করার পরামর্শ দিচ্ছি। সোর্স কোড এখানে ডাউনলোড করুন, বা পাবলিক বিটা এখানে পান।
আমি GCD-র একটি সংক্ষিপ্ত পরিচয় দিয়ে শুরু করব, তারপর সিন্ক, অ্যাসিন্ক, সিরিয়াল এবং কনকারেন্ট সম্পর্কে বিস্তারিত ব্যাখ্যা দেব। তারপর, কনকারেন্সির সাথে কাজ করার সময় কিছু পিটফল কভার করব। চূড়ান্তভাবে, একটি সারসংক্ষেপ এবং কিছু সাধারণ পরামর্শ দিয়ে সমাপন করব।
পরিচয়
GCD এবং ডিসপ্যাচ কিউ-র একটি সংক্ষিপ্ত পরিচয় দিয়ে শুরু করা যাক। আপনি যদি ইতিমধ্যে টপিকটির সাথে পরিচিত হন, তবে নিঃসঙ্কোচে Sync vs Async সেকশনে যেতে পারেন।
কনকারেন্সি এবং Grand Central Dispatch
কনকারেন্সি আপনাকে আপনার ডিভাইসে একাধিক CPU কোর থাকার সুবিধা নেওয়ার অনুমতি দেয়। এই কোরগুলো ব্যবহার করতে হলে, আপনাকে একাধিক থ্রেড ব্যবহার করতে হবে। যਦြাপি, থ্রেড একটি লো-লেভেল টুল, এবং থ্রেডগুলো ম্যানুয়ালি একটি দক্ষভাবে ম্যানেজ করা অত্যন্ত কঠিন।
Grand Central Dispatch ১০ বছরের বেশি আগে Apple দ্বারা তৈরি হয়েছিল একটি অ্যাবস্ট্রাকশন হিসেবে যাতে ডেভেলপাররা থ্রেড নিজেরা তৈরি ও ম্যানেজ না করেই মাল্টি-থ্রেডেড কোড লিখতে পারেন।
GCD-তে, Apple একটি অ্যাসিনক্রোনাস ডিজাইন অ্যাপ্রোচ নিয়েছে সমস্যা সমাধানের জন্য। সরাসরি থ্রেড তৈরি করার বদলে, আপনি GCD ব্যবহার করেন ওয়ার্ক টাস্ক শিডিউল করার জন্য, এবং সিস্টেমটি তার রিসোর্সগুলো সর্বোত্তমভাবে ব্যবহার করে এই টাস্কগুলো পারফর্ম করে। GCD আবশ্যক থ্রেডগুলো তৈরি করে এবং আপনার টাস্কগুলো সেগুলোতে শিডিউল করে, থ্রেড ম্যানেজমেন্টের ভার ডেভেলপার থেকে সিস্টেমে শিফ্ট করে দেয়।
GCD-এর একটি বড় সুবিধা হলো কনকারেন্ট কোড লিখার সময় আপনাকে হার্ডওয়্যার রিসোর্স নিয়ে চিন্তা করতে হয় না। GCD আপনার জন্য একটি থ্রেড পুল ম্যানেজ করে, এবং এটি সিঙ্গেল-কোর Apple Watch থেকে শুরু করে ম্যানি-কোর MacBook Pro-পর्यন্ট স্কেল করতে পারে।
ডিসপ্যাচ কিউ
এই GCD-এর প্রধান বিল্ডিং ব্লক যা আপনাকে আপনার ডিফাইন করা প্যারামিটার সেট ব্যবহার করে আর্বিট্রারি কোড ব্লক এক্সিকিউট করতে দেয়। ডিসপ্যাচ কিউ-র টাস্কগুলো সবসময় ফার্স্ট-ইন, ফার্স্ট-আউট (FIFO) ফ্যাশনে শুরু হয়। দ্রষ্টব্য, আমি শুরু বললাম, কারণ আপনার টাস্কগুলোর কমপ্লিশন টাইম অনেক ফ্যাক্টরের ওপর ডিপেন্ড করে, এবং এটি FIFO-তে কমপ্লিট হওয়ার গ্যারান্টি নেই (আরও後ে আলোচনা হবে)।
বড় দৃষ্টিতে, আপনার উপলব্ধ তিনটি ধরনের কিউ রয়েছে:
- মেইন ডিসপ্যাচ কিউ (সিরিয়াল, প্রি-ডিফাইনড)
- গ্লোবাল কিউ (কনকারেন্ট, প্রি-ডিফাইনড)
- প্রাইভেট কিউ (সিরিয়াল বা কনকারেন্ট হতে পারে, আপনি তৈরি করবেন)
প্রতিটি অ্যাপের সাথে একটি মেইন কিউ থাকে, যা একটি সিরিয়াল কিউ যা মেইন থ্রেডে টাস্ক এক্সিকিউট করে। এই কিউ আপনার অ্যাপ্লিকেশনের UI ড্র এবং ইউজার ইন্টারঅ্যাকশন (টাচ, স্ক্রোল, প্যান, ইত্যাদি) রেসপন্স করার জন্য দায়িত্ববাহন করে। আপনি যদি এই কিউকে অনেকক্ষণ ব্লক করে রাখেন, তবে আপনার iOS অ্যাপ ফ্রিজড মনে হবে, এবং macOS-এ বিখ্যাত বিচ বল/স্পিনিং হুইল দেখাবে।
দীর্ঘদিন চলমান টাস্ক (নেটওয়ার্ক কল, কম্পিউটেশনালি ইনটেনসিভ ওয়ার্ক, ইত্যাদি) পারফর্ম করার সময়, আমরা ব্যাকগ্রাউন্ড কিউতে এই কাজটি করি UI ফ্রিজ না হওয়ার জন্য। তারপর রেজাল্টগুলো মেইন কিউতে আনে UI আপডেট করি:
URLSession.shared.dataTask(with: url) { data, response, error in if let data = data { DispatchQueue.main.async { // UI work self.label.text = String(data: data, encoding: .utf8) } } }
রুল অফ থাম্ব হিসেবে, সব UI ওয়ার্ক অবশ্যই মেইন কিউতে এক্সিকিউট করা উচিত। আপনি Xcode-এ Main Thread Checker অপশন অন করতে পারেন যাতে UI ওয়ার্ক ব্যাকগ্রাউন্ড থ্রেডে এক্সিকিউট হলে ওয়ার্নিং পান।
মেইন কিউর পাশাপাশি, প্রতিটি অ্যাপের সাথে বেশ কিছু প্রি-ডিফাইনড কনকারেন্ট কিউ থাকে যাদের ভিন্ন ভিন্ন কোয়ালিটি অফ সার্ভিস (GCD-এ প্রায়োরিটির একটি অ্যাবস্ট্রাক্ট ধারণা) থাকে।
উদাহরণস্বরূপ, user interactive (সর্বোচ্চ প্রায়োরিটি) QoS কিউতে অ্যাসিনক্রোনাসলি ওয়ার্ক সাবমিট করার কোড:
DispatchQueue.global(qos: .userInteractive).async { print("We're on a global concurrent queue!") }
বিকল্পভাবে, আপনি ডিফল্ট প্রায়োরিটি গ্লোবাল কিউ কল করতে পারেন QoS নির্দিষ্ট না করে একে এইভাবে:
DispatchQueue.global().async { print("Generic global queue") }
অতিরিক্তভাবে, আপনি নিচের সিনট্যাক্স ব্যবহার করে নিজস্ব প্রাইভেট কিউ তৈরি করতে পারেন:
let serial = DispatchQueue(label: "com.besher.serial-queue") serial.async { print("Private serial queue") }
প্রাইভেট কিউ তৈরি করার সময়, ডেসক্রিপটিভ লেবেল ব্যবহার করা (রিভার্স DNS নোটেশন মত) আপনাকে ডিবাগিং-এ সাহায্য করে Xcode-এর নেভিগেটর, lldb, এবং Instruments-এ:
ডিফল্টলি, প্রাইভেট কিউ সিরিয়াল (আমি শীঘ্রই ব্যাখ্যা করবো এই কী!) আপনি যদি একটি প্রাইভেট কনকারেন্ট কিউ তৈরি করতে চান, তবে আপনি অপশনাল অ্যাট্রিবিউটস প্যারামিটার ব্যবহার করে করতে পারেন:
let concurrent = DispatchQueue(label: "com.besher.serial-queue", attributes: .concurrent) concurrent.sync { print("Private concurrent queue") }
একটি অপশনাল QoS প্যারামিটারও আছে। আপনি যে প্রাইভেট কিউগুলো তৈরি করবেন সেগুলো অবশেষে তাদের দেওয়া প্যারামিটার ভিত্তিক করে গ্লোবাল কনকারেন্ট কিউ-এ যাবে।
একটি টাস্কে কী থাকে?
আমি কিউতে টাস্ক ডিসপ্যাচ করার কথা বলেছিলাম। টাস্ক বলতে যেকোনো কোড ব্লক যেটি আপনি sync বা async ফাংশন ব্যবহার করে একটি কিউতে সাবমিট করেন। সেগুলো অ্যানোনিমাস ক্লোজার হিসেবে সাবমিট করা যায়:
DispatchQueue.global().async { print("Anonymous closure") }
অথবা একটি ডিসপ্যাচ ওয়ার্ক আইটেমের ভিতরে যা পরে এক্সিকিউট হবে:
let item = DispatchWorkItem(qos: .utility) { print("Work item to be executed later") }
আপনি সিনক্রোনাসলি বা অ্যাসিনক্রোনাসলি ডিসপ্যাচ করুন, এবং সিরিয়াল বা কনকারেন্ট কিউ বেছে নিন, একটি সিঙ্গল টাস্কের ভিতরে সকল কোড লাইন বাই লাইন এক্সিকিউট হবে। কনকারেন্সি শুধু প্রাসঙ্গিক যখন একাধিক টাস্ক ইভ্যালুয়েট করা হয়।
উদাহরণস্বরূপ, আপনার যদি একই টাস্কের ভিতরে ৩টি লুপ থাকে, তবে এই লুপগুলো সবসময় ক্রমেই এক্সিকিউট হবে:
DispatchQueue.global().async { for i in 0..<10 { print(i) } for _ in 0..<10 { print("?") } for _ in 0..<10 { print("?") } }
এই কোড সবসময় ০ থেকে ৯ পর্যন্ত দশ ডিজিট প্রিন্ট করবে, তারপর দশ ব্লু সার্কেল, তারপর দশ ব্রোকেন হার্ট, আপনি কীভাবে ক্লোজারটি ডিসপ্যাচ করলেন তার নির্বিশেষে।
ইনডিভিজুয়াল টাস্কগুলোরও নিজস্ব QoS লেভেল থাকতে পারে (ডিফল্টলি তারা তাদের কিউর প্রায়োরিটি ব্যবহার করে)। কিউ QoS এবং টাস্ক QoS-এর এই পার্থক্য কিছু আকর্ষণীয় বিহেভিয়ারের দিকে নিয়ে যায় যা আমরা প্রায়োরিটি ইনভার্সন সেকশনে আলোচনা করব।
এখন পর্যন্ত আপনি চিন্তা করতে পারেন সিরিয়াল এবং কনকারেন্ট আসলে কী। আপনি sync এবং async-এর পার্থক্য সম্পর্কেও চিন্তা করতে পারেন টাস্ক সাবমিট করার সময়। এখানেই এই আর্টিকেলের মূল বিষয়, তাই চলে যাই গভীরে!
Sync vs Async
আপনি যখন একটি টাস্ক কিউতে ডিসপ্যাচ করেন, আপনি সেটি সিনক্রোনাসলি বা অ্যাসিনক্রোনাসলি করার চয়েস করতে পারেন sync এবং async ডিসপ্যাচ ফাংশন ব্যবহার করে। Sync এবং async প্রধানত সাবমিট করা টাস্কের সোর্স-কে প্রভাবিত করে, অর্থাৎ যে কিউ থেকে এটি সাবমিট করা হয়েছে।
যখন আপনার কোড একটি sync স্টেটমেন্টে পৌঁছায়, এটি করেন্ট কিউকে ব্লক করে রাখে যতক্ষণ না সেই টাস্ক কমপ্লিট হয়। একবার টাস্ক রিটার্ন/কমপ্লিট হলে, কন্ট্রোল কলারকে ফেরত দেয়া হয়, এবং sync টাস্কের পরে থাকা কোড চালু থাকে।
sync-কে 'ব্লকিং'-এর সমার্থক বলে ভাবুন।
অন্যদিকে, একটি async স্টেটমেন্ট করেন্ট কিউর সাপেক্ষে অ্যাসিনক্রোনাসলি এক্সিকিউট হবে, এবং async ক্লোজারের কন্টেন্ট এক্সিকিউট হওয়ার জন্য অপেক্ষা না করেই কলারকে কন্ট্রোল ফেরত দিয়ে দেবে। সেই অ্যাসিন্ক ক্লোজারের কোড কখন এক্সিকিউট হবে সেটির কোনো গ্যারান্টি নেই।
কারেন্ট কিউ?
সোর্স, বা কারেন্ট, কিউ কী তা সবসময় স্পষ্ট নয়, কারণ কোডে এটি সবসময় এক্সপ্লিসিটলি ডিফাইন করা থাকে না।
উদাহরণস্বরূপ, আপনি যদি আপনার sync স্টেটমেন্ট viewDidLoad-এ কল করেন, আপনার কারেন্ট কিউ হবে মেইন ডিসপ্যাচ কিউ। আপনি যদি একই ফাংশনটি URLSession কমপ্লিশন হ্যান্ডলারের ভিতরে কল করেন, আপনার কারেন্ট কিউ হবে একটি ব্যাকগ্রাউন্ড কিউ।
Sync vs async-এ ফিরে যাই, এই উদাহরণটি নিই:
DispatchQueue.global().sync { print("Inside") } print("Outside") // Console output: // Inside // Outside
উপরের কোড করেন্ট কিউকে ব্লক করবে, ক্লোজারের ভিতরে যাবে এবং গ্লোবাল কিউতে এর কোড এক্সিকিউট করবে "Inside" প্রিন্ট করে, তারপর "Outside" প্রিন্ট করবে। এই অর্ডার গ্যারান্টид।
দেখি async ট্রাই করলে কী হয়:
DispatchQueue.global().async { print("Inside") } print("Outside") // Potential console output (based on QoS): // Outside // Inside
আমাদের কোড এখন ক্লোজারটি গ্লোবাল কিউতে সাবমিট করে, তারপর অবিলম্বে পরবর্তী লাইন চালাতে থাকে। এটি সম্ভবত "Outside" "Inside"-এর আগে প্রিন্ট করবে, কিন্তু এই অর্ডার গ্যারান্টид নয়। এটি সোর্স এবং ডেস্টিনেশন কিউর QoS, সেইসাথে সিস্টেম যা কন্ট্রোল করে সেগুলোর ওপর ডিপেন্ড করে।
থ্রেড GCD-এ একটি ইমপ্লিমেন্টেশন ডিটেইল — আমাদের এগুলোতে ডাইরেক্ট কন্ট্রোল নেই এবং কিউ অ্যাবস্ট্রাকশন ব্যবহার করে সেগুলোতে ডील করতে পারি। তবুও, আমি মনে করি থ্রেড বিহেভিয়ার 'আন্ডার দ্যা কভার' তাকিয়ে দেখা GCD-এ আমরা যে কিছু চ্যালেঞ্জ মুখোমুখি হই সেগুলো বুঝতে উপকারী হতে পারে।
উদাহরণস্বরূপ, আপনি যখন sync ব্যবহার করে একটি টাস্ক সাবমিট করেন, GCD পারফরম্যান্স অপ্টিমাইজ করার জন্য সেই টাস্কটি কলারের থ্রেডেই (অর্থাৎ কারেন্ট থ্রেড) এক্সিকিউট করে।
একটি এক্সেপশন আছে, যা হলো আপনি যখন মেইন কিউতে একটি সিন্ক টাস্ক সাবমিট করেন — এটা সবসময় মেইন থ্রেডে টাস্ক রান করবে, কলারের থ্রেডে না। এই বিহেভিয়ারের কিছু র্যামিফিকেশন থাকতে পারে যা আমরা প্রায়োরিটি ইনভার্সন সেকশনে এক্সপ্লোর করব।
কোনটি ব্যবহার করবেন?
কিউতে ওয়ার্ক সাবমিট করার সময়, Apple অ্যাসিনক্রোনাস এক্সিকিউশন রিকমেন্ড করে সিনক্রোনাস এক্সিকিউশনের চেয়ে। যাই হোক, কিছু পরিস্থিতি আছে যেখানে sync बेटার চয়েস হতে পারে, যেমন রেস কন্ডিশন হ্যান্ডেল করার সময়, বা খুব ছোট একটি টাস্ক পারফর্ম করার সময়। আমি শীঘ্রই এই পরিস্থিতিগুলো কভার করব।
একটি ফাংশনের ভিতরে বড় টাস্ক অ্যাসিনক্রোনাসলি পারফর্ম করার একটি বড় পরিণাম হলো ফাংশনটি আর সরাসরি তার ভ্যালু রিটার্ন করতে পারবে না (যদি সেগুলো অ্যাসিন্ক ওয়ার্কের ওপর ডিপেন্ড করে)। এটিকে বদলে একটি ক্লোজার/কমপ্লিশন হ্যান্ডলার প্যারামিটার ব্যবহার করতে হবে রেজাল্ট ডেলিভার করার জন্য।
এই কনসেপ্ট ডেমো করার জন্য, একটি ছোট ফাংশন নিই যা ইমেজ ডেটা এক্সেপ্ট করে, ইমেজ প্রসেস করার জন্য কিছু এক্সপেনসিভ কম্পিউটেশন করে, তারপর রেজাল্ট রিটার্ন করে:
func processImage(data: Data) -> UIImage? { guard let image = UIImage(data: data) else { return nil } // calling an expensive function let processedImage = upscaleAndFilter(image: image) return processedImage }
এই উদাহরণে, upscaleAndFilter(image:) ফাংশনটি বেশ কিছু সেকেন্ড নিতে পারে, তাই আমরা এটিকে একটি আলাদা কিউতে অফলোড করতে চাই UI ফ্রিজ না হওয়ার জন্য। একটি ডেডিকেটেড কিউ তৈরি করি ইমেজ প্রসেসিং-এর জন্য, এবং তারপর এক্সপেনসিভ ফাংশনটি অ্যাসিনক্রোনাসলি ডিসপ্যাচ করি:
let imageProcessingQueue = DispatchQueue(label: "com.besher.image-processing") func processImageAsync(data: Data) -> UIImage? { guard let image = UIImage(data: data) else { return nil } imageProcessingQueue.async { let processedImage = upscaleAndFilter(image: image) return processedImage } }
এই কোডে দুটি সমস্যা আছে। প্রথমে, রিটার্ন স্টেটমেন্ট অ্যাসিন্ক ক্লোজারের ভিতরে আছে, তাই এটি আর processImageAsync(data:) ফাংশনে ভ্যালু রিটার্ন করছে না, এবং বর্তমানে কোনো উদ্দেশ্য সার্ভ করছে না।
বড় সমস্যা হলো আমাদের processImageAsync(data:) ফাংশনটি আর কোনো ভ্যালু রিটার্ন করছে না, কারণ ফাংশন তার বডির শেষে পৌঁছায় তার আগে async ক্লোজারের ভিতরে ঢুকছে না।
এই এরর ফিক্স করতে, আমরা ফাংশনটি অ্যাডজাস্ট করব যাতে এটি আর সরাসরি ভ্যালু রিটার্ন না করে। বদলে, এতে একটি নতুন কমপ্লিশন হ্যান্ডলার প্যারামিটার থাকবে যেটি আমরা কল করব একবার আমাদের অ্যাসিনক্রোনাস ফাংশন তার কাজ কমপ্লিট করলে:
let imageProcessingQueue = DispatchQueue(label: "com.besher.image-processing") func processImageAsync(data: Data, completion: @escaping (UIImage?) -> Void) { guard let image = UIImage(data: data) else { completion(nil) return } imageProcessingQueue.async { let processedImage = self.upscaleAndFilter(image: image) completion(processedImage) } }
এই উদাহরণে স্পষ্ট হয়, ফাংশনকে অ্যাসিনক্রোনাস করার পরিবর্তন এর কলারটাও প্রয়াপ্য হয়েছে, যাকে এখন একটি ক্লোজার পাস করতে হবে এবং রেজাল্ট অ্যাসিনক্রোনাসলি হ্যান্ডেল করতে হবে। একটি অ্যাসিনক্রোনাস টাস্ক ইন্ট্রোডিউস করলে আপনি সম্ভবত বেশ কিছু ফাংশনের চেইন মডিফাই করতে পারেন।
কনকারেন্সি এবং অ্যাসিনক্রোনাস এক্সিকিউশন আপনার প্রজেক্টে কমপ্লেক্সিটি যোগ করে যেমন আমরা পরীক্ষা করেছি। এই ইনডাইরেকশন ডিবাগিং-কেও কঠিন করে দেয়। তাই কনকারেন্সি সম্পর্কে ডিজাইন সাইকেলের শুরুতে চিন্তা করা দারুণ ফলাফল দেয় — এটি এমন কিছু নয় যা আপনি আপনার ডিজাইন সাইকেলের শেষে ট্যাক অন করবেন।
বিপরীতে, সিনক্রোনাস এক্সিকিউশন কমপ্লেক্সিটি বর্ধন করে না। বরং, এটি আপনাকে আগের মতো রিটার্ন স্টেটমেন্ট ব্যবহার করতে দেয়। একটি sync টাস্ক ধারণকারী ফাংশন টাস্কের কোড কমপ্লিট হওয়া পর্যন্ত রিটার্ন করবে না। তাই এটিকে কমপ্লিশন হ্যান্ডলার দরকার নেই।
আপনি যদি একটি ছোট টাস্ক সাবমিট করেন (উদাহরণস্বরূপ, একটি ভ্যালু আপডেট), তবে সেটি সিনক্রোনাসলি করার কথা বিবেচনা করুন। এটি না কেবল আপনার কোড সিম্পল রাখে, এটি বেটার পারফর্মও করে — অ্যাসিন্ক মনে করা হয় ১ms-এর নিচে কমপ্লিট হওয়া ছোট টাস্কের জন্য অ্যাসিনক্রোনাসলি কাজ করার বেনিফিট চেয়ে বেশি ওভারহেড ইনকার করছে।
আপনি যদি একটি বড় টাস্ক সাবমিট করেন, যেন উপরের ইমেজ প্রসেসিং, তবে অ্যাসিনক্রোনাসলি করার কথা বিবেচনা করুন কলারকে অনেকক্ষণ ব্লক না করার জন্য।
একই কিউতে ডিসপ্যাচ করা
একটি কিউ থেকে নিজে নিজে কিউতে অ্যাসিনক্রোনাসলি টাস্ক ডিসপ্যাচ করা সেফ (উদাহরণস্বরূপ, আপনি কারেন্ট কিউতে .asyncAfter ব্যবহার করতে পারেন), কিন্তু আপনি একই কিউতে সিনক্রোনাসলি একটি টাস্ক ডিসপ্যাচ করতে পারবেন না। এটা করলে একটি ডেডলক হবে যা অবিলম্বে অ্যাপ ক্র্যাশ করাবে!
এই ইস্যু বাড়াতে পারে যখন সিনক্রোনাস কলের একটি চেইন পারফর্ম করা হয় যা মূল কিউতে ফেরত আসে। অর্থাৎ, আপনি অন্য একটি কিউতে একটি টাস্ক sync করেন, এবং টাস্ক কমপ্লিট হলে, এটি রেজাল্টগুলো মূল কিউতে sync করে ফেরত দেয়, যা ডেডলক করে। এতে এড়াতে async ব্যবহার করুন।
মেইন কিউ ব্লক করা
মেইন কিউ থেকে টাস্ক সিনক্রোনাসলি ডিসপ্যাচ করলে সেটি মেইন কিউকে ব্লক করবে, তদন্ত UI ফ্রিজ করে দেবে, যতক্ষণ না টাস্ক কমপ্লিট হয়। তাই মেইন কিউ থেকে সিনক্রোনাসলি ওয়ার্ক ডিসপ্যাচ করা এড়ানো ভালো, যদি না আপনি খুব হালকা ওয়ার্ক পারফর্ম করেন।
Serial vs Concurrent
সিরিয়াল এবং কনকারেন্ট ডেস্টিনেশন-কে প্রভাবিত করে — অর্থাৎ যে কিউতে আপনার ওয়ার্ক সাবমিট করা হয়েছে রান হওয়ার জন্য। এটি সিন্ক এবং অ্যাসিন্ক-এর বিপরীত, যা সোর্স-কে প্রভাবিত করে।
একটি সিরিয়াল কিউ একবারে একাধিক থ্রেডে তার ওয়ার্ক এক্সিকিউট করবে না, আপনি সেটিতে কতটুকু টাস্ক ডিসপ্যাচ করুন না কেন। ফলস্বরূপ, টাস্কগুলো শুধু শুরুর نظمে (FIFO) নয়, বরং টারমিনেশনেও FIFO অর্ডার মেইন্টেইন করতে গ্যারান্টид।
অতিরিক্তভাবে, আপনি যখন একটি সিরিয়াল কিউ ব্লক করেন (sync কল, সেমাফোর, বা অন্য কোনো টুল ব্যবহার করে), সেই কিউর সব কাজ ব্লক থাকবে যতক্ষণ না ব্লকটি শেষ হয়।
একটি কনকারেন্ট কিউ একাধিক থ্রেড স্পাউন করতে পারে, এবং সিস্টেম 決定 করে কতগুলো থ্রেড তৈরি হবে। টাস্কগুলো সবসময় শুরু হয় FIFO অর্ডারে, কিন্তু কিউ টাস্কগুলো ফিনিশ হওয়ার জন্য অপেক্ষা করে না পরবর্তী টাস্ক শুরু করার আগে, তাই কনকারেন্ট কিউ-র টাস্কগুলো যেকোনো অর্ডারে ফিনিশ করতে পারে।
আপনি যখন একটি কনকারেন্ট কিউতে ব্লকিং কমান্ড পারফর্ম করেন, এটি সেই কিউর অন্যান্য থ্রেডকে ব্লক করে না। অতিরিক্তভাবে, যখন একটি কনকারেন্ট কিউ ব্লক হয়, এটি থ্রেড এক্সপ্লোজান-এর রিস্কে পড়ে। আমি এটি পরে আরো বিস্তারিত আলোচনা করব।
আপনার অ্যাপের মেইন কিউ সিরিয়াল। সকল গ্লোবাল প্রি-ডিফাইনড কিউ কনকারেন্ট। আপনি যেকোনো প্রাইভেট ডিসপ্যাচ কিউ তৈরি করবেন সেটি ডিফল্টলি সিরিয়াল, কিন্তু অপশনাল অ্যাট্রিবিউট ব্যবহার করে কনকারেন্ট করা যায় যেমন আগে আলোচনা করা হয়েছে।
এখানে একটি গুরুত্বপূর্ণ বিষয় নোট করা যাক যে সিরিয়াল vs কনকারেন্ট কনসেপ্ট শুধু প্রাসঙ্গিক যখন একটি নির্দিষ্ট কিউ নিয়ে আলোচনা করা হয়। সকল কিউ একে অপর সাপেক্ষে কনকারেন্ট।
অর্থাৎ, আপনি যদি মেইন কিউ থেকে একটি প্রাইভেট সিরিয়াল কিউতে ওয়ার্ক অ্যাসিনক্রোনাসলি ডিসপ্যাচ করেন, সেই ওয়ার্কটি মেইন কিউ সাপেক্ষে কনকারেন্টলি কমপ্লিট হবে। এবং আপনি যদি দুটি ভিন্ন সিরিয়াল কিউ তৈরি করেন, এবং সেটির একটিতে ব্লকিং ওয়ার্ক পারফর্ম করেন, অন্য কিউটি অপ্রভাবিত থাকবে।
একাধিক সিরিয়াল কিউর কনকারেন্সি ডেমো করার জন্য, এই উদাহরণটি নিই:
let serial1 = DispatchQueue(label: "com.besher.serial1") let serial2 = DispatchQueue(label: "com.besher.serial2") serial1.async { for _ in 0..<5 { print("?") } } serial2.async { for _ in 0..<5 { print("?") } }
এখানে দুটি কিউই সিরিয়াল, কিন্তু রেজাল্টগুলো জামেলা হয়েছে কারণ সেগুলো একে অপর সাপেক্ষে কনকারেন্টলি এক্সিকিউট হয়েছে। সেগুলো প্রতিটা সিরিয়াল (বা কনকারেন্ট) হলে এই রেজাল্টের ওপর কোনো ইফেক্ট নেই। তাদের QoS লেভেল নির্ধারণ করে কে সাধারণত প্রথম ফিনিশ করবে (অর্ডার গ্যারান্টিড নেই)।
আমরা যদি চাই প্রথম লুপটি প্রথম ফিনিশ করে তারপর সেকেন্ড লুপ শুরু হয়, তবে আমরা প্রথম টাস্কটি কলার থেকে সিনক্রোনাসলি সাবমিট করতে পারি:
let serial1 = DispatchQueue(label: "com.besher.serial1") let serial2 = DispatchQueue(label: "com.besher.serial2") serial1.sync { // <---- we changed this to 'sync' for _ in 0..<5 { print("?") } } // we don't get here until first loop terminates serial2.async { for _ in 0..<5 { print("?") } }
এটি~んেশাসারি নয়, কারণ আমরা এখন কলারকে ব্লক করছি যতক্ষণ না প্রথম লুপ এক্সিকিউট হচ্ছে।
কলারকে ব্লক না করে এড়াতে, আমরা দুটি টাস্কই অ্যাসিনক্রোনাসলি সাবমিট করতে পারি, কিন্তু একই সিরিয়াল কিউতে:
let serial = DispatchQueue(label: "com.besher.serial") serial.async { for _ in 0..<5 { print("?") } } serial.async { for _ in 0..<5 { print("?") } }
এখন আমাদের টাস্কগুলো কলার সাপেক্ষে কনকারেন্টলি এক্সিকিউট হচ্ছে, কিন্তু তাদের অর্ডার ইনট্যাক্ট রেখে।
দ্রষ্টব্য: আপনি যদি অপশনাল প্যারামিটার দিয়ে আমাদের সিঙ্গল কিউকে কনকারেন্ট করে দেন, তবে আমরা আবার জামেলা রেজাল্টে ফিরে যাব, প্রত্যাশা অনুযায়ী:
let concurrent = DispatchQueue(label: "com.besher.concurrent", attributes: .concurrent) concurrent.async { for _ in 0..<5 { print("?") } } concurrent.async { for _ in 0..<5 { print("?") } }
কখনো কখনো আপনি সিনক্রোনাস এক্সিকিউশন কে সিরিয়াল এক্সিকিউশনের সাথে গুলিয়ে ফেলতে পারেন (কমপক্ষে আমি করতাম), কিন্তু এগুলো ভিন্ন জিনিস। উদাহরণস্বরূপ, আমাদের আগের উদাহরণের লাইন ৩-এ প্রথম ডিসপ্যাচকে sync কল-এ পরিবর্তন করুন:
let concurrent = DispatchQueue(label: "com.besher.concurrent", attributes: .concurrent) concurrent.sync { for _ in 0..<5 { print("?") } } concurrent.async { for _ in 0..<5 { print("?") } }
হঠাৎ, আমাদের রেজাল্টগুলো আবার পারফেক্ট অর্ডারে আসে। কিন্তু এটি একটি কনকারেন্ট কিউ, তাহলে এটি কীভাবে সম্ভব? কি sync স্টেটমেন্টটি যেন এটিকে সিরিয়াল কিউতে রূপান্তরিত করেছে?
উত্তর না!
এটি একটু চোড়া। ঘটনাটি হলো আমরা async কলটি পেয়ে নাই যতক্ষণ না প্রথম টাস্ক তার এক্সিকিউশন কমপ্লিট করেছে। কিউটি এখনও খুব কনকারেন্ট, কিন্তু এই কোডের জুম-ইন করা সেকশনে। এটি যেন সিরিয়াল হয়েছে। এর কারণ হলো আমরা কলারকে ব্লক করছি, এবং পরবর্তী টাস্কে যাচ্ছি না যতক্ষণ না প্রথমটি ফিনিশ হয়।
আপনার অ্যাপে কোথাও অন্য একটি কিউ যদি এই সেম কিউতে ওয়ার্ক সাবমিট করার চেষ্টা করেযখন এটা এখনো sync স্টেটমেন্ট এক্সিকিউট করছে, সেই ওয়ার্ক ইতবে যেই কিছুর সাথে এখানে রান করছে সেটি কনকারেন্টলি রান করবে, কারণ এটা এখনও একটি কনকারেন্ট কিউ।
কোনটি ব্যবহার করবেন?
সিরিয়াল কিউ CPU অপ্টিমাইজেশন এবং ক্যাশিং-র সুবিধা নেয়, এবং কন্টেক্সট সুইচিং কমাতে সাহায্য করে।
Apple আপনার অ্যাপে প্রতিটি সাবসিস্টেমের জন্য একটি সিরিয়াল কিউ দিয়ে শুরু করার পরামর্শ দেয় — উদাহরণস্বরূপ নেটওয়ার্কিং-এর জন্য একটি, ফাইল কমপ্রেশনের জন্য একটি, ইত্যাদি। যদি প্রয়োজন হয়, তবে আপনি পরে সেটার্গেট মেথড বা কিউ বিল্ড করার সময় অপশনাল টার্গেট প্যারামিটার ব্যবহার করে প্রতিটি সাবসিস্টেমের জন্য কিউর একটি হায়ারার্কি তৈরি করতে পারেন।
আপনি যদি পারফরম্যান্স বটলनेक atrap করেন, আপনার অ্যাপের পারফরম্যান্স মেজার করুন তারপর দেখুন কনকারেন্ট কিউ সাহায্য করে কিনা। আপনি যদি মেজারেবল বেনিফিট না দেখেন, তবে সিরিয়াল কিউতে থাকা ভালো।
পিটফল (Pitfalls)
প্রায়োরিটি ইনভার্সন এবং কোয়ালিটি অফ সার্ভিস
প্রায়োরিটি ইনভার্সন হলো যখন একটি হাই প্রায়োরিটি টাস্ক একটি লো প্রায়োরিটি টাস্ক দ্বারা রান হতে বাধা পায়, যেন তাদের রিলেটিভ প্রায়োরিটি উল্টে যায়।
এই পরিস্থিতি প্রায় হয় যখন একটি হাই QoS কিউ একটি লো QoS কিউর সাথে একটি রিসোর্স শেয়ার করে, এবং লো QoS কিউটি সেই রিসোর্সে লক পায়।
কিন্তু আমি একটি ভিন্ন সিনেরিও কভার করতে চাই যা আমাদের আলোচনার সাথে বেশি রিলেভেন্ট — সেটি হলো আপনি লো QoS সিরিয়াল কিউতে টাস্ক সাবমিট করেন, তারপর সেই সেম কিউতে একটি হাই QoS টাস্ক সাবমিট করেন। এই সিনেরিওও প্রায়োরিটি ইনভার্সন তৈরি করে, কারণ হাই QoS টাস্কটি লো QoS টাস্কগুলো ফিনিশ হওয়ার জন্য অপেক্ষা করতে হয়।
GCD প্রায়োরিটি ইনভার্সন সমাধান করে সেই কিউর QoS সাময়িকভাবে বেড়িয়ে দিয়ে যাতে লো প্রায়োরিটি টাস্কগুলো যা 'সামনে' আছে, বা হাই প্রায়োরিটি টাস্ককে ব্লক করছে, সেগুলোর QoS হাই প্রায়োরিটি টাস্কর সাথে মিলিয়ে দেয়।
এটা একটু যেন একটি অ্যাম্বুলেন্সের সামনে গাড়িগুলো আটকে আছে। হঠাৎ তারা রেড লাইট পার করার অনুমতি পায় শুধু যাতে অ্যাম্বুলেন্সটি যেতে পারে (বাস্তবে গাড়িগুলো একদিকে হয়ে যায়, কিন্তু একটি সংকীর্ণ (সিরিয়াল) স্ট্রিট কল্পনা করুন, আপনি পয়েন্ট বুঝতে পারছেন :-P)
ইনভার্সন সমস্যা ইলাস্ট্রেট করতে, এই কোডটি দিয়ে শুরু করি:
enum Color: String { case blue = "?" case white = "⚪️" } func output(color: Color, times: Int) { for _ in 1...times { print(color.rawValue) } } let starterQueue = DispatchQueue(label: "com.besher.starter", qos: .userInteractive) let utilityQueue = DispatchQueue(label: "com.besher.utility", qos: .utility) let backgroundQueue = DispatchQueue(label: "com.besher.background", qos: .background) let count = 10 starterQueue.async { backgroundQueue.async { output(color: .white, times: count) } backgroundQueue.async { output(color: .white, times: count) } utilityQueue.async { output(color: .blue, times: count) } utilityQueue.async { output(color: .blue, times: count) } // next statement goes here }
আমরা একটি স্টার্টার কিউ তৈরি করি (যেখান থেকে আমরা টাস্ক সাবমিট করি), এবং দুটি ভিন্ন QoS-য় কিউ। তারপর আমরা এই দুটি কিউতে টাস্ক ডিসপ্যাচ করি, প্রতিটি টাস্ক একটি নির্দিষ্ট রঙের সার্কেল প্রিন্ট করে (ইউটিলিটি কিউ ব্লু, ব্যাকগ্রাউন্ড হোয়াইট)।
যেহেতু এই টাস্কগুলো অ্যাসিনক্রোনাসলি সাবমিট হয়েছে, আপনি অ্যাপ রান করলে প্রতিবার কিছুটা ভিন্ন রেজাল্ট দেখবেন। যাই হোক, আপনার আশা অনুযায়ী, লো QoS-য় কিউ (ব্যাকগ্রাউন্ড) প্রায় সবসময় লাস্টে ফিনিশ করে। আসলে, লাস্ট ১০-১৫টি সার্কেল প্রায় সবসময় হোয়াইট।
কিন্তু দেখুন কী হয় যখন আমরা লাস্ট অ্যাসিন্ক স্টেটমেন্টের পর ব্যাকগ্রাউন্ড কিউতে একটি সিন্ক টাস্ক সাবমিট করি। আপনাকে সিন্ক স্টেটমেন্টের ভিতরে কিছু প্রিন্ট করতে হবে না, শুধু এই লাইনটি যোগ করলেই হবে:
// add this after the last async statement, // still inside starterQueue.async backgroundQueue.sync {}
কনসোলে রেজাল্টগুলো উল্টে গিয়েছে! এখন, হাই প্রায়োরিটি কিউ (ইউটিলিটি) সবসময় লাস্টে ফিনিশ করছে, এবং লাস্ট ১০-১৫টি সার্কেল ব্লু।
এটি কেন হয় তা বুঝতে, আমাদের সিনক্রোনাস ওয়ার্ক কলারের থ্রেডে এক্সিকিউট হয় এই ফ্যাক্টটি পুনরাবলম্বন করতে হবে (মেইন কিউতে সাবমিট না করা সত্ত্বে)।
আমাদের উপরের উদাহরণে, কলার (starterQueue) টপ QoS (userInteractive) 갖고 আছে। তাই সেটা এক secara innocuous sync টাস্ক শুধু স্টার্টার কিউকে ব্লক করছে না, বরং সেটি স্টার্টারের হাই QoS থ্রেডেও রান করছে। টাস্কটি তাই হাই QoS-এ রান করছে, কিন্তু সেইম ব্যাকগ্রাউন্ড কিউতে এর সামনে দুটি অন্য টাস্ক আছে যাদের ব্যাকগ্রাউন্ড QoS। প্রায়োরিটি ইনভার্সন ডিটেক্টেড!
আশা অনুযায়ী, GCD এই ইনভার্সন সমাধান করে পুরো কিউর QoS সাময়িকভাবে হাই QoS টাস্কর সাথে মেলিয়ে দিয়ে। ফলস্বরূপ, ব্যাকগ্রাউন্ড কিউর সব টাস্ক শেষে user interactive QoS-এ রান করে, যা utility QoS চেয়ে বেশি। এবং তাই ইউটিলিটি টাস্কগুলো লাস্টে ফিনিশ করছে!
সাইড-নোট: আপনি যদি সেই উদাহরণ থেকে স্টার্টার কিউ সরিয়ে মেইন কিউ থেকে সাবমিট করেন, আপনি সিমিলার রেজাল্ট পাবেন, কারণ মেইন কিউও user interactive QoS 갖고 থাকে।
এই উদাহরণে প্রায়োরিটি ইনভার্সন এড়াতে, আমাদের sync স্টেটমেন্ট দিয়ে স্টার্টার কিউ ব্লক করা এড়াতে হবে। async ব্যবহার করলে সেই সমস্যা সমাধান হয়।
যদি এটি সবসময় আইডিয়াল না হয়, আপনি প্রাইভেট কিউ তৈরি করার সময় বা গ্লোবাল কনকারেন্ট কিউতে ডিসপ্যাচ করার সময় ডিফল্ট QoS ব্যবহার করে প্রায়োরিটি ইনভার্সন মিনিমাইজ করতে পারেন।
থ্রেড এক্সপ্লোজান
আপনি যখন একটি কনকারেন্ট কিউ ব্যবহার করেন, আপনি থ্রেড এক্সপ্লোজান-এর রিস্কে পড়েন আপনি সতর্ক না থাকলে। এটি ঘটতে পারে যখন আপনি একটি কনকারেন্ট কিউতে টাস্ক সাবমিট করার চেষ্টা করেন যা বর্তমানে ব্লক্ড (উদাহরণস্বরূপ সেমাফোর, সিন্ক, বা অন্য কোনো উপায় দিয়ে।) আপনার টাস্কগুলো রান করবে, কিন্তু সিস্টেম সম্ভবত নতুন থ্রেড স্পিন আপ করবে এই নতুন টাস্কগুলো অকমোডেট করার জন্য, এবং থ্রেড সস্তা নয়।
এই কারণেই Apple আপনার অ্যাপে প্রতিটি সাবসিস্টেমের জন্য একটি সিরিয়াল কিউ দিয়ে শুরু করার পরামর্শ দেয়, কারণ প্রতিটি সিরিয়াল কিউ শুধু একটি থ্রেড ব্যবহার করতে পারে। মনে রাখবেন সিরিয়াল কিউ altres কিউ সাপেক্ষে কনকারেন্ট, তাই আপনি আপনার ওয়ার্ক একটি কিউতে অফলোড করলে পারফরম্যান্স বেনিফিট পাবেন, এমনকি সেটি কনকারেন্ট না হলেও।
রেস কন্ডিশন
Swift অ্যারে, ডিকশনারি, স্ট্রাক্ট, এবং অন্যান্য ভ্যালু টাইপ ডিফল্টলি থ্রেড-সেফ নয়। উদাহরণস্বরূপ, যখন একাধিক থ্রেড একই অ্যারে অ্যাক্সেস এবং মডিফাই করার চেষ্টা করে, আপনি সমস্যায় পড়বেন।
রিডার্স-রাইটার্স সমস্যার বিভিন্ন সমাধান আছে, যেমন লক বা সেমাফোর ব্যবহার করা। কিন্তু আমি এখানে যে রিলেভেন্ট সমাধান আলোচনা করতে চাই সেটি হলো একটি আইসোলেশন কিউ ব্যবহার করা।
মান লইন আমাদের একটি ইন্টিজার অ্যারে আছে, এবং আমরা এই অ্যারে রেফারেন্স করে অ্যাসিনক্রোনাস ওয়ার্ক সাবমিট করতে চাই। যতক্ষণ আমাদের ওয়ার্ক শুধু অ্যারে রিড করে এবং মডিফাই করে না, আমরা সেফ। কিন্তু য EBV আমরা আমাদের অ্যাসিনক্রোনাস টাস্কের একটিতে অ্যারে মডিফাই করার চেষ্টা করি, আমরা আমাদের অ্যাপে অনস্টেবিলিটি আনার চেষ্টা করি।
এটি একটি ট্রিকি সমস্যা কারণ আপনার অ্যাপ ১০ বার ইশ্যু ছাড়া রান করতে পারে, এবং ১১वी বার ক্র্যাশ করবে। এই পরিস্থিতির জন্য Xcode-এ Thread Sanitizer একটি খুব হ্যান্ডি টুল। এই অপশন অন করলে আপনার অ্যাপে পটেনশিয়াল রেস কন্ডিশন আইডেন্টিফাই করতে সাহায্য করে।
সমস্যা ডেমো করতে, এই (মাননীয় কন্ট্রাইভড) উদাহরণটি নিই:
class ViewController: UIViewController { let concurrent = DispatchQueue(label: "com.besher.concurrent", attributes: .concurrent) var array = [1,2,3,4,5] override func viewDidLoad() { for _ in 0...1 { race() } } func race() { concurrent.async { for i in self.array { // read access print(i) } } concurrent.async { for i in 0..<10 { self.array.append(i) // write access } } } }
অ্যাসিন্ক টাস্কের একটি অ্যারে মডিফাই করছে ভ্যালু অ্যпен্ড করে। আপনি আপনার সিমুলেটরে এটি রান করলে ক্র্যাশ নাও হতে পারে। কিন্তু যতবার রান করুন (অথবা লাইন ৭-এ লুপ ফ্রিকোয়েন্সি বড়ান), এন্ডে আপনি ক্র্যাশ করবেন। আপনি যদি থ্রেড স্যানিটাইজার অন করেন, আপনি প্রতিবার অ্যাপ রান করলে ওয়ার্নিং পাবেন।
এই রেস কন্ডিশন ডিল করার জন্য, আমরা একটি আইসোলেশন কিউ যোগ করব যা ব্যারियर ফ্ল্যাগ ব্যবহার করে। এই ফ্ল্যাগ কিউর যেকোনো আউটস্ট্যান্ডিং টাস্ক ফিনিশ করতে দেয়, কিন্তু যেকোনো পরবর্তী টাস্ক এক্সিকিউট হতে ব্লক করে যতক্ষণ না ব্যারियर টাস্ক কমপ্লিট হয়।
ব্যারিয়ারকে একটি জন Tosilet ক্লিনার (শেয়ারড রিসোর্স) মনে করুন। রেস্টরুমের ভিতরে একাধিক (কনকারেন্ট) স্টল আছে যা লোকজন ব্যবহার করতে পারে।
আগমনের সময়, জন Tosilet একটি ক্লিনিং সাইন (ব্যারিয়ার) বসিয়ে যেকোনো নতুন কেউ আউটসাইড থেকে প্রবেশ করতে ব্লক করে যতক্ষণ না ক্লিনিং শেষ হয়, কিন্তু জন Tosilet ক্লিনিং শুরু করে না যতক্ষণ না ভিতরের সব লোক তাদের কাজ শেষ করে বের হয়ে যায়। সবাই বের হলে, জন Tosilet আইসোলেশনে পাবলিক Tosilet ক্লিন করে।
শেষে, জন Tosilet সাইনটি (ব্যারিয়ার) সরিয়ে দেয় যাতে বাহিরে কিউ করতে থাকা লোকজন নিশ্চিন্তে প্রবেশ করতে পারে।
কোডে এটি যেন দেখাবে:
class ViewController: UIViewController { let concurrent = DispatchQueue(label: "com.besher.concurrent", attributes: .concurrent) let isolation = DispatchQueue(label: "com.besher.isolation", attributes: .concurrent) private var _array = [1,2,3,4,5] var threadSafeArray: [Int] { get { return isolation.sync { _array } } set { isolation.async(flags: .barrier) { self._array = newValue } } } override func viewDidLoad() { for _ in 0...15 { race() } } func race() { concurrent.async { for i in self.threadSafeArray { print(i) } } concurrent.async { for i in 0..<10 { self.threadSafeArray.append(i) } } } }
আমরা একটি নতুন আইসোলেশন কিউ যোগ করেছি, এবং প্রাইভেট অ্যারে-এ অ্যাক্সেস রেস্ট্রিক্ট করেছি একটি গেটার এবং সেটার দিয়ে যা অ্যারে মডিফাই করার সময় ব্যারियर প্লেস করবে।
গেটার sync হতে হবে সরাসরি ভ্যালু রিটার্ন করার জন্য। সেটার async হতে পারে, কারণ রাইট হলে কলারকে ব্লক করার দরকার নেই।
আমরা সিরিয়াল কিউ বিনা ব্যারियर দিয়ে রেস কন্ডিশন সমাধান করতে পারতাম, কিন্তু তখন আমরা অ্যারেতে কনকারেন্ট রিড অ্যাক্সেসের সুবিধা হারাতাম। সম্ভবত আপনার কেসে সেটি মেনে চলে, আপনি ডিসাইড করুন।
উপসংহার
এতদূর পড়ার জন্য অসংখ্য ধন্যবাদ! আশা করি এই আর্টিকেল থেকে আপনি কিছু নতুন শিখেছেন। আমি আপনাকে একটি সারসংক্ষেপ এবং কিছু সাধারণ পরামর্শ দিয়ে ছেড়ে যাচ্ছি:
সারসংক্ষেপ
- কিউগুলো সবসময় তাদের টাস্ক শুরু করে FIFO অর্ডারে
- কিউগুলো সবসময় অন্য কিউ সাপেক্ষে কনকারেন্ট
- Sync vs Async সোর্সকে কনসার্ন করে
- Serial vs Concurrent ডেস্টিনেশনকে কনসার্ন করে
- Sync হলো 'ব্লকিং'-এর সমার্থক
- Async অবিলম্বে কলারকে কন্ট্রোল ফেরত দেয়
- Serial একক থ্রেড ব্যবহার করে, এবং এক্সিকিউশন অর্ডার গ্যারান্টি দেয়
- Concurrent একাধিক থ্রেড ব্যবহার করে, এবং থ্রেড এক্সপ্লোজান-এর রিস্ক থাকে
- কনকারেন্সি সম্পর্কে ডিজাইন সাইকেলের শুরুতে চিন্তা করুন
- সিনক্রোনাস কোড রিজন করা এবং ডিবাগ করা সহজ
- গ্লোবাল কনকারেন্ট কিউতে নির্ভর করা এড়ান
- প্রতিটি সাবসিস্টেমের জন্য একটি সিরিয়াল কিউ দিয়ে শুরু করার কথা বিবেচনা করুন
- কনকারেন্ট কিউতে স্যুইচ করুন শুধু যখন আপনি মেজারেবল পারফরম্যান্স বেনিফিট দেখেন
Swift Concurrency Manifesto-র 'কনকারেন্সির সমুদ্রে সিরিয়ালাইজেশনের একটি দ্বীপ' এই রূপক আমার পছন্দ। এই সেন্টিমেন্ট Matt Diephouse-এর এই টুইট-এও শেয়ার করা হয়েছে:
The secret to writing concurrent code is to make most of it serial. Restrict concurrency to a small, outer layer. (Serial core, concurrent shell.)
— Matt Diephouse (@mdiep) December 18, 2019
e.g. instead of using a lock to manage 5 properties, create a new type that wraps them and use a single property inside the lock.
আপনি যখন এই ফিলোসফি মনে রেখে কনকারেন্সি অ্যাপ্লাই করবেন, আমি মনে করি এটি আপনাকে কনকারেন্ট কোড লিখতে সাহায্য করবে যা রিজন করা যাবে কলব্যাকের গুলো মেসে হারিয়ে যাবেন না।
আপনার যেকোনো প্রশ্ন বা মন্তব্য থাকলে, টুইটারে আমাকে যোগাযোগ করতে বাধ্য মনে করবেন না।
Besher Al Maleh
Cover photo by Onur K on Unsplash
কম্প্যানিয়ন অ্যাপ এখানে ডাউনলোড করুন:
almaleh/Dispatcher
আমার কনকারেন্সি আর্টিকেলের কম্প্যানিয়ন অ্যাপ। GitHub-এ অ্যাকাউন্ট তৈরি করে almaleh/Dispatcher ডেভেলপমেন্টে অবদান রাখুন।
almaleh
GitHub
আমার অন্যান্য কিছু আর্টিকেল চেক আউট করুন:
Fireworks — A visual particles editor for Swift
Generate Swift code on the fly for macOS and iOS as you design and iterate on your particle effects
Besher Al Maleh
Flawless iOS
You don't (always) need [weak self]
এই আর্টিকেলে, আমরা Swift ক্লোজারের ভিতরে weak self নিয়ে কথা বলব রিটেইন সাইকেল এড়াতে এবং যেসব কেসে এটি সেলফকে weakly ক্যাপচার করা প্রয়োজন হয় বা নয় সেগুলো এক্সপ্লোর করব।
Besher Al Maleh
Flawless iOS
আরও পড়ুন:
Introduction
Explains how to implement concurrent code paths in an application.
Concurrent Programming: APIs and Challenges · objc.io
objc.io publishes books on advanced techniques and practices for iOS and OS X development
Florian Kugler
Low-Level Concurrency APIs · objc.io
objc.io publishes books on advanced techniques and practices for iOS and OS X development
Daniel Eggert
https://khanlou.com/2016/04/the-GCD-handbook/
Concurrent vs serial queues in GCD
I'm struggling to fully understand the concurrent and serial queues in GCD. I have some issues and hoping someone can answer me clearly and at the point.
I'm reading that serial queues are created...
Bogdan Alexandru
Stack Overflow
WWDC ভিডিও:
Modernizing Grand Central Dispatch Usage - WWDC 2017 - Videos - Apple Developer
macOS 10.13 and iOS 11 have reinvented how Grand Central Dispatch and the Darwin kernel collaborate, enabling your applications to run...
Apple Developer
Building Responsive and Efficient Apps with GCD - WWDC 2015 - Videos - Apple Developer
watchOS and iOS Multitasking place increased demands on your application's efficiency and responsiveness. With expert guidance from the...
Apple Developer