多线程基础
GET
只要线程数量控制好就可以避免缺点,在iOS开发中,一般控制子线程在2条以内
GET
解决方案可以是互斥锁,下文有例子
结论
耗时操作不能放到主线程,如果将耗时操作放到主线程中,会将UI界面卡死
多线程实现方案
从上图中我们能知道在iOS开发中会用到三种多线程的实现方案,接下来会依此列出
通过NSThread创建子线程以及线程相关属性
import UIKit
class ViewController: UIViewController {
override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
print("touchesBegan:\(NSThread.currentThread())")
//将耗时操作放到主线程会将UI界面卡死
//self.longTimeOperation()
//self.creatThread1()
//self.creatThread2()
//self.creatThread3()
self.creatThread4()
}
//MARK: - 耗时操作
func longTimeOperation(){
for _ in 1...1000{
//阻塞1秒
//NSThread.sleepForTimeInterval(1)
print(NSDate())
print(NSThread.currentThread())
}
//耗时操作执行完成后将子线程取消
NSThread.currentThread().cancel()
}
func longTimeOperation2(){
for _ in 1...1000{
//阻塞1秒
//NSThread.sleepForTimeInterval(1)
print(NSDate())
print(NSThread.currentThread())
}
//耗时操作执行完成后将子线程取消
NSThread.currentThread().cancel()
}
}
//MARK: - 子线程的创建方式
//创建子线程(程序员自己创建的线程都是子线程,主线程无法自己创建,只能获取)
extension ViewController{
//方式1 - 直接创建
func creatThread1(){
//参数1:调用方法的对象
//参数2:要在子线程中�执行的方法对应的Selector(这个方法可以带一个参数,这个参数的实参就是参数3,参数类型可以是任何类型)
//参数3:参数2中的方法的实参
//功能:当创建出来的子线程启动后,参数1在子线程中去调用参数2中的方法
let thread = NSThread(target: self, selector: "longTimeOperation", object: nil)
print("创建:\(thread)")
//2.启动线程
thread.start()
}
//方式2 - 快速创建一个子线程
func creatThread2(){
//创建子线程对象,但是线程对象不会被返回,创建好之后不需要手动启动
NSThread.detachNewThreadSelector("longTimeOperation", toTarget: self, withObject: nil)
}
//方式3 - 隐式的创建一个子线程
func creatThread3(){
//创建一个后台线程(也是子线程),创建好之后不需要手动启动
self.performSelectorInBackground("longTimeOperation", withObject: nil)
}
}
//MARK: - 线程属性
extension ViewController{
func creatThread4(){
let threardA = NSThread(target: self, selector: "longTimeOperation", object: nil)
//设置线程名字
threardA.name = "线程A"
//设置线程优先级(默认0.5),数值越大优先级越高。一般在ios开发中不设置
//优先级影响并不是哪一个线程先被执行,而是影响的是CPU调度子线程的时候停留的时间(时间片)
threardA.threadPriority = 1
//启动线程
threardA.start()
let threadB = NSThread(target: self, selector: "longTimeOperation2", object: nil)
threadB.name = "线程B"
threadB.threadPriority = 0.1
threadB.start()
//获取当前线程(现在这里是主线程)
//let tread = NSThread.currentThread()
//获取主线程
//let mainThread = NSThread.mainThread()
}
}
线程操作
线程操作也就是改变线程的状态(状态图看上面的图片)
import UIKit
class ViewController: UIViewController {
//MARK: - 耗时操作
func longTimeOpration(){
for item in 0...10{
if item == 5{
//3.阻塞 -> 线程从可调线程池中移除,但是还在内存中,睡眠时间到了之后,会重新添加到进程池中
NSThread.sleepForTimeInterval(1)
}
if item == 9{
//4.非正常死亡
//NSThread.exit()
}
print(NSThread.currentThread(),item)
}
//执行完10次之后自然死亡 -> 从可调度池中移除,并在内存中销毁
}
override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
//1.新建
let thread = NSThread(target: self, selector: "longTimeOpration", object: nil)
//2.就绪->运行:线程被添加到了可调用线程池中,当CPU调度当前的线程,那么当前的线程处于运行状态,否则就是就绪状态
//注意:CPU只能调度在可调用线程池中的线程
thread.start()
}
}
线程安全解决方式 - 互斥锁
在上文中已经提到过线程的安全问题,比如卖票问题
在同一时间卖票点1和卖票点2同时访问票数,读取的数据相同,同时减1都返回数据99,而现实生活中应高是剩余98张。
解决这个问题可以在一个卖票点访问票数时加锁,其它的卖票点无法访问,等访问完再解锁。
- 模拟
import UIKit
//模拟售票
class ViewController: UIViewController {
//MARK: - 属性
var tickets = 20
//创建互斥锁
let lock = NSLock()
//MARK: - 方法
override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
//创建两个线程卖票
let threadA = NSThread(target: self, selector: "sellTickets", object: nil)
threadA.name = "售票点1"
threadA.start()
let thread2 = NSThread(target: self, selector: "sellTickets", object: nil)
thread2.name = "售票点2"
thread2.start()
}
//MARK: - 卖票
func sellTickets(){
while true{
//加锁
//在一个线程执行的时候,另外的线程是没有办法执行的
self.lock.lock()//一个线程在执行这段代码时其他线程无法访问
NSThread.sleepForTimeInterval(1)
if self.tickets > 0{
tickets -= 1
print("\(NSThread.currentThread())剩余\(tickets)张")
}
else{
print("售完")
//卖完后进程全部死亡
NSThread.exit()
}
//解锁
self.lock.unlock()
}
}
}
线程间的通信
线程间的通信方式有很多种,现在先简单介绍一种,下文GCD和NSOperation还会用到其它的通信方式
import UIKit
class ViewController: UIViewController {
//MARK: - 属性
@IBOutlet weak var imageView: UIImageView!
//MARK: - 方法
override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
//在子线程中下载图片
self.performSelectorInBackground("downloadImage", withObject: nil)
}
//MARK: - 下载图片
func downloadImage(){
print("下载图片:\(NSThread.currentThread())")
//图片路径
let imagePath = "图片地址"
//将地址转换成URL
let url = NSURL(string: imagePath)
//下载图片(耗时操作)
//let data = NSData(contentsOfURL: url!)
//将二进制转换成图片(有问题,这里先用本地的)
let image = UIImage(named: "海贼06.jpg")
print("下载完成")
//!!!图片下载完成之后回主线程展示图片!!!
//参数1:指定的方法
//参数2:指定的线程
//参数3:方法中的实参
//参数4:是否等待指定方法执行完成
//功能:在指定的线程中调用指定的方法
self.performSelector("showImage:", onThread: NSThread.mainThread(), withObject: image, waitUntilDone: false)
}
//MARK: - 显示图片
func showImage(image:UIImage){
print("显示图片:\(NSThread.currentThread())")
self.imageView.image = image
}
}
通过GCD创建子线程
涉及知识点:主队列,全局队列,线程间的通信,队列组(多个任务同时执行,全部执行完毕才回到主线程)
import UIKit
//MARK: - 两个概念
//1.任务:想要执行的操作
//2.队列:用来存储任务的容器(FIFO)
//GCD的使用步骤:
//1.创建队列(需要确定队列的类型)
//2.创建任务并且将任务添加到队列中(确定任务的执行方式)
//GCD会自动将任务从队列中取出放到对应的线程中执行
//MARK: - 四个术语
//任务的执行方式
//1.同步:在当前线程中执行(不会创建新的线程),需要马上执行的任务
//2.异步:在另外一个线程中执行(会创建新的线程)
//队列的类型
//3.并发:队列中的任务可以同时执行(前提是有多个线程)
//4.串行:队列中的任务一个一个按顺序执行
class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
//************************//
//MARK: - 基本应用
//===========1.串行、同步
//结果:在主线程中按顺序执行,先执行的任务再打印的结束
func test1(){
//创建队列变量
//参数1:队列的标签,用来区分不同的队列,可以不使用
//参数2:队列属性(类型),要么是串行,要么是并行
//DISPATCH_QUEUE_SERIAL -> 串行
//DISPATCH_QUEUE_CONCURRENT -> 并行
let queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL)
//将指定的任务添加到指定的队列中并且确定任务的执行方式是同步执行
//参数1:指定队列
//参数2:指定的任务(有点闭包感觉)
dispatch_sync(queue) {self.oprationAction(0)}
//为了便于观察,在队列中添加多个任务
for item in 1...10{
dispatch_sync(queue, {
self.oprationAction(item)
NSThread.sleepForTimeInterval(1)
})
}
print("结束")
}
//===========2.并发、同步
//结果:在主线程中按顺序执行,先执行的任务再打印的结束(因为只有一个线程,无法并行)
func test2(){
//创建并行队列
//DISPATCH_QUEUE_SERIAL -> 串行
//DISPATCH_QUEUE_CONCURRENT -> 并行
let queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT)
//将任务添加到队列中并确定执行方式是同步执行
//为了便于观察,在队列中添加多个任务
for item in 0...10{
dispatch_sync(queue, {
self.oprationAction(item)
NSThread.sleepForTimeInterval(1)
})
}
print("结束")
}
//===========3.串行、异步
//结果:在一个子线程中按顺序一个一个执行,先打印结束,再执行任务
func test3(){
//创建串行队列
let queue = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL)
//将任务添加到队列中,异步执行
for item in 0...10{
dispatch_async(queue, {
self.oprationAction(item)
NSThread.sleepForTimeInterval(1)
})
}
print("结束")
}
//===========4.并发、异步
//结果:多个线程中多个任务同时执行
func test4(){
//创建并发队列
let queue = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT)
//将任务添加到队列,异步执行
for item in 0...10{
dispatch_async(queue, {
self.oprationAction(item)
NSThread.sleepForTimeInterval(1)
})
}
print("结束")
}
//MARK: - 操作
func oprationAction(i:Int){
print(NSThread.currentThread(),i)
}
//****************************//
//MARK: - 全局队列
//在iOS进程中,系统会自动创建一个全局队列,这个队列是并发的。目的是方便程序员快速的创建一个并发队列
//全局队列:就是系统自动创建的一个并发队列,在使用的时候只需要获取这个队列
//异步执行全局队列中的任务
//结果:多个线程同时执行多个任务
func test5(){
//获取全局队列(单例,在整个进程中只有一个)
//参数1:优先级,一般不设置(0)
//参数2:预留参数(0)
let queue = dispatch_get_global_queue(0, 0)
//将任务添加到全局队列异步执行
for item in 0...10{
dispatch_async(queue, {
self.oprationAction(item)
})
}
}
//************************//
//MARK: - 主队列
//主队列是在iOS程序启动成功后系统自动创建的和主线程关联的队列,在主队列中的任务都是在主线程中执行的
//1.异步执行主队列中的任务
//结果:在主线程中按顺序执行(因为主队列的任务要在主线程执行,而异步不能创建主线程)
func test6(){
//拿到主队列
let queue = dispatch_get_main_queue()
//将任务添加到全局队列异步执行
for item in 0...10{
dispatch_async(queue, {
self.oprationAction(item)
})
}
}
//2.同步执行主队列中的任务
//结果:死锁
func test7(){
for item in 0...10{
dispatch_sync(dispatch_get_main_queue(), {
self.oprationAction(item)
})
}
}
//MARK: - !!!线程间的通信!!!
func test8(){
//并发异步下载图片
dispatch_async(dispatch_get_global_queue(0, 0)) {
//下载图片
print("下载图片\(NSThread.currentThread())")
let path = "http://i1.073img.com/160509/4230327_100603_1_lit.jpg"
let url = NSURL(string: path)
let data = NSData(contentsOfURL: url!)
let image = UIImage(data: data!)
//回到主线程显示图片
dispatch_async(dispatch_get_main_queue(), {
//显示图片
print("显示图片:\(NSThread.currentThread())")
self.imageView.image = image
})
}
}
//MARK: - 队列组
//队列组的作用:让多个任务同时执行,让所有的任务全部结束之后才回到主线程
func test9(){
//1.创建队列组
let group = dispatch_group_create()
//2.将任务添加到队列中,然后将队列添加到队列组(异步)
//参数1:组
//参数2:队列
//参数3:任务
dispatch_group_async(group, dispatch_get_global_queue(0, 0)) {
//下载图片
NSThread.sleepForTimeInterval(1)
print(NSThread.currentThread())
print("下载图片A")
}
dispatch_group_async(group, dispatch_get_global_queue(0, 0)) {
//下载图片
NSThread.sleepForTimeInterval(1)
print(NSThread.currentThread())
print("下载图片B")
}
//3.当group中所有的任务都执行完之后再回到主线程
//在指定的组中的任务都完成以后去执行指定队列中的指定任务
//参数1:指定队列组
//参数2:指定队列
//参数3:指定任务
dispatch_group_notify(group, dispatch_get_main_queue()) {
print(NSThread.currentThread())
print("回到主线程")
}
}
override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
//self.test1()
//self.test2()
//self.test3()
//self.test4()
//self.test5()
//self.test6()
//self.test7()
//self.test8()
self.test9()
}
}
通过NSOperation创建子线程
涉及知识点:线程间的通信,最大并发数,队列的挂起和取消挂起,任务的取消,任务依赖关系
import UIKit
//MARK: - 两个核心概念
//1.任务(NSOperation):要做的事情 -> 需要封装成一个任务/操作对象
//2.队列(NSOperationQueue):用来存放任务的容器
//使用NSOperation必须设置最大并发数
//使用NSOperation和NSOperationQueue实现多线程的步骤:
//1.创建队列对象
//2.创建任务对象
//3.将任务添加到队列
//系统会自动将队列中的任务取出来放到一个新的线程中执行
class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
lazy var queue:NSOperationQueue = {
return NSOperationQueue()
}()
//MARK: - 操作
func longtimeOperation(i:Int){
NSThread.sleepForTimeInterval(1)
print(NSThread.currentThread(),i)
}
//************************//
//MARK: - 基本应用
//异步并发
func test1(){
//创建队列
let queue = NSOperationQueue()
//创建任务对象
let operation = NSBlockOperation {
self.longtimeOperation(0)
}
//将任务添加到队列
queue.addOperation(operation)
//在队列中添加多个任务
for item in 1...10{
let operation2 = NSBlockOperation(block: { self.longtimeOperation(item)
})
queue.addOperation(operation2)
}
}
//MARK: - 快速向队列中添加任务
//异步并发
func test2(){
//向队列中添加任务
for item in 0...10{
self.queue.addOperationWithBlock {
self.longtimeOperation(item)
}
}
}
//MARK: - !!!线程间的通信!!!
func test3(){
//1.去子线程中下载图片
self.queue.addOperationWithBlock {
//下载图片
print("下载图片\(NSThread.currentThread())")
let data = NSData(contentsOfURL: NSURL(string: "http://i1.073img.com/160509/4230327_100603_1_lit.jpg")!)
let image = UIImage(data: data!)
//2.回到主线程去显示图片(其实就是将任务添加到主队列)
//注意:所有的UI操作必须回到主线程中执行
NSOperationQueue.mainQueue().addOperationWithBlock({
//显示图片
print("显示图片\(NSThread.currentThread())")
self.imageView.image = image
})
}
}
//***************************//
//MARK: - 高级应用
//1.最大并发数:同一时刻能够执行的最多的任务个数!!!
func test4(){
//设置最大并发数
//一般2-3个,任务繁重就设置2
self.queue.maxConcurrentOperationCount = 3
//添加任务
for item in 0...10{
self.queue.addOperationWithBlock {
self.longtimeOperation(item)
}
}
}
//2.队列的挂起和取消挂起(暂停和取消暂停)
//队列暂停:暂停从队列中取出任务,但是已经从队列取出来的任务仍然会继续执行
@IBAction func btnAction(sender: AnyObject) {
if sender.currentTitle == "暂停"{
//暂停队列
self.queue.suspended = true
sender.setTitle("继续", forState: .Normal)
}
else{
//取消暂停
self.queue.suspended = false
sender.setTitle("暂停", forState: .Normal)
}
}
//3.队列的取消
//队列的取消指的是调用cancelAllOperations()的瞬间将队列中所有的任务销毁,如果任务已经从任务中取出来了,就不能被销毁。如果队列取消,那么销毁的任务就不能再复原
@IBAction func cancelAction(sender: AnyObject) {
self.queue.cancelAllOperations()
}
//4.依赖关系
//解决的问题:如果要求必须是任务A完成后才可以执行任务B,那么可以让任务B去依赖任务A
func test5(){
//1.创建任务
//任务A
let operationA = NSBlockOperation{
NSThread.sleepForTimeInterval(1)
print("登陆成功")
}
//任务B
let operationB = NSBlockOperation{
NSThread.sleepForTimeInterval(1)
print("下载电影")
}
//2.建立依赖关系
//注意:
//a.任务之间的依赖关系必须在被任务被添加到队列之前建立,否则依赖关系无效
//b.依赖关系不可以形成一个闭环
//这里让B依赖A,也就是A执行完才能执行B
operationB.addDependency(operationA)
//任务C
let operationC = NSBlockOperation{
NSThread.sleepForTimeInterval(1)
print("播放广告")
}
//3.在队列中同时添加多个任务
self.queue.addOperations([operationA,operationB,operationC], waitUntilFinished: false)
}
override func touchesBegan(touches: Set, withEvent event: UIEvent?) {
//self.test1()
//self.test2()
//self.test3()
//self.test4()
self.test5()
}
}