Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。

有两种任务执行队列,均遵循FIFO原则进行任务的执行:

  • 串行队列(Serial Dispatch Queue)
  • 并发队列(Concurrent Dispatch Queue)。

系统默认提供的:

  • 主线程串行队列(Main Dispatch Queue),默认所有代码都是放在主队列中由主线程执行。
  • 全局并发队列(Global Dispatch Queue),可以传入参数获取不同优先级的并发队列。 可自定义创建的:
  • let queue = DispatchQueue(label: "serial"),串行队列
  • let queue = DispatchQueue(label: "serial", attributes: .concurrent),并发队列

无论是串行还是并发,dispatch的方式都有两种syncasync,也就是同步和异步。结合两种队列,可以有四种组合:

  1. 并发 + 同步

    •   var param1 = 0
        let queue = DispatchQueue(label: "barrier", attributes: .concurrent)
        print(1)
        queue.sync {
            print("sync1: \(self.param1)")
        }
        queue.sync {
            print("sync2: \(self.param1)")
        }
        print(2)
        queue.sync {
            print("sync3: \(self.param1)")
        }
        queue.sync {
            print("sync4: \(self.param1)")
        }
        print(3)
      
        // 结果:由于同步无法开启新的线程,所以这种情况下所有代码都是同步等待地按顺序执行完。
        1
        sync1: 0
        sync2: 0
        2
        sync3: 0
        sync4: 0
        3
      
  2. 并发 + 异步

    •   var param1 = 0
        let queue = DispatchQueue(label: "barrier", attributes: .concurrent)
        print(1)
        queue.async {
            print("async1: \(self.param1)")
        }
        queue.async {
            print("async2: \(self.param1)")
        }
        print(2)
        queue.sync {
            print("sync3: \(self.param1)")
        }
        queue.async {
            print("async4: \(self.param1)")
        }
        print(3)
      
        // 结果:由于async可以开启新线程,所以他们的执行顺序是可以乱序的。但是由于3是sync,
        // 所以3和async4一定在sync3之后。
        ----------------
        1
        2
        sync3: 0
        3
        async1: 0
        async2: 0
        async4: 0
        ----------------
        1
        2
        sync3: 0
        async1: 0
        3
        async4: 0
        async2: 0
      
  3. 串行 + 同步

    •   var param1 = 0
        let queue = DispatchQueue(label: "barrier")
        print(1)
        queue.sync {
            print("sync1: \(self.param1)")
        }
        queue.sync {
            print("sync2: \(self.param1)")
        }
        print(2)
        queue.sync {
            print("sync3: \(self.param1)")
        }
        queue.sync {
            print("sync4: \(self.param1)")
        }
        print(3)
      
        // 结果:代码完全按照顺序执行,因为同步且没有新线程。
        ----------------
        1
        sync1: 0
        sync2: 0
        2
        sync3: 0
        sync4: 0
        3
        ----------------
        1
        sync1: 0
        sync2: 0
        2
        sync3: 0
        sync4: 0
        3
      
  4. 串行 + 异步

    •   var param1 = 0
        let queue = DispatchQueue(label: "barrier")
        print(1)
        queue.async {
            Thread.sleep(forTimeInterval: 2)
            print("async1: \(self.param1)")
        }
        queue.async {
            print("async2: \(self.param1)")
        }
        print(2)
        queue.sync {
            print("sync3: \(self.param1)")
        }
        queue.async {
            print("async4: \(self.param1)")
        }
        print(3)
      
        // 结果: sync3一定在async1和async2之后执行。
        // 异步虽可以开新线程,但任务是按顺序执行(串行队列每次只有一个任务被执行,任务一个接一个按顺序执行)
        1
        2
        async1: 0
        async2: 0
        sync3: 0
        3
        async4: 0
      

总而言之,只有当异步 + 并发时你才能让代码不按顺序地执行,其余情况要么因为同步无法开启新线程,要么因为即使异步开启了新线程但是得串行执行,所以都只能按顺序执行代码。

栅栏dispatch_barrier

假如有如下代码:即并发 + 异步

let queue = DispatchQueue(label: "barrier", attributes: .concurrent)
print(1)
queue.async {
    Thread.sleep(forTimeInterval: 2)
    print("async1")
}
queue.async {
    print("async2")
}
print(2)
queue.sync {
	print("sync3")
}
queue.async {
	print("async4")
}
queue.async {
	print("async5")
}
print(3)

但如果我们有一个需求,要让前两个(1和2)async和后面两个(4和5)async,分为两个组,并且这个两组的执行顺序是12执行完再执行45,这时候sync3虽然可以让任务阻塞在这里,知道它的block执行结束再向下执行,但async1由于延时了2秒,极有可能在45执行完后才打印出async1。

所以可以使用dispatch_barrier来解决这个问题。

let queue = DispatchQueue(label: "barrier", attributes: .concurrent)
print(1)
queue.async {
    Thread.sleep(forTimeInterval: 2)
    print("async1")
}
queue.async {
    print("async2")
}
print(2)
// 这里就可以保证上面的4个async,一定是12完全执行完毕后,再执行45,但是12内部和45内部的顺序是不定的。
// 并且print(3) 是可以在sync3之前执行的,因为这里虽然是barrier但它依然是async,不会阻塞主线程执行。
// 如果想要实现,print(3)一定在sync3之后执行,那么就需要用queue.sync(flags: .barrier)。
queue.async(flags: .barrier) {
	print("sync3")
}

queue.async {
	print("async4")
}
queue.async {
	print("async5")
}
print(3)

死锁问题

切忌使用如下代码,会造成死锁:

DispatchQueue.main.sync {
	print("hello main")
}

NEVER call the sync function on the main queue

If you call the sync function on the main queue it will block the queue as well as the queue will be waiting for the task to be completed but the task will never be finished since it will not be even able to start due to the queue is already blocked. It is called deadlock.

意思就是说,如果你在主线程中调用【主队列+同步】方法,那么sync本身就会阻塞当前队列,直到队列中的任务完成。而与此同时,当前队列的任务永远不会完成,因为它所在的队列被阻塞了。这就是死锁。

与此相似的,即使不是在主线程中,而是另一个串行队列开启的并发线程中,去sync这个新线程方法,也会死锁。

死锁和线程没关系 不是阻塞了线程,而是阻塞了队列,只要看 当前要sync的串行队列 之前正在执行的方法有没有结束。没结束就死锁!!!为啥主队列特殊?无非就是我们最外层的方法在主队列执行呗!!!

UI渲染

凡是要更新UI的操作,必须统一放到主线程下去做。

标签: 队列, 线程, GCD, 并发, 串行

添加新评论

0%