[swift-corelibs-dev] libdispatch roadmap and api addition proposal

Pierre Habouzit phabouzit at apple.com
Thu Dec 10 12:01:02 CST 2015

> On Dec 10, 2015, at 1:06 AM, Joakim Hassila via swift-corelibs-dev <swift-corelibs-dev at swift.org> wrote:
>> On 8 dec. 2015, at 17:07, Pierre Habouzit <phabouzit at apple.com> wrote:
>> My point is, adding API to dispatch is not something we do lightly. I’m not keen on an interface that only works for base queues. Mac OS and iOS code where dispatchy code is pervasive, more than 2 queue deep queues hierarchy is very common typically.
> Gotcha.
>>> That would indeed be a very interesting idea, the problem is that the thread using ‘dispatch_barrier_trysync’ is not returning to the pthread_workqueue pool to grab the next dispatch queue for processing, but is instead going back to block on a syscall (e.g. read() from a socket) - and even the latency to wake up a thread (as is commonly done now) with mutex/condition signaling is way too slow for the use case we have (thus the very ugly workaround with a spin thread for some deployments).
>>> <snip>
>>> Perhaps we have to live with the limited implementation we have for practical purposes, but I have the feeling that the behavior we are after would be useful for other use cases, perhaps the queue attribute suggested above could be another way of expressing it without introducing new dispatch API.
>> I completely agree with you, but I think that the way to address this is by making the thread pool smarter, not having the developper have to sprinkle his code with dispatch_barrier_trysync() where he feels like it. Using it properly require a deep understanding of the implementation of dispatch he’s using and changes on each platform / version combination. that’s not really the kind of interface we want to build.
>> “overcommit” is exactly the hint you’re after as far as the queue is concerned. It means “if I’m woken up, bring up a new thread provided it doesn’t blow up the system, no matter what”. So make your queue non overcommit by targetting it manually to dispatch_get_global_queue(0, 0) (that one isn’t overcommit), and make the thread pool smarter. That’s the right way to go and the design-compatible way to do it.
> Ok, this is interesting (and probably just points out my misunderstanding of the intended semantics of the overcommit flag) - the part with “doesn’t blow up the system” is actually not clear from the header files, it says:

on OS X, the “doesn’t blow up the system” part is, you still have less than 512 threads and enough threads allowed globally on that host, it’s not really a very strong limit.

> ++++++
> * The queue will create a new thread for invoking blocks, regardless of how
> * busy the computer is.
> ++++++
> The ‘regardless of how busy the computer is’ was also the implementation of overcommit queues in pwq, so we essentially banned the usage of them here internally as the semantics where not very usable for us, as a new thread was always created (and we do use a fairly large number of dispatch queues).
> If we would have semantics like:
> “overcommit” - "if I’m woken up, bring up a new thread provided it doesn’t blow up the system, no matter what” and the definition of “blowing up the system” is to not have more concurrent running threads than there are active cores
> “non overcommit” - “only bring up a new thread a provided that enough ‘pressure’ is applied” (to allow for essentially the ping pong you suggest)
> Then I think we can work with making the thread pool smarter and get desired behavior - will think a bit more about it (I think that some care would be required to not have essentially lost wakeups for the non overcommit variant in that case), but it feels like a possibly better way forward, thanks.
> Would such interpretation of the overcommit attribute semantics be reasonable? (we wouldn’t want to have a completely different view of it to keep the API behavior robust across platforms)

The overcommit intent was that “if I’m on a single core machine and I wake up that queue but I myself keep a thread busy, would the program livelock because that queue I just woke up wouldn’t wakeup a thread”. IOW, could I risk to be blocked forever if that queue didn’t run right away.

That’s the problem to keep in mind with overcommit, it’s the intent. I don’t think the current wq implementation on OS X is pretty smart about it, probably not as much as it should.

As long as you fix that issue (guarantee that overcommit queue provided you don’t have 109238192038 competing for your cores will get a thread in a relatively timely fashion) then I think it’s ok if specific platforms semantics vary a bit. It’s already the case with the pthread thread pool vs wq anyway.

>> If your thread block in read() then I would argue that it should use a READ dispatch source instead, that way, the source would get enqueued *after* your async and you can ping pong. Doing blocking read()s is not dispatchy at all and will cause you all sorts of problems like that one, because re-async doesn’t work for you.
> Here we are a bit living with legacy considerations, but perhaps one possible way we are discussing is if such threads could use the pwq API (at the tail end) to facilitate the behavior you suggest.
> I.e. pseudocode:
> f() // legacy non-dispatchy code
> {
>  read()                                  // get some new work
>  dispatch_async()                        // dispatch the work on a non-overcommit queue
>  pthread_workqueue_additem_np(q, f, ...) // repeat f(), switch between overcommit/nonovercommit target work queues as needed/desired, this thread could thus be stolen to process the above dispatch_async
> }

oh you’re not on a dispatchy thread, then yeah well, you’re more or less on your own. I would if you need that tweak the thread pool so that it always has one ready for you (IOW it never kills them all) so that the wake up is less expensive. But that’s clearly not a pattern we will try to optimize for because it’ll have bad effect on the library API surface for something that we prefer people try to embrace fully with time.

Here if you’re simulating dispatch this way though, you could have a source for the read still:

source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, NULL);
dispatch_source_set_event_handler_f(source, …, f);

If your code really looks like you’re describing that should naturally work, no?


More information about the swift-corelibs-dev mailing list