I have come across a very interesting problem related to queue dead lock in iOS. Any way to avoid this?
Consider this:
Create a custom serial queue.
Dispatch some task (#1) on this serial queue asynchronously.
This async task (#1) on dispatches some task (#2) onto main queue sync.
Main queue dispatches some task (#3) onto serial queue sync.
Result - DeadLock
Below is the sample code for this.
Since self.opQueue is a serial queue, task#3 will not start till task#1 completes.
Since task#1 is calling main queue sync, so it will never complete till main queue completes task#2.
Since main queue is waiting for opQueue to finish task#3, and opQueue is waiting for main queue to finish task#2 there is a deadlock.
#import "ViewController.h"
#interface ViewController ()
#property(nonatomic,strong) dispatch_queue_t opQueue;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_queue_create("com.tarun.sqqueue",
DISPATCH_QUEUE_SERIAL);
self.opQueue = queue;
[self performOperations];
}
/// 1. Dispatch on serial queue async.
/// 2. This async task on serial queue dispatchs some task onto
/// main queue sync.
/// 3. Main queue dispatched some task onto serial queue sync.
/// 4. Result - DeadLock
- (void)performOperations {
/// task#1: Dispatch task on the serial Queue Asynchronously.
/// So this is not blocking.
dispatch_async(self.opQueue, ^{
for (int i = 1; i<=100; i++) {
NSLog(#"%d. Async on Serial Queue from Main Queue.",i);
}
/// task#2: Dispatching task on main queue synchronously from serial
/// queue.So this queue will wait till main queue executes this task.(Blocking)
dispatch_sync(dispatch_get_main_queue(), ^{
for (int i = 1; i<=100; i++) {
NSLog(#"%d. Sync on main queue from Serial Queue.",i);
}
});
});
/// task#3: Dispatching task on swrial queue synchronously from main
/// queue.So main queue will wait till serial queue executes this task. (Blocking)
dispatch_sync(self.opQueue, ^{
for (int i = 1; i<=100; i++) {
NSLog(#"%d. Sync on Serial Queue From Main Queue.",i);
}
});
/// Since self.opQueue is a serial queue, task#3 will not start till task#1 completes.
/// Since task#1 is calling main queue sync,
/// so it will never complete till main queue completes task#2.
/// Since main queue is waiting for opQueue to finish task#3, and opQueue is waiting for main queue to finish task#2 there is a deadlock.
NSLog(#"Back to main queue");
}
#end
From Apple
Important: You should never call the dispatch_sync or dispatch_sync_f
function from a task that is executing in the same queue that you are
planning to pass to the function. This is particularly important for
serial queues, which are guaranteed to deadlock, but should also be
avoided for concurrent queues.
Its a guaranteed dead lock. The only way to avoid this is to not implement it this way.
Related
I have to protect a critical section of my code.
I don't want the caller to be blocked by the function that can be time consuming so I'm creating a serial queue with background qos and then dispatching asynchronously:
private let someQueue = DispatchQueue(label: "\(type(of: self)).someQueue", qos: .background)
func doSomething() {
self.someQueue.async {
//critical section
}
}
For my understanding, the function will directly return on the calling thread without blocking.
I've also seen somewhere dispatching first asynchronously on the global queue, the synchronously on a serial queue:
private let someQueue2 = DispatchQueue(label: "\(type(of: self)).someQueue2")
func doSomething() {
DispatchQueue.global(qos: .background).async {
self.someQueue2.sync {
//critical section
}
}
}
What's the difference between the two approaches?
Which is the right approach?
In the first approach, the calling thread is not blocked and the task (critical section) passed in the async block will be executed in background.
In the second approach, the calling thread is not blocked, but the "background" thread will be waiting for the sync block (critical section) execution which is executed by another thread.
I don't know what you do in your critical section, but it seems first approach seems the best one. Note that background qos is quite slow, maybe use default qos for your queue, unless you know what you are doing. Also note that convention wants that you use bundle identifier as label for your queue. So something like this:
private let someQueue = DispatchQueue(label: "\(Bundle.main.bundleIdentifier ?? "").\(type(of: self)).someQueue")
Whats the difference between
dispatch_async_f
and
dispatch_async
in ios?
The main reason behind using this async blocks is to have the background task.
dispatch_async:
By using this block you can run a code block asynchronously
Eg.
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//Background Thread
dispatch_async(dispatch_get_main_queue(), ^(void){
//Run UI Updates can be done only on main thread
});
});
dispatch_async_f:
Irrespective of the block in async task you can put your custom function to be performed in the background.
Eg:
void mainFunc(void) {} // your function
void callingFuncForAsyncTask(void*) { mainFunc(); } // new function which takes arguments for calling inside async_f
dispatch_async_f(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), 0, &callingFuncForAsyncTask);
For more info and parameter details kindly refer:
https://developer.apple.com/reference/dispatch/1452834-dispatch_async_f
dispatch_async -
Submits a block for asynchronous execution on a dispatch queue and returns immediately.
This function is the fundamental mechanism for submitting blocks to a dispatch queue. Calls to this function always return immediately after the block has been submitted and never wait for the block to be invoked.
Declaration : void dispatch_async( dispatch_queue_t queue, dispatch_block_t block);
Params :
queue - the queue on which block is to be submitted & can’t be NULL.
block - block to be submitted to the target queue & can’t be NULL.
dispatch_async_f -
Submits a application defined block for async execution on a dispatch queue & returns immediately.
This function is the fundamental mechanism for submitting application-defined functions to a dispatch queue. Calls to this function always return immediately after the function has been submitted and never wait for it to be invoked.
Declaration : void dispatch_async_f( dispatch_queue_t queue, void *context, dispatch_function_t work);
Params :
queue - the queue on which block is to be submitted & can’t be NULL.
work - application defined function to be invoked on target dispatch queue 7 can’t be NULL.
So I'm putting 10 tasks on a concurrent queue using dispatch_async. They do not block the next task, and gets processed in order. My UI is responsive.
for (int i = 0; i < 10; i++) {
dispatch_async(concurrencyQueue, ^() {
NSLog(#"..calling insertion method to insert record %d", i);
dispatch_sync(serialQueue, ^() {
//this is to simulate writing to database
NSLog(#"----------START %d---------", i);
[NSThread sleepForTimeInterval:1.0f];
NSLog(#"--------FINISHED %d--------", i);
});
});
}
Within each task, we simulate a write to database with a "1 sec sleep" on a serial Queue via dispatch_sync.
I always thought dispatch_sync blocks everyone, and syncs its tasks because that's how it behaves when I use it individually. However, in this situation, it does not block the main thread. Instead, it runs beautifully in the background like I want it.
Is it because whatever thread is associated with the queue is being affected?
For example, the main thread is executing the concurrent queue via dispatch_async and that's why it is not blocked.
The dispatch_sync only syncs and blocks against the background thread that's working on the concurrent queue. Hence, the dispatch_sync is associated with the background thread, thus never affecting my UI main thread.
Is my thinking correct?
thank you!
You never block the main thread because your code is running on either the threads of the concurrencyQueue or the thread of the serialQueue. None of those are the main thread.
All of the calls to sleep happen one by one on the thread of the serialQueue. So it is the thread of the serialQueue that is blocked.
However, since you dispatch to the serialQueue using dispatch_sync, you are also blocking each thread of the concurrent queue. This would be better pictured if you add another NSLog after the call to dispatch_sync.
for (int i = 0; i < 10; i++) {
dispatch_async(concurrencyQueue, ^() {
NSLog(#"..calling insertion method to insert record %d", i);
dispatch_sync(serialQueue, ^() {
//this is to simulate writing to database
NSLog(#"----------START %d---------", i);
[NSThread sleepForTimeInterval:1.0f];
NSLog(#"--------FINISHED %d--------", i);
});
NSLog(#"..called insertion method to insert record %d", i);
});
}
That 2nd NSLog after the dispatch_sync will show you better how the dispatch_sync is affecting the calls to dispatch_async.
Yes, you are right. dispatch_sync() blocks only the thread the queue is running on.
the apple document says:(concurrencyProgrammingGuide,page49)
Important: You should never call the dispatch_sync or dispatch_sync_f function from a task that is executing in the same queue that you are planning to pass to the function. This is particularly important for serial queues, which are guaranteed to deadlock, but should also be avoided for concurrent queues.
but the code here not cause a deadlock, since i have ran it many times:
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^(){
NSLog(#"in outer queue: %#", [NSThread currentThread]);
dispatch_sync(concurrentQueue, ^(){
NSLog(#"do someting thread: %#", [NSThread currentThread]);
});
});
Yet,we all know,in main thread context, if we execute the code below,it will cause deadlock in main thread. so i am confused why calling dispatch_sync in the same thread, one not deadlock(the code above), the other opposite(the code below)?
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(#"________update__UI");
});
dispatch_get_global_queue() returns a system-defined global concurrent queue.
Serial Dispatch Queue (the main queue and user created queues with default flag) uses only just one thread. Concurrent Dispatch Queue (global queue, created queues with concurrent flag) uses multiple threads (aka thread pool). The number of thread is vary with system, situation.
Take a look at the following code.
dispatch_async(queue, ^(){
/* Task 1 */
dispatch_sync(queue, ^(){
/* Task 2 */
});
});
Task 1 and Task 2 should be executed on the same order as it was queued. Thus, Task 1 is executed, and then Task 2.
On Serial Dispatch Queue, dispatch_sync have to wait in order to execute Task 2 on the thread that is executing Task 1 right now. DEADLOCK.
On Concurrent Dispatch Queue, dispatch_sync usually doesn't need to wait to execute Task 2 on a thread in the thread pool. But the number of thread in the thread pool is not unlimited actually, sometimes dispatch_sync have to wait until some other task finished. That's why "but should also be avoided for concurrent queues". dispatch_sync is also highly optimized, it uses the same thread of Task 1 for Task 2 in some situation.
EDITED
Thus, dispatch_sync a block means the exactly same as ordinary block(function) call. In this case, DEADLOCK never happened.
EDITED
Test code.
#import <Foundation/Foundation.h>
void task2()
{
NSLog(#"task2: %#", [NSThread currentThread]);
}
void task1(dispatch_queue_t q)
{
NSLog(#"task1: %#", [NSThread currentThread]);
dispatch_sync(q, ^{
task2();
});
}
int main()
{
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
dispatch_async(q, ^{
task1(q);
});
dispatch_main();
return 0;
}
lldb log
(lldb) breakpoint set -l 6
(lldb) run
task1: <NSThread: 0x1001155a0>{number = 2, name = (null)}
task2: <NSThread: 0x1001155a0>{number = 2, name = (null)}
Process stopped
(lldb) bt
* thread #2: tid = 0x4dbcc, 0x0000000100000d34 a.out`task2 + 4 at a.m:5, queue = 'com.apple.root.default-qos', stop reason = breakpoint 1.1
* frame #0: 0x0000000100000d34 a.out`task2 + 4 at a.m:5
frame #1: 0x0000000100000dc5 a.out`__task1_block_invoke(.block_descriptor=<unavailable>) + 21 at a.m:12
frame #2: 0x00007fff8d6d6c13 libdispatch.dylib`_dispatch_client_callout + 8
frame #3: 0x00007fff8d6e19a1 libdispatch.dylib`_dispatch_sync_f_invoke + 39
frame #4: 0x0000000100000da3 a.out`task1(q=0x00007fff79749b40) + 67 at a.m:11
task1 function calls task2 function via libdispatch APIs but it almost the same as ordinary function call.
I have a really heavy task to perform, and I dont want to block the main thread. So I'm starting a separate Concurrent queue for it. There can be 4 instances of the said task.
-(dispatch_queue_t)getConcurrentQueue
{
if(concurrentQueue == nil)
{
concurrentQueue = dispatch_queue_create("com.myself.HeavyTask", DISPATCH_QUEUE_CONCURRENT);
}
return concurrentQueue;
}
Now to start the heavy task I have -
-(void)beginTask
{
//.....
//.....
__weak typeof(self) weakSelf = self;
dispatch_queue_t queue = [self getConcurrentQueue];
dispatch_async(queue, ^{
[weakSelf heavyTask];
});
}
Now the method heavyTask goes like this --
-(void)heavyTask
{
//...
dispatch_sync(dispatch_get_current_queue(), ^{
// Initialising code for heavy task
// This is the critical section. Only one of the 4 concurrent threads can enter this at a time
}
//....
while(condition)
{
// Perform meat of the task
}
//...
dispatch_sync(dispatch_get_current_queue(), ^{
// Teardown code. Freeing memory etc.
// This is also a critical section.
}
//...
}
The initialising code and teardown code uses some 3rd party C methods which are not thread safe. So making them thread safe is not the scope of the question.
Now I've put the "initialising code" and and the "teardown code" within -
dispatch_sync(dispatch_get_current_queue(), ^{
}
My code is crashing and I'm getting error messages stating that there is insufficient thread locking around the critical section code.
I read that dispatch_get_current_queue() is not safe, so I replaced it with concurrentQueue. I also tried replacing with dispatch_get_main_queue(). Still the code crashes, complaining about insufficient thread locking.
I know there is something wrong in my understanding of implementing critical section using GCD.
Can anyone clearly show me how to make my code work properly here??
Side question -- Can I use #synchronized { } blocks here??
There's a lot wrong with your code, including not adhering to naming conventions.
So basically, if you want to execute the same task concurrently with respect to each other, use the global concurrent queue to execute those tasks.
If you want to concurrently access shared resources from within these tasks (or from elsewhere), define a dedicated queue, say "sync_queue" where you exclusively access these resources. This "sync_queue" executes your "critical sections".
The "sync_queue" can be serial or concurrent.
If you use a serial queue, use dispatch_async(sync_queue, block) for write access and dispatch_sync(sync_queue, block) for read access to shared resources.
If you use a concurrent queue, use dispatch_barrier_async(sync_queue, block) for write access and dispatch_barrier_sync(sync_queue, block) for read access to shared resources.
Example:
// Read access using a serial sync_queue:
...
__block int counter;
dispatch_sync(sync_queue, ^{
counter = _counter;
});
// Write access using a serial sync_queue:
...
dispatch_async(sync_queue, ^{
_counter = counter;
});
// Read access using a concurrent sync_queue:
...
__block int counter;
dispatch_barrier_sync(sync_queue, ^{
counter = _counter;
});
// Write access using a concurrent sync_queue:
...
dispatch_barrier_async(sync_queue, ^{
_counter = counter;
});
Example for your "heavy task":
-(void)heavyTask
{
dispatch_barrier_async(sync_queue, ^{
// Initialize heavy task
...
// Continue with the task:
dispatch_async(dispatch_get_global_queue(0,0), ^{
BOOL condition = YES; // condition must be local to the block (it's not a shared resource!)
while(condition)
{
// Perform meat of the task
condition = ...;
}
dispatch_barrier_async(sync_queue, ^{
// Teardown code. Freeing memory etc.
// This is also a critical section.
...
}
});
}
}
You called it "getSerialQueue" but really you are creating a "concurrent" queue in it. Try to fix it substituting DISPATCH_QUEUE_CONCURRENT with DISPATCH_QUEUE_SERIAL in getSerialQueue.
Keep in mind that:
dispatch_sync means: I will wait here until this block finishes
dispatch_async means: I will not wait
This is not related to concurrent or serial. If two tasks in a concurrent queue call dispatch_sync(block), 'block' will be executed concurrently.
Hope this helps.