Is this really different from using a transaction with separate read (using an update lock) and update statements, and looping over that in the same way from the application until the count is 0?
Yes. That incurs many of the consequences of doing them all in one query (particularly lock contention and dead tuple bloat), and takes longer — making those consequences worse than doing it in a single query.