swift--多线程

本文参考:《iOS移动开发从入门到精通 第二版》(编著:李发展 ) 第14章 多线程技术,感兴趣的可以读下原著

本文主要描述iOS线程的基本概念,以及Thread、Operation和grand central dispatch三种多线程技术的使用。

一、 任务、进程和线程简述

1.1 任务 Task

任务既可以是一个线程,也可以是一个进程,是指为了达到目的的一组操作集合。

1.2 进程 Process

进程是一个独立的应用程序,在内存中具有独立的数据和代码空间,其拥有的数据和变量只属于他自己。是系统资源分配和调度的独立单位

1.3 线程 Thread

线程是比进程更小的能独立运行的基本单位,也是cpu调度和分派的基本单位。线程存在于进程之中,一个进程可以由多个线程构成。

二、 线程的stack space

iOS主线程栈空间默认1MB,子线程默认512KB,macOS为8MB。它不是立刻被创建分配,而是在使用过程中逐渐增加。子线程允许分配的最小栈空间为16KB,并且为4的倍数。

三、 线程优先级

线程的优先级属性是一个0.0~1.0的浮点值,1最高,0最小,默认0.5。注意,最高优先级不是100%会被优先执行,只是得到cpu调度的概率更高。

四、 线程的生命周期

创建,就绪,运行(在线程执行结束之前,会在就绪和运行之间来回切换),阻塞,消亡

五、 线程和RunLoop

runloop是一个对收到事件进行处理的循环。每条线程都有唯一的runloop对象,主线程自动创建并运行,子线程需要手动创建并调用run方法。

runloop可以从两个不同的事件源接收消息

1.Input sources投递异步消息,如对象的perform函数

self.perform(#selector(threadTask), with: nil)

2.Timer Sources 在计划时间或者重复的时间间隔内传递消息

Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(threadTask), userInfo: "infomation", repeats: true)

六、 使用Thread

//用Thread方式下载一张图片
let imageUrl = "http://www.antu58.club/images/work-1.jpg"
//初始化一个线程实例
let thread = Thread.init(target: self, selector: #selector(threadTask(path:)), object: imageUrl)
//启动该线程
thread.start()
@objc func threadTask(path:String) {
    let url = URL(string: path)
    var data:Data!
    do {
        try data = Data(contentsOf: url!)
        let image = UIImage(data: data)
        //在主线程显示图片
        self.perform(#selector(showImage(image:)), on: Thread.main, with: image, waitUntilDone: true)
    } catch  {
        print("error")
    }
}
@objc func showImage(image:UIImage) {
    self.image.image = image
}

 给thread加锁使线程同步

iOS下常用的锁为Lock对象,加锁用lock方法,解锁用unlock方法。当一个Lock对象,调用过一次lock方法之后,并且没有调用unlock方法前,任何线程都不能再对此Lock对象加锁。

iOS还提供recursiveLock、ConditionLock等类型的锁。recursive可以重复lock,但解锁时需要调用相同次数的unlock。ConditionLock是一个带有条件的锁,可以根据条件对线程进行加锁。

实例:

import UIKit

class ViewController: UIViewController {

    var booksCount = 100
    var soldBooksCount = 0
    var lock:NSLock!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        lock = NSLock()
        
        let salesmanA = Thread.init(target: self, selector: #selector(sellBook), object: nil)
        salesmanA.name = "销售A"
        salesmanA.start()
        
        let salesmanB = Thread.init(target: self, selector: #selector(sellBook), object: nil)
        salesmanB.name = "销售B"
        salesmanB.start()
        
        let salesmanC = Thread.init(target: self, selector: #selector(sellBook), object: nil)
        salesmanC.name = "销售C"
        salesmanC.start()
    }

    @objc func sellBook(){
        while true {
            lock.lock()
            if booksCount > 0 {
                Thread.sleep(forTimeInterval: 0.01)
                soldBooksCount += 1
                booksCount -= 1
                let threadName = Thread.current.name
                
                print("当前销售员为:\(String(describing: threadName)), 已售出:\(soldBooksCount), 剩余:\(booksCount)")
            } else {
                //结束
                Thread.exit()
            }
            lock.unlock()
        }
    }
    
}

假如不执行加锁操作,就会出现这种情况

七、Operation技术使用

过多的线程会消耗大量系统资源,导致程序变慢卡顿。和Thread相比,operation的好处是不需要担心线程管理和数据同步。

Operation是一个抽象类,使用它的子类BlockOperation创建一个operation子类的对象,并把对象添加到QperationQueue队列执行。

通过设置OperationQueu的maxConcurrentOperationCount属性,我们可以控制同时运行的线程数。

实例:

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var imageViewA: UIImageView!
    @IBOutlet weak var imageViewB: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        let imageUrl = "http://www.antu58.club/images/work-1.jpg"
        
        let taskA = getOperation(name: "taskA", imageUrl: imageUrl, isTopOne: true)
        
        let taskB = getOperation(name: "taskB", imageUrl: imageUrl, isTopOne: false)
        
        let queue = OperationQueue()
        queue.maxConcurrentOperationCount = 1
        queue.addOperation(taskA)
        queue.addOperation(taskB)
        
        for operation in queue.operations {
            print("Operation: \(String(describing: operation.name))")
        }

    }


    func getOperation(name:String, imageUrl:String, isTopOne:Bool) -> BlockOperation {
        let dowonLoad = BlockOperation {
            let url = URL(string: imageUrl)
            var data:Data!
            do {
                try data = Data(contentsOf: url!)
                let image = UIImage(data: data)
                //在主线程显示图片
                if isTopOne {
                    self.perform(#selector(self.showImage1(image:)), on: Thread.main, with: image, waitUntilDone: true)
                } else {
                    self.perform(#selector(self.showImage2(image:)), on: Thread.main, with: image, waitUntilDone: true)
                }
            } catch  {
                print("error")
            }
        }
        dowonLoad.name = name
        return dowonLoad
    }
    
    @objc func showImage1(image:UIImage) {
        self.imageViewA.image = image
    }
    
    @objc func showImage2(image:UIImage) {
        self.imageViewB.image = image
    }
    
}

八、Grand Central Dispatch的使用

GCD是iOS4.0时推出的一个多核编程的解决方案,可以替换thread和Operation.它会自动利用更多的cpu内核,会自动管理生命周期。也推荐大家多使用这个。

GCD 的 Dispatch Queue,它是一个对象,可以接收任务,并且以先到先执行的顺序来执行。dispatch的调度队列可以是并发的,也可以是串行的。

GCD的调度队列由三种类型

1. The main queue

串行队列,和应用主线程功能相同,会在程序主线程中执行,一般是界面元素的更新。可以通过DispatchQueue.main来获得main队列。

2. Global queue 全局队列是并发队列,有高、中、低、后台四个优先级,默认为中。可以通过DispatchQueue.global来获得全局队列。

3. 用户线程队列,通过DispatchQueue.init来获得用户线程队列,可以创建串行或者并行的队列。

实例1 使用GCD查询IP地址信息:

let apiUrl = URL(string: "http://taobao.com/serveice/getIpInfo.php?=27.156.152.57")
let globalQueue = DispatchQueue.global()
//开启一个新的线程
globalQueue.async {
    let result = try? Data(contentsOf: apiUrl!)
    let message = String(data: result!, encoding: .utf8)
    //在主线程同步UI信息
    DispatchQueue.main.async {
        self.label.text = message
    }
}

实例2 DispatchGroup 调度组的使用:

有时我们需要在几件事结束之后,再去执行一个任务,这样的事就可以借助dispatchGroup,他可以将多个block拼成一组,以监测这些block全部完成或者等待全部完成时发出消息。

print("开始任务")

let group = DispatchGroup()
let globalQueue = DispatchQueue.global()
let task1 = DispatchWorkItem{
    print("task1")
}
let task2 = DispatchWorkItem{
    print("task2")
}
let task3 = DispatchWorkItem{
    print("task3")
}
globalQueue.async(group: group, execute: task1)
globalQueue.async(group: group, execute: task2)
globalQueue.async(group: group, execute: task3)

group.notify(queue: globalQueue) {
    print("所有任务已经完成")
}

最后用GCD的方式改写一下之前售书案例


import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var label: UILabel!
    
    var booksCount = 100
    var soldBooksCount = 0
    var lock:NSLock = NSLock()
    
    override func viewDidLoad() {
        super.viewDidLoad()

        let group = DispatchGroup()
        let globalQueue = DispatchQueue.global()
        let task1 = DispatchWorkItem{
            self.sellBook()
        }
        let task2 = DispatchWorkItem{
            self.sellBook()
        }
        let task3 = DispatchWorkItem{
            self.sellBook()
        }
        globalQueue.async(group: group, execute: task1)
        globalQueue.async(group: group, execute: task2)
        globalQueue.async(group: group, execute: task3)
         
        group.notify(queue: globalQueue) {
            print("所有任务已经完成")
        }

    }
 
    func sellBook(){
        while true {
            lock.lock()
            if booksCount > 0 {
                soldBooksCount += 1
                booksCount -= 1

                print("已售出:\(soldBooksCount), 剩余:\(booksCount)")
                DispatchQueue.main.async {
                   //更新UI
                   self.label.text = "已售出:\(soldBooksCount), 剩余:\(booksCount)"
                }
            } else {
                return
            }
            lock.unlock()
        }
    }
    
}

补充:GCD延时

//延时两秒
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2) {
    //do...
}

猜你喜欢

转载自blog.csdn.net/weixin_41735943/article/details/104397977