Effective use of libdispatch

( Note: the author of the original material is Thomas @tclementdev, a github and twitter user. The first-person narrative used by the author is saved in the translation below. )



I think that most developers use libdispatch inefficiently because of how it was introduced to the community, as well as due to confusing documentation and APIs. I came to this thought after reading the discussion of “concurrency” in the Swift development mailing list (swift-evolution). Messages from Pierre Habouzit (Pierre Habouzit - is engaged in support of libdispatch in Apple) are especially enlightened:





He also has many tweets on this topic:





Made by me:





Take a look at all the calls to dispatch_async () in your code and ask yourself: is the task you submit with this call really worth the context switch. In most cases, locking is probably the best choice.



As soon as you start using and reusing queues (execution contexts) from a pre-designed set, there will be a danger of deadlocks. Danger arises when sending tasks to these queues using dispatch_sync (). This usually happens when queues are used for thread safety. Therefore, once again: the solution is to use locking mechanisms and use dispatch_async () only when you need to switch to another execution context.



I personally saw huge performance improvements from following these guidelines.

(in highly loaded programs). This is a new approach, but worth it.



More links



The program should have very few queues using the global pool





( Note perev.: Reading the last link, could not resist and transferred a piece from the middle of the correspondence of Pierre Habuzit with Chris Lattner. Below is one of the answers of Pierre Habuzit in 039420.html )

<...>

I understand that it’s hard for me to convey my point of view, because I am not a guy in language architecture, I am a guy in system architecture. And I definitely don’t understand Actors enough to decide how to integrate them into the OS. But for me, returning to the database example, the Actor-Database-Data, or the Actor-Network-Interface from the earlier correspondence, are different from, say, this SQL query or this network query. The first are the entities that the OS should know about in the kernel. While a SQL query or a network query are just actors queued for execution first. In other words, these top-level actors are different because they are top-level, directly on top of the kernel / low-level runtime. And this is the essence that the core should be able to reason about. This makes them great.



There are 2 types of queues and corresponding API level in the dispatch library:

  • global queues that are not queues like the others. And in reality, they are just an abstraction over the thread pool.
  • all other queues that you can set as target one for another as you want.


Today it has become clear that this was a mistake and that there should be 3 types of queues:



  • global queues, which are not real queues, but represent which family of system attributes your execution context requires (mostly priorities). And we must prohibit sending tasks directly to these queues.
  • lower queues (which GCD has tracked in recent years and calls "bases" in the source code ( it seems that the source code of GCD itself is referring to - approx. transl. ). The lower queues are known to the kernel when they have tasks.
  • any other "internal" queues that the kernel does not know about at all.


In the dispatch development team, we regret every passing day that the difference between the second and third group of queues was not initially made clear in the API.



I like to call the second group “execution contexts,” but I can understand why you want to call them Actors. This is perhaps more consistent (and the GCD did the same, presenting both this and that as a queue). Such top-level “Actors” should be few in number because if they all become active at the same time, they will need the same number of threads in the process. And this is not a resource that can be scaled. That is why it is important to distinguish between them. And, as we discuss, they are also commonly used to protect a shared state, resource, or the like. It may not be possible to do this using internal actors.

<...>





Start with sequential execution.





Do not use global queues





Beware of competitive lines





Do not use async calls to protect shared state





Do not use async calls for small tasks





Some classes / libraries should just be synchronous





Fighting parallel tasks among themselves is a productivity killer





To avoid deadlocks, use locking mechanisms when you need to protect a shared state





Do not use semaphores to wait for an asynchronous task





The NSOperation API has some serious pitfalls that can lead to performance degradation.





Avoid Micro Performance Tests





Resources are not unlimited





About dispatch_async_and_wait ()





Using 3-4 cores is not easy





Many performance improvements in iOS 12 have been achieved with single-threaded daemons






All Articles