iOS 死锁

来源:互联网 发布:中国gdp季度数据 编辑:程序博客网 时间:2024/06/03 16:38
在使用dispatch_sync时,一定要防止deadlock发生。
如下:

- (void)viewDidLoad

{

    [superviewDidLoad];

    

    dispatch_queue_tqueue1 = dispatch_get_main_queue();

    dispatch_sync(queue1,^{NSLog(@"222 Hello?");});

    

    NSLog(@"aaaaaaa");

}


记过发现:主线程已经死锁了,始终不会打印log:aaaaaaaa,为什么呢?

就是说,这段代码是在主线程(viewdidload)中添加的,主线程执行到这里的时候,开始执行blocktask,他就会阻塞主线程, 直到queue完成了你给的task,但queue要完成你给的task,因为queueFIFO的,意味着要完成之前的任务,才有机会执行你刚才给的task, 相当于当前线程等待queue里面所有任务执行完毕,因此导致死锁。所以这句话不能在当前queue的任务代码里面调用,所以在使用dispatch_sync一定要特别注意,要问下自己,为什么要用它,在实例代码中,完全没必要,这是属于没事找事型的。


下面改成:

- (void)viewDidLoad

{

    [superviewDidLoad];

    

    dispatch_queue_tqueue1 = dispatch_get_main_queue();

    dispatch_async(queue1,^{NSLog(@"222 Hello?");});

    

    NSLog(@"aaaaaaa");

}


会发现:先输出log:aaaaaaaa
再输出log:  222 Hello?

可以再次验证即使是并行队列也是按照FIFO顺序执行

GCD编程的核心就是dispatch队列,dispatch block的执行最终都会放进某个队列中去进行,它类似NSOperationQueue但更复杂也更强大,并且可以嵌套使用。所以说,结合block实现的GCD,把函数闭包(Closure)的特性发挥得淋漓尽致。

 

dispatch队列的生成可以有这几种方式:

1. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial"DISPATCH_QUEUE_SERIAL); //生成一个串行队列,队列中的block按照先进先出(FIFO)的顺序去执行,实际上为单线程执行。第一个参数是队列的名称,在调试程序时会非常有用,所有尽量不要重名了。

2. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent"DISPATCH_QUEUE_CONCURRENT); //生成一个并发执行队列,block被分发到多个线程去执行

3. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //获得程序进程缺省产生的并发队列,可设定优先级来选择高、中、低三个优先级队列。由于是系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。需要注意的是,三个队列不代表三个线程,可能会有更多的线程。并发队列可以根据实际情况来自动产生合理的线程数,也可理解为dispatch队列实现了一个线程池的管理,对于程序逻辑是透明的。

官网文档解释说共有三个并发队列,但实际还有一个更低优先级的队列,设置优先级为DISPATCH_QUEUE_PRIORITY_BACKGROUND。Xcode调试时可以观察到正在使用的各个dispatch队列。

4. dispatch_queue_t queue = dispatch_get_main_queue(); //获得主线程的dispatch队列,实际是一个串行队列。同样无法控制主线程dispatch队列的执行继续或中断。

接下来我们可以使用dispatch_async或dispatch_sync函数来加载需要运行的block。

dispatch_async(queue, ^{

  //block具体代码

}); //异步执行block,函数立即返回

dispatch_sync(queue, ^{

  //block具体代码

}); //同步执行block,函数不返回,一直等到block执行完毕。编译器会根据实际情况优化代码,所以有时候你会发现block其实还在当前线程上执行,并没用产生新线程。

实际编程经验告诉我们,尽可能避免使用dispatch_sync,嵌套使用时还容易引起程序死锁。

如果queue1是一个串行队列的话,这段代码立即产生死锁:

   dispatch_sync(queue1, ^{

      dispatch_sync(queue1, ^{

    ......

  });

  ......

 });

不妨思考下,为什么下面代码也肯定死锁:

dispatch_sync(dispatch_get_main_queue(), ^{

  ......

}); 

 

那实际运用中,一般可以用dispatch这样来写,常见的网络请求数据多线程执行模型:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

  //子线程中开始网络请求数据

  //更新数据模型

  dispatch_sync(dispatch_get_main_queue(), ^{

    //在主线程中更新UI代码

  });

});

程序的后台运行和UI更新代码紧凑,代码逻辑一目了然。

 

dispatch队列是线程安全的,可以利用串行队列实现锁的功能。比如多线程写同一数据库,需要保持写入的顺序和每次写入的完整性,简单地利用串行队列即可实现:

dispatch_queue_t queue1 = dispatch_queue_create("com.dispatch.writedb"DISPATCH_QUEUE_SERIAL);

- (void)writeDB:(NSData *)data

{

  dispatch_async(queue1, ^{

    //write database

  });

} 

下一次调用writeDB:必须等到上次调用完成后才能进行,保证writeDB:方法是线程安全的。 

 

dispatch队列还实现其它一些常用函数,包括:

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t)); //重复执行block,需要注意的是这个方法是同步返回,也就是说等到所有block执行完毕才返回,如需异步返回则嵌套在dispatch_async中来使用。多个block的运行是否并发或串行执行也依赖queue的是否并发或串行。

void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); //这个函数可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行。

void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block); //同上,除了它是同步返回函数

void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); //延迟执行block

最后再来看看dispatch队列的一个很有特色的函数:

void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);

它会把需要执行的任务对象指定到不同的队列中去处理,这个任务对象可以是dispatch队列,也可以是dispatch源(以后博文会介绍)。而且这个过程可以是动态的,可以实现队列的动态调度管理等等。比如说有两个队列dispatchA和dispatchB,这时把dispatchA指派到dispatchB:

dispatch_set_target_queue(dispatchA, dispatchB);

那么dispatchA上还未运行的block会在dispatchB上运行。这时如果暂停dispatchA运行:

dispatch_suspend(dispatchA);

则只会暂停dispatchA上原来的block的执行,dispatchB的block则不受影响。而如果暂停dispatchB的运行,则会暂停dispatchA的运行。

这里只简单举个例子,说明dispatch队列运行的灵活性,在实际应用中你会逐步发掘出它的潜力。

dispatch队列不支持cancel(取消),没有实现dispatch_cancel()函数,不像NSOperationQueue,不得不说这是个小小的缺憾。

        前面我们说了block中提到它用于多线程,而gcd则是其用于多线程的典型。gcd其全称(Grand Central Dispatch)

      那到底什么叫gcd,官方的解释如下:

      Grand Central Dispatch (GCD) comprises language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in iOS and OS X

     翻译:

     Grand Central Dispatch(GCD包括语言的特点运行库和系统的完善提供系统的全面的改进支持并行执行代码在多核硬件在iOS和OS X。

     

     ios里主要用到其的就是关于队列的管理和调度,说到队列这里有三种队列

GCD provides and manages FIFO queues to which your application can submit tasks in the form of block objects. Blocks submitted to dispatch queues are executed on a pool of threads fully managed by the system. No guarantee is made as to the thread on which a task executes. GCD offers three kinds of queues:

  • Main: tasks execute serially on your application’s main thread

  • Concurrent: tasks are dequeued in FIFO order, but run concurrently and can finish in any order.

  • Serial: tasks execute one at a time in FIFO order

The main queue is automatically created by the system and associated with your application’s main thread. Your application uses one (and only one) of the following three approaches to invoke blocks submitted to the main queue:

  • Calling dispatch_main

  • Calling UIApplicationMain (iOS) or NSApplicationMain (OS X)

  • Using a CFRunLoopRef on the main thread

Use concurrent queues to execute large numbers of tasks concurrently. GCD automatically creates three concurrent dispatch queues that are global to your application and are differentiated only by their priority level. Your application requests these queues using the dispatch_get_global_queue function. Because these concurrent queues are global to your application, you do not need to retain and release them; retain and release calls for them are ignored. In OS X v10.7 and later, you can also create additional concurrent queues for use in your own code modules.

Use serial queues to ensure that tasks to execute in a predictable order. It’s a good practice to identify a specific purpose for each serial queue, such as protecting a resource or synchronizing key processes. Your application must explicitly create and manage serial queues. It can create as many of them as necessary, but should avoid using them instead of concurrent queues just to execute many tasks simultaneously.

Important: GCD is a C level API; it does not catch exceptions generated by higher level languages. Your application must catch all exceptions before returning from a block submitted to a dispatch queue.

  • dispatch_async
  • dispatch_async_f
  • dispatch_sync
  • dispatch_sync_f
  • dispatch_after
  • dispatch_after_f
  • dispatch_apply
  • dispatch_apply_f
  • dispatch_once

   翻译过来大概是说

     GCD提供和管理FIFO(First In First Out)队列您的应用程序可以在块对象的形式提交的任务调度队列在系统管理的线程池中执行。不能确保在哪个线程上执行任务GCD提供三种队列

         Main(全局的可用的串行队列):在主线程中串行执行任务

       Concurrent(并发队列):队列按照先进先出的顺序同时运行可以以任何顺序完成

      Serial(串行队列不过是私有):在任意时候以先进先出的顺序执行

     主要队列自动创建的系统和应用程序的主线程关联你的应用程序使用一个只有一个)调用提交的主要队列三块的方法

   先呼叫dispatch_main,也就是从子线程跳出到主线程

   再呼叫main.h中的UIApplicationMain

   最后调用主线程中的CFRunLoopRef

    

    使用并行队列执行大量的任务的同时GCD自动创建三个并发调度队列是全局应用程序只有它们的优先级区分您的应用程序请求这些队列使用dispatch_get_global_queue功能因为这些并行的队列是覆盖到您的应用程序您不需要保留和释放retain和release,他们被忽略在OS X v10.7或以后还可以创建您自己的代码模块使用额外的并发队列

    

      使用串行队列来确保任务按一定顺序执行这是一个很好的实践确定每个串行队列特定目的,如保护资源或同步的关键过程您的应用程序必须显式地创建和管理串行队列可以创建必要的许多人应避免使用它们而不是并发队列执行许多任务同时

   

   GCD是一个C级API它不抓捕的更高水平的语言产生异常你的应用程序必须提交给调度队列块之前返回所有异常

  

 

  • dispatch_async
  • dispatch_async_f比较,它们之音的区别就是多了一个_f,再就是用_f多了一个参数,其它的好像没什么区别,官网上也没查到

 其用法举个例子如:
 
 

    //这里用的是串行队列

    dispatch_queue_t que=dispatch_queue_create("123",NULL);

    //异步执行私有调度队列

    dispatch_async(que, ^{

    

        //这里是当上面的内容执行完马上执行,即同步执行

        dispatch_sync(dispatch_get_main_queue(), ^{

        

        });

          //这里不确定什么时候能执行即异步中的异步

//        dispatch_async(dispatch_get_main_queue(), ^{

//        

//        });

    });

    //释放

    dispatch_release(que);


    //这里是异步执行并发行队列

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{

        

        //同步执行全局可用的串行队列

        dispatch_sync(dispatch_get_main_queue(), ^{

        

        

        });

    });

    上面是不是处处都包含着block
  有什么问题请多多指教

0 0
原创粉丝点击