一、操作系统中的几个基本概念
进程(process):指一个正在运行的可执行程序(应用程序),它可以包含多个线程。
线程(thread):指独立执行的代码片段,最小的执行单位。一个进程中所有的任务都在线程中执行。
任务(task):表示需要执行的工作。它是一个抽象的概念。
线程的三种状态:运行(running)、就绪(ready)、阻塞(blocked)。线程持续在这三个状态之间切换,直到它最终退出或者进入中断状态。当你显式的中断线程的时候,线程永久停止,且被系统回收。
单线程:在单个线程中任务的执行是串行的。如果在1个线程中执行多个任务,那么只能一个接一个的按顺序执行这些任务。所以在同一个线程中,是不存在资源不同步的。
多线程:一个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务。所以多线程可以提高程序的执行效率。
多线程的原理:同一时刻,CPU只能处理1条线程,所以多线程并发执行其实就是cpu快速的在不同的线程之间进行调度(有多种调度算法),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。所以如果在一个应用程序中开辟了非常多的线程,就会让CPU来回在多条线程之间进行切换,会消耗大量的CPU资源,从而导致每条线程被调度执行的频率降低,结果就是线程的执行效率降低。
多线程的优缺点
优点:提高程序执行效率;提高资源利用利率(cpu、内存利用率)。
缺点:创建线程是有开销的,ios下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB,主线程1MB,也可以使用-setStackSize:设置,但是必须是4K的倍数,并且小于16K),创建线程的时间大约是90毫秒;如果开启大量线程,会降低程序的性能;线程越多,CPU在调度线程上的开销就越大;程序设计更复杂,比如多线程间的通信,资源同步等。
ios中多线程的实现方案:
二、线程同步
概念
线程同步:多条线程按顺序的执行任务,即多条线程在同一条线上执行。
多线程的安全隐患及解决方式
隐患:
一块资源可能被多个线程共享,比如多个线程同时访问并操作一个对象、同一个变量、同一个文件等。当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题。
解决方法:
为操作资源的代码片段添加互斥锁。也就是在读取和改变资源的代码片段。切记,在同一个线程不会出现资源不同步的现象,所谓的资源不同步一定是在多线程中才会发生的。
互斥锁的使用格式:
@synchronized(锁对象){ //操作资源的代码片段 }
// 锁定1份代码只用1把锁,用多把锁是无效的
互斥锁的优缺点:
优点:能有效防止因多线程抢夺资源造成的数据安全问题。
缺点:需要消耗大量的CPU资源
互斥锁使用前提:多条线程争夺同一块资源
OC中的原子性和非原子性:
atomic:原子属性,为setter方法加锁(默认就是atomic)。
nonatomic:非原子属性,不会为setter方法加锁。
ios开发的建议:
- 所有的属性都声明为nonatomic
- 尽量避免多线程抢夺同一块资源
- 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减少客户端的压力。
eg:模拟窗口卖票
1.不加锁情况下:
//
// ViewController.m
// 线程安全
//
// Created by zhangqi on 15/3/2016.
// Copyright (c) 2016 zhangqi. All rights reserved.
//
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,strong) NSThread *thread01;
@property (nonatomic,strong) NSThread *thread02;
@property (nonatomic,strong) NSThread *thread03;
@property (nonatomic,assign) NSInteger ticketCount;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.ticketCount = 100;
self.thread01 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread01.name = @"01窗口";
self.thread02 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread02.name = @"02窗口";
self.thread03 = [[NSThread alloc] initWithTarget:self selector:@selector(saleTicket) object:nil];
self.thread03.name = @"03窗口";
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self.thread01 start];
[self.thread02 start];
[self.thread03 start];
}
- (void)saleTicket
{
while (1){
if (self.ticketCount > 0) {
self.ticketCount--;
NSLog(@"%@买票1张,余票:%zd张",[NSThread currentThread].name,self.ticketCount);
}else{
NSLog(@"票卖光了...");
break;
}
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
运行结果:
2016-03-15 11:41:48.640 线程安全[62060:10112864] 01窗口买票1张,余票:97张
2016-03-15 11:41:48.639 线程安全[62060:10112865] 02窗口买票1张,余票:98张
2016-03-15 11:41:48.639 线程安全[62060:10112866] 03窗口买票1张,余票:98张
2016-03-15 11:41:48.640 线程安全[62060:10112864] 01窗口买票1张,余票:96张
2016-03-15 11:41:48.640 线程安全[62060:10112865] 02窗口买票1张,余票:95张
2016-03-15 11:41:48.641 线程安全[62060:10112866] 03窗口买票1张,余票:94张
2016-03-15 11:41:48.641 线程安全[62060:10112864] 01窗口买票1张,余票:93张
……
2016-03-15 11:41:48.657 线程安全[62060:10112865] 票卖光了...
2016-03-15 11:41:48.657 线程安全[62060:10112866] 票卖光了...
2016-03-15 11:41:48.657 线程安全[62060:10112864] 票卖光了...
2.加锁情况
- (void)saleTicket
{
while (1){
@synchronized(self){
if (self.ticketCount > 0) {
self.ticketCount--;
NSLog(@"%@买票1张,余票:%zd张",[NSThread currentThread].name,self.ticketCount);
}else{
NSLog(@"票卖光了...");
break;
}
}
}
}
运行结果:
2016-03-15 11:46:14.674 线程安全[62161:10117397] 02窗口买票1张,余票:99张
2016-03-15 11:46:14.674 线程安全[62161:10117398] 03窗口买票1张,余票:98张
2016-03-15 11:46:14.675 线程安全[62161:10117396] 01窗口买票1张,余票:97张
2016-03-15 11:46:14.675 线程安全[62161:10117397] 02窗口买票1张,余票:96张
……
2016-03-15 11:46:14.701 线程安全[62161:10117398] 票卖光了...
2016-03-15 11:46:14.702 线程安全[62161:10117397] 票卖光了...
2016-03-15 11:46:14.702 线程安全[62161:10117396] 票卖光了...
三、线程间的通信
四、NSOperation
简介
NSOperation的作用:
NSOperation配合NSOperationQueue也能实现多线程编程
NSOperation和NSOperationQueue实现多线程的具体步骤:
- 首先将需要执行的操作封装到一个NSOperation对象中
- 然后将NSOperation对象添加到NSOperationQueue中
- 系统会自动将NSOperationQueue中的NSOperation取出来
- 将取出来的NSOperation封装的操作放到一条新线程中执行
NSOperation的子类:
NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类。使用其子类的方式有三种:
- NSInvocationOperation
- NSBlockOperation
- 自定义子类继承NSOperation,实现内部相应的方法
NSInvocationOperation的使用:
使用创建出的NSInvocationOperation对象调用start()方法后,默认情况下,调用了start方法后并不会开启一条新线程去执行操作,而是在当前线程同步执行操作。只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作。
eg:
- (void)testInvocationOperation
{
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationSel) object:nil];
[invocationOperation start];
}
- (void)invocationSel
{
NSLog(@"当前线程是---%@",[NSThread currentThread]);
}
运行结果:
2016-03-18 13:44:44.832 ScanboxOperation[11666:3438344] 当前线程是—{number = 1, name = main}
NSBlockOperation的使用:
实例化方法:+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
添加操作:- (void)addExecutionBlock:(void (^)(void))block;
当使用NSBlockOperation封装操作时候,创建对象时候添加的操作一定在主线程中运行,而在后来通过addExecutionBlock方法添加的操作,我在IPhone6S上的测试是:前两个添加的操作在主线程中执行。
eg:
- (void)testBlockOperation
{
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
// 在主线程中打印
NSLog(@"创建时候的打印---%@",[NSThread currentThread]);
}];
// 添加额外的任务时候,在子线程中执行
[blockOperation addExecutionBlock:^{
NSLog(@"添加时候的打印1---%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"添加时候的打印2---%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"添加时候的打印3---%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"添加时候的打印4---%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"添加时候的打印5---%@",[NSThread currentThread]);
}];
[blockOperation start];
}
运行结果:
2016-03-18 14:13:24.631 ScanboxOperation[42808:11565184] 创建时候的打印—{number = 1, name = main}
2016-03-18 14:13:24.631 ScanboxOperation[42808:11565184] 添加时候的打印1—{number = 1, name = main}
2016-03-18 14:13:24.631 ScanboxOperation[42808:11565184] 添加时候的打印3—{number = 1, name = main}
2016-03-18 14:13:24.631 ScanboxOperation[42808:11565631] 添加时候的打印2—{number = 6, name = (null)}
2016-03-18 14:13:24.632 ScanboxOperation[42808:11565184] 添加时候的打印4—{number = 1, name = main}
2016-03-18 14:13:24.632 ScanboxOperation[42808:11565631] 添加时候的打印5—{number = 6, name = (null)}
结论:创建时候添加的操作一定是在主线程中执行,其后来添加的操作不一定在子线程中执行。
NSOperationQueue的使用:
eg:
- (void)invocationSel1
{
NSLog(@"11当前线程是---%@",[NSThread currentThread]);
}
- (void)invocationSel2
{
NSLog(@"22当前线程是---%@",[NSThread currentThread]);
}
- (void)testOperationQueue
{
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
// 创建NSInvocationOperation对象
NSInvocationOperation *invocationOperation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationSel1) object:nil];
NSInvocationOperation *invocationOperation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationSel2) object:nil];
// 创建NSBlockOperation
NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"boockOperation3------%@",[NSThread currentThread]);
}];
[blockOperation3 addExecutionBlock:^{
NSLog(@"add operation1 to boockOperation3------%@",[NSThread currentThread]);
}];
[blockOperation3 addExecutionBlock:^{
NSLog(@"add operation2 to boockOperation3------%@",[NSThread currentThread]);
}];
NSBlockOperation *blockOperation4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"boockOperation4------%@",[NSThread currentThread]);
}];
[operationQueue addOperation:invocationOperation1];
[operationQueue addOperation:invocationOperation2];
[operationQueue addOperation:blockOperation3];
[operationQueue addOperation:blockOperation4];
}
运行结果:
2016-03-18 14:24:38.651 ScanboxOperation[43082:11573361] 22当前线程是—{number = 5, name = (null)}
2016-03-18 14:24:38.651 ScanboxOperation[43082:11573362] 11当前线程是—{number = 4, name = (null)}
2016-03-18 14:24:38.651 ScanboxOperation[43082:11573360] boockOperation4——{number = 2, name = (null)}
2016-03-18 14:24:38.651 ScanboxOperation[43082:11573363] boockOperation3——{number = 3, name = (null)}
2016-03-18 14:24:38.651 ScanboxOperation[43082:11573361] add operation1 to boockOperation3——{number = 5, name = (null)}
2016-03-18 14:24:38.651 ScanboxOperation[43082:11573362] add operation2 to boockOperation3——{number = 4, name = (null)}
操作依赖:
NSOperation之间可以设置依赖来保证执行顺序。比如一定要在操作A执行完之后再执行操作B:[opeartionB addDependency:operationA]
eg:
- (void)testDependency
{
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation1-------%@",[NSThread currentThread]);
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation2-------%@",[NSThread currentThread]);
}];
NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation3-------%@",[NSThread currentThread]);
}];
NSBlockOperation *blockOperation4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation4-------%@",[NSThread currentThread]);
}];
NSBlockOperation *blockOperation5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"blockOperation5-------%@",[NSThread currentThread]);
}];
blockOperation5.completionBlock = ^{
NSLog(@"blockOperation5--执行完毕-----%@",[NSThread currentThread]);
};
[blockOperation3 addDependency:blockOperation1];
[blockOperation1 addDependency:blockOperation2];
[blockOperation2 addDependency:blockOperation4];
[blockOperation4 addDependency:blockOperation5];
// 执行顺序:5、4、2、1、3
[operationQueue addOperation:blockOperation1];
[operationQueue addOperation:blockOperation2];
[operationQueue addOperation:blockOperation3];
[operationQueue addOperation:blockOperation4];
[operationQueue addOperation:blockOperation5];
}
运行结果:
2016-03-18 14:49:33.048 ScanboxOperation[43638:11590189] blockOperation5——-{number = 2, name = (null)}
2016-03-18 14:49:33.049 ScanboxOperation[43638:11590191] blockOperation5–执行完毕—–{number = 3, name = (null)}
2016-03-18 14:49:33.049 ScanboxOperation[43638:11590189] blockOperation4——-{number = 2, name = (null)}
2016-03-18 14:49:33.049 ScanboxOperation[43638:11590189] blockOperation2——-{number = 2, name = (null)}
2016-03-18 14:49:33.049 ScanboxOperation[43638:11590189] blockOperation1——-{number = 2, name = (null)}
2016-03-18 14:49:33.050 ScanboxOperation[43638:11590189] blockOperation3——-{number = 2, name = (null)}