সুইফট কনকারেন্সি

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

প্রসেস, থ্রেড এবং টাস্ক

কনকারেন্সি সম্পর্কে বিস্তারিত জানার আগে জানা যাক প্রসেস, থ্রেড এবং টাস্ক সম্পর্কে।

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

কনকারেন্সি এবং প্যারালালিজম এর পার্থক্যঃ

কনকারেন্সির সাথে আরেকটা টপিক্স জড়িত, আর তা হচ্ছে প্যারালালিজম। প্যারালালিজমেও একের অধিক টাস্ক এক সাথে প্রসেস করে। তবে কনকারেন্সির সাথে সামান্য পার্থক্য রয়েছে।

কনকারেন্সিঃ কনকারেন্সির ক্ষেত্রে সিপিউ একের অধিক টাস্ক একই সাথে প্রসেস করে। এখানে এই টাস্ক গুলো সিপিউর একই কোর ব্যবহার করতে পারে। মূলত কনটেক্সট সুইচিং এর মাধ্যমে একের অধিক টাস্ক এক সাথে প্রসেস করে।

প্যারালালিজমঃ বর্তমানের বেশির ভাগ সিপিউতে একের অধিক কোর থাকে। তো একের অধিক কোরে একের অধিক টাস্ক একই সাথে প্রসেস করাই হচ্ছে প্যারালালিজম।

কনকারেন্সি কোড লেখার কমপ্লেক্সিটি গুলোঃ

কনকারেন্ট কোড লিখলে যে সমস্যা গুলো পড়তে হয়, সেগুলো হচ্ছেঃ

  • Race Conditions: যখন একের অধিক টাস্ক একই শেয়ার্ড ডেটা এক্সেস করার চেষ্টা করে তখন এই রেইস কন্ডিশন দেখা দেয়।
  • Deadlocks: ডেডলক নাম থেকেই এই সমস্যাটা বুঝা যায়। যখন দুই বা তার অধিক প্রসেস একটা আরেকটার কাজ শেষ হওয়ার জন্য অপেক্ষা করে বসে থাকে, তখন এই সমস্যা তৈরি হয়।
  • Starvation: একটা প্রসেস সম্পূর্ণ করতে যে পরিমাণ রিসোর্স লাগবে, তা না থাকায় ঐ প্রসেস এক্সিকিউশ করতে না পারাই হচ্ছে স্টারভেশন।

স্ট্র্যাকচার্ড কনকারেন্সি

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

সুইফটের কনারেন্সি মডেল

সুইফটের কঙ্কারেন্সি মডেল গুলো মূলত থ্রেডের উপর ভিত্তি করে কাজ করে। আমরা সরাসরি থ্রেডের সাথে ইন্টারেক্ট করি না, সুইফট নিজে এসব ম্যানেজ করে। কনকারেন্ট কোড লেখার জন্য সুইফটের মূল কনসেফট গুলো হচ্ছেঃ

  1. Async/Await: async/await কিওয়ার্ড ব্যবহার করে আমরা অ্যাসিঙ্ক্রোনাস কোড লিখতে পারি যা দেখতে মূলত সিঙ্ক্রোনাস কোডের মত। ফলে এই কোড গুলো সহজে পড়া যায় এবং ম্যানেজ করা যায়। একটা ফাংশন অথবা মেথডে async কিওয়ার্ড দিয়ে মার্ক করে আমরা বলে দেই যে এই ফাংশনটা অ্যাসিঙ্ক্রোনাস কোড রান করতে পারে। অ্যাসিঙ্ক্রোনাস ফাংশন বা মেথড কল করার জন্য await কিওয়ার্ড ব্যবহার করতে হয়।
  2. Tasks and Task Groups: টাস্ক মূলত অ্যাসিঙ্ক্রোনাস প্রসেসের ইউনিট। টাস্ক গ্রুপের মাধ্যমে আমরা একের অধিক কনকারেন্ট টাস্ক তৈরি করতে পারি।
  3. Actors: এক্টরের মাধ্যমে ডেটা আইসোলেশনের কাজ করা হয়।

অ্যাসিঙ্ক্রোনাস ফাংশন লেখা

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

async কিওয়ার্ডঃ কোন ফাংশন বা মেথড অ্যাসিঙ্ক্রোনাস হলে async কিওয়ার্ড দিয়ে বলে দিতে হয়। যেমন এই সিঙ্ক্রোনাস ফাংশনটি দেখিঃ

func hello() ->String{
   return "Hello World!"
}

উপরের ফাংশনের অ্যাসিঙ্ক্রোনাস রূপ হবে এমনঃ

func hello() async ->String{
   return "Hello World!"
}

কোন প্যারামিটার থাকলে প্যারামিটার গুলোর পরে async কিওয়ার্ড ব্যবহার করতে হয়ঃ

func add(a: Int , b:Int) async -> String{
  var sum = a + b
  return "result is \(sum)"
}

অ্যাসিঙ্ক্রোনাস ফাংশন যদি কোন এরর দিতে পারে মনে করি, তাহলে আমরা রেগুলার সিঙ্ক্রোনাস ফাংশনের মত throw কিওয়ার্ড ব্যবহার করতে পারি। আর তা async কিওয়ার্ডের পর ব্যবহার করতে হয়। যেমনঃ

func hello() async throws ->String{
   return "Hello World!"
}

যদিও এই অ্যাসিঙ্ক্রোনাস ফাংশনের ভেতর আমরা সিঙ্ক্রোনাস কোডই লিখিছি। এবার একটা অ্যাসিঙ্ক্রোনাস কোডের ডেমো দেখা যাকঃ

func fetchDataFromServer() async throws -> String {
    // Simulate a network delay
    try await Task.sleep(nanoseconds: 2 * 1_000_000_000) // 2 seconds
    
    // Return fetched data
    return "Fetched Data from Server"
}

let result = try await fetchDataFromServer()
print(result)

await কিওয়ার্ডঃ আমরা যখন কোন অ্যাসিঙ্ক্রোনাস ফাংশন অথবা মেথড কল করব, তখন await কিওয়ার্ড ব্যবহার করতে হয়। await কিওয়ার্ড দিয়ে আমরা বলে দেই যে ফাংশনটির কাজ শেষ হওয়া পর্যন্ত অপেক্ষা করতে। একটা উদাহরণ দেখা যাকঃ

func fetchDataFromServer() async throws -> String {
    // Simulate a network delay
    try await Task.sleep(nanoseconds: 2 * 1_000_000_000) // 2 seconds
    // Return fetched data
    return "Fetched Data from Server"
}

let result = try await fetchDataFromServer()
print(result)

সাধারণত কোন অ্যাসিংক্রোনাস ফাংশন বা মেথড কল করার সময় do catch ব্লক ব্যবহার করা হয়। যেন কোন এরর থ্রো করলে তা হ্যান্ডেল করা যায়। এভাবেঃ

do {
    let result = try await fetchDataFromServer()
    print(result)
} catch {
    print("Fetching data failed with error \(error)")
}

স্ট্র্যাকচার্ড কনকারেন্ট কোড লেখা

এর আগে আমরা অ্যাসিঙ্ক্রোনাস ফাংশন সম্পর্কে জেনেছি। যেটা কনকারেন্সির একটা অংশ। আরেকটা অংশ হচ্ছে টাস্ক। টাস্ককে বলা যায় অ্যাসিঙ্ক্রোনাস প্রসেসের ইউনিট। বলা যায় একটা টাস্ক আরেকটা টাস্কের উপর ডিপেন্ডেন্ড না হয়ে রান করতে পারবে। ফলে একের অধিক টাস্ক আমরা প্যারালালি রান করতে পারি। আর একের অধিক টাস্ক এভাবে লেখাকেই বলা হয় স্ট্র্যাকচার্ড কনকারেন্সি। async let, টাস্ক গ্রুপ এবং টাস্ক এপিআই এর স্ট্র্যাকচার্ড কনকারেন্সি কোড লেখা হয়।

async let: প্যারালালি একের অধিক অ্যাসিঙ্ক্রোনাস কোড রান করার জন্য async let কিওয়ার্ড ব্যবহার করা হয়। যেমনঃ

async let firstResult = fetchData()
async let secondResult = fetchData()
 
let results = try await (firstResult, secondResult)
print("Results: \(results)")

টাস্ক গ্রুপঃ ডাইন্যামিক্যালি একের অধিক টাস্ক রান করার জন্য টাস্ক গ্রুপ তৈরি করতে পারি। যেমনঃ

func fetchAllData() async throws -> [Data] {
    var results: [Data] = []
     
    try await withThrowingTaskGroup(of: Data.self) { group in
        for _ in 0..<5 {
            group.addTask {
                return try await fetchData()
            }
        }
         
        for try await result in group {
            results.append(result)
        }
    }
     
    return results
}

এখানে আমরা 5টা টাস্ক যুক্ত করেছি। রিয়েল কোডে এখানে সাধারণত ডাইনামিক ডেটার সংখ্যা হবে। আর তার উপর ভিত্তি করে ডাইন্যামিক্যালি চাইল্ড টাস্ক তৈরি করে নিবে।

টাস্ক কন্ট্রোল

কনকারেন্ট টাস্ক গুলো কন্ট্রোল করার জন্য সুইফটে ট্রাস্ক ক্যান্সেলেশন, টাস্ক প্রায়োরিটি ইত্যাদি রয়েছে।

টাস্ক ক্যান্সেলেশনঃ অ্যাসিঙ্ক্রোনাস টাস্ক যদি অনেক সময় নেয়, আমরা চাইলে ঐ টাস্ক ক্যান্সেল করতে পারি। আর কোন টাস্ক ক্যান্সেল করেছে কিনা, তা হ্যান্ডেল করা যায় Task.isCancelled প্রোপার্টির মাধ্যমে। একটা সিম্পল উদাহরণঃ

func fetchDataWithCancellation() async throws -> Data {
    if Task.isCancelled {
        throw CancellationError()
    }
     
    // Simulate network request
    return Data()
}
 
let task = Task {
    try await fetchDataWithCancellation()
}
 
// Cancel the task
task.cancel()

টাস্ক প্রায়োরিটিঃ কিছু টাস্ক কমপ্লিট করা বেশি গুরুত্বপূর্ণ হতে পারে। আর তাই আমরা টাস্কের প্রায়োরিটি সেট করে দিতে পারি। যেমনঃ

let task = Task(priority: .high) {
    try await fetchData()
}

এক্টর / Actors

আমরা জেনেছি টাস্ক গুলো ইন্ডিপেন্ডলি রান হতে পারে। একের অধিক টাস্ক প্যারালালি রান হতে পারে। কিন্তু কিছু কিছু ক্ষেত্রে একের অধিক টাস্ক একই রিসোর্স শেয়ার করতে পারে। আর এই শেয়ার্ড রিসোর্স নিরাপদে এক্সেস করার জন্য এক্টর ব্যবহার করা হয়। এক্টর মূলত ডেটা রেইস সমস্যার সমাধান করে।

এক্টর তৈরিঃ এক্টর তৈরির জন্য actor কিওয়ার্ড ব্যবহার করা হয়। যেমনঃ

actor DataManager {
    private var data: Data?
    func updateData(newData: Data) {
        self.data = newData
    }
     
    func getData() -> Data? {
        return self.data
    }
}

এক্টরের মেথড গুলো এক্সেস করাঃ এক্টরের মেথড এক্সেস করার ক্ষেত্রে await কিওয়ার্ড ব্যবহার করতে হয়।

let dataManager = DataManager()
 
await dataManager.updateData(newData: Data())
let data = await dataManager.getData()
print("Data: \(data)")

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

সুইফট নিয়ে অন্যান্য লেখা গুলোঃ বাংলায় সুইফট প্রোগ্রামিং

Leave a Reply