Migrating a ThreadSafe class to use Modern Concurrency

I have a thread-safe class which uses synchronization technique with async writes and synchronous reads. I see that swift Actors offer thread safety the same way, hence, I am thinking of migrating it to use Modern Concurrency techniques.

I want to migrate class A, which performs its reads synchronously and for the writes, it sends its synchronizationQueue to another class (a cache), to retrieve messages and then asynchronously perform writes. A code sample is below. While migrating to Actors/Modern concurrency, how can we perform this type of synchronization between classes as these are sharing a single queue to perform tasks?

class ThreadSafeClassA { 
   var messages = [String]()
   let messageCache: ThreadSafeClassB
   let synchronizationQueue = DispatchQueue("mySynchronizationQueue")

   init() {
      messageCache = ThreadSafeClassB(queue: synchronizationQueue)
   }

   func readFromMessages()  -> [String] {
      synchronizationQueue.sync {
       messages
     }
  }

 func writeToMessages() {
   messageCache.retrieveMessages(on: synchronizationQueue) {
      messages.append($0)
   }
 }
 }
class ThreadSafeClassB { 
   init(synchronizationQueue: DispatchQueue) {
   }

  func retriveMessages(on: DispatchQueue, completionHandler: @escaping (([String]) -> Void)) {
    synchronizationQueue.async {
      // calls completion handler
   }
  }
}

Multiple actor types using the same queue is done via custom actor executors: swift-evolution/proposals/0392-custom-actor-executors.md at main ยท apple/swift-evolution ยท GitHub

1 Like

Thank you @ktoso for your quick response. If these classes are being used for logging purposes, would it be a performance overhead to use SerialExecutors?

When in doubt, benchmark things :slight_smile:

1 Like

If your writes are really as simple as just appending to an array, or anything close to that complexity, you should consider just using a lock instead of a sync/async pattern. The DispatchQueue.sync you do in your read path is essentially just acquiring and releasing a relatively heavyweight lock. The DispatchQueue.async you do in your write path doesn't necessarily require acquiring a lock, but it does do allocation in the set-up, so the writes would have to be fairly expensive to make doing them asynchronously worthwhile.

4 Likes