iOS多线程-线程同步/线程安全
线程同步
和同步线程
是两个概念。不要搞混了。
同步线程
:串行执行任务的线程,会把你的同步代码一行一行的执行下去,即使是在block里面。
异步线程
:并行执行的线程,代码会放在另外一块区域去执行,执行完毕之后返回结果。
线程异步
:我也不知道表达的什么,这个词汇是不应该存在,可以理解为异步线程。
线程同步
:是指多个线程同时访问一个资源时可能存在竞争问题提供的解决方案
,使多个线程可以对同一个资源进行操作,比如线程A为数组M添加了一个数据,线程B可以接收到添加数据后的数组M。线程同步就是线程之间相互的通信。
常见比如线程内操作了一个线程外的变量,这个时候一定要考虑线程安全和同步。
- (void)getIamgeName:(NSMutableArray *)imageNames{//假如每个进来的都是一个线程
//1.imageNames是线程外的变量,这个时候就需要考虑线程安全
/*2.NSMutableArray *array = [[NSMutableArray alloc]initWithArray:imageNames];
这里如果新生成一个array,下面也把imageNames换成array就不需要考虑线程安全,但是这样array.count判断永远大于0,也就是永远等于imageNames.count
*/
NSString *imageName;
if (imageNames.count>0) {
imageName = [imageNames lastObject];
[imageNames removeObject:imageName];
}
}
同步异步任务
这里有个概念容易混搅:
在GCD里面创建一个并行队列,如果向队列添加同步任务,任务还是同步执行的。主线程会等待同步任务执行,同步任务会卡主线程,如果添加了同步任务需要注意主线程的响应问题。
GCD的队列只是定义了队列里面的任务是怎么执行的,是串行还是并行,并没有定义任务是在异步线程执行还是同步线程执行。
dispatch_queue_t globalQueue = dispatch_queue_create([@"com.yasin.dispatchqueue" cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_CONCURRENT);;
for (int i=0; i<100; i++) {
dispatch_sync(globalQueue, ^(){
[self getIamgeName];
for (int m =100*(100-i); m>0; m--) {
/**
* i从0到100,m就是10000,9900,9800---100减减到0,
所以这里的任务耗时是越来越少
如果是一个接着一个执行就会是正序输出i,
如果是一起执行最后输入,也许不会倒序输出i,但至少是乱序的
*/
BOOL ss = [[NSUserDefaults standardUserDefaults]boolForKey:@"随便一个key"];
if (ss) {
//读取和判断很耗时
//模拟耗时操作
}
}
NSLog(@"%d",i);
});
}
这里会一个接着一个执行同步线程的任务,并不会出现几个线程同时执行的现象。你规定了要同步一个一个执行线程任务怎么可能会并行执行多个任务。这个代码书写逻辑就不对。
--小结-- 其实只要不做傻事就不会出问题,要一个一个执行的任务就放在串行队列里面,需要异步就异步,需要同步就同步(异步不卡主线程,同步卡主线程);如果想要并行执行多个任务,就放在并行队列里面,开异步线程去做。
线程同步的方法
-
原子操作
我们在声明一个变量的时候一般会使用nonatomic
,这个就是非原子操作;原子操作是atomic
。
简单的加减使用原子操作具有更高的性能优势。注意是加减,不是增删!!
也就是说仅仅对于getter,setter是线程安全的,两个线程都去对变量赋值是安全的。对于比如NSMutableArray类型的增删操作不是线程安全的 -
线程锁
锁可以保护临界区,代码在临界区同一时间只会被一个线程执行。有互斥锁、递归锁、读写锁、分布锁、自旋锁、双重检查锁等等。
后续会重点介绍这部分 -
条件、信号量
有个BOOL类型的变量,当线程A进入临界区时把BOOL值置为NO,如果线程B准备进入临界区时发现BOOL值为NO就挂起等待,当线程A出临界区时把BOOL置为YES,线程B会被唤醒并继续执行。
条件就是使用信号量在线程之间相互发生信号。
条件通常被使用来说明资源可用性,或用来确保任务以特定的顺序执行。 -
使用Selector
selector方法允许你的线程以异步的方式来传递消息,以确保它们在同一个线程上面执行是同步的。
比如NSObject
中的方法
performSelector:withObject:afterDelay:
performSelectorInBackground:withObject:
performSelector:onThread:withObject:waitUntilDone:
代码:
[self performSelector:@selector(test:) withObject:nil afterDelay:1];
[self performSelectorInBackground:@selector(test:) withObject:nil];
//等效于[NSThread detachNewThreadSelector:@selector(test:) toTarget:self withObject:nil];
代码2:
-(void)viewDidLoad
{
[super viewDidLoad];
[self threadInfo:@"UI"];
_isNewThreadAborted = NO;
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread:) object:nil];
//开始线程
[_thread start];
//在另一个线程中的Run Loop中执行Selector
[self performSelector:@selector(test:) onThread:_thread withObject:nil waitUntilDone:NO];
}
//在新线程中创建并开始一个NSRunLoop
-(void)newThread:(id)obj
{
@autoreleasepool
{
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
while (!_isNewThreadAborted)
{
[currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"线程停止");
}
}
//Selector执行
-(void)test:(id)obj
{
[self threadInfo:@"test"];
_isNewThreadAborted = YES;
}
-(void)threadInfo:(NSString*)category
{
NSLog(@"%@ - %@", category, [NSThread currentThread]);
}
- 内存屏障和 Volatile 变量
OSMemoryBarrier函数,设置内存屏蔽
volatile变量
因为内存屏障和volatile变量降低了编译器可执行的优化,因此你应该谨慎使用它们,只在有需要的地方时候,以确保正确性。
这部分涉及编译器,现在还不是很理解,以后再补充
并行并发
并发编程、并发程序,和并行计算机。
并发性与软件结构有关,而并行性与硬件有关。
也就是说,并发就是多线程编程,并行就是多核处理器。
天下武功出少林
这里推荐一个特别好的文章iOS多线程编程指南(四)线程同步
后续我会着重研究线程锁这一块