版权声明:本文为博主原创文章,未经博主允许不得转载。
2017/09/06
看到一篇关于线程锁的文章,十分的精彩。有兴趣的朋友可以点进去看看。
下面是几点自己关于线程锁的体会
什么是线程锁?
一般情况下,多个进程可以同时访问一个内存。但如果中间穿插写操作就会发生一些异常。所以需要用线程锁,限制同一时刻只能有一个线程访问一个内存空间。读写是如何的实现?(主要讲写)
. 首先从内存中读取数据到寄存器,i = 0
. 在寄存器中对数据进行写操作 i = 1
. 将寄存器中操作后的数据写入内存, i = 1
从这里可以看到如果A、B进行同时进行,A进行写数据,B进行读数据。则B读取到的数据可能是A写之前的数据也有可能是A写之后的数据
- 线程锁有哪些类型?
互斥锁、自旋锁、读写锁
从 实现原理上来讲,Mutex属于sleep-waiting类型的锁。例如在一个双核的机器上有两个线程(线程A和线程B),它们分别运行在Core0和 Core1上。
假设线程A想要通过pthread_mutex_lock操作去得到一个临界区的锁,而此时这个锁正被线程B所持有,那么线程A就会被阻塞 (blocking),Core0 会在此时进行上下文切换(Context Switch)将线程A置于等待队列中,此时Core0就可以运行其他的任务(例如另一个线程C)而不必进行忙等待
结论:
- async操作开辟子线程,sync操作不开辟子线程;
2 . 同步会阻塞当前队列添加其他线程,异步不会阻塞当前队列。 - 并发队列可以同时执行多个线程任务,串行队列同时只能执行一个线程任务。
多线程
在iOS 中,操作多线程主要有4个方案:
PThreads:它是C语言的框架,使用起来很不方便,基本不会用。
NSThread:苹果对多线程进行了封装,是面向对象的。但因为它需要我们去管理线程的生命周期,所以用的也不是很多。不过用它去查看当前线程,或者让当前线程睡眠还是很方便的。
然后是GCD:GCD是苹果对多核运算提出的解决方案,而且gcd会自己管理生命周期,这样可以让我们把注意力集中在具体任务上。
最后一个是是NSOperation,它是对GCD封装。增加一些功能。比如线程都挂起和暂停,还有线程的依赖。
不过像我最常用的还GCD,对他也最了解。所以就讲一下它吧。
GCD新增的2个概念
任务:
//同步为主队列添加了一个任务,等到block结束后返回结果。没有创建一个新的线程
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
//异步为主队列添加了一个任务,立刻返回结果,创建了一个新的线程执行任务
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
队列
* At the core, dispatch provides serial FIFO queues to which blocks may be
* submitted. Blocks submitted to these dispatch queues are invoked on a pool
* of threads fully managed by the system. No guarantee is made regarding
* which thread a block will be invoked on; however, it is guaranteed that only
* one block submitted to the FIFO dispatch queue will be invoked at a time.
*
* When multiple queues have blocks to be processed, the system is free to
* allocate additional threads to invoke the blocks concurrently. When the
* queues become empty, these threads are automatically released.
大概意思:dispatch_sync,dispatch_async中的block代码块(任务)会以FIFO的方式在队列中被调用。他们将会由系统从线程池中随机分配一个线程,然后执行任务。
ps:主队列的线程为主线程。
主队列:dispatch_get_main_queue()
全局队列: dispatch_get_global_queue(0, 0)
自定义队列:
dispatch_queue_t serialQueue = dispatch_queue_create("com.lai.www", DISPATCH_QUEUE_SERIAL);//串行
dispatch_queue_t serialQueue = dispatch_queue_create("com.lai.www", DISPATCH_QUEUE_CONCURRENT);//并发
并发队列:
- 任务以FIFO从序列中移除,然后并发运行,一次可以调度多个,可以按照任何顺序完成。
- 可以同时执行多个线程任务
串行队列:
- 任务以FIFO从序列中一个一个执行。一次只调度一个任务
- 而且只会开启一条线程
同步:
dispatch_queue_t Aqueue = ~;
dispatch_sync(Aqueue, ^{
NSLog(@"2");
});
* dispatch_sync() will not return until the block has finished(同步为某队列添加了一个任务,要知道该任务返回才能返回。)
注意:
0. 不会创建新的线程,所以在当前的线程中执行
1. 该返回可以为dispatch_async() 的返回,也可以为任务执行完毕
2. 阻塞的是当前队列,而不是Aqueue队列;
3. 先阻塞当前队列,然后在向Aqueue队列中添加任务。(就是因为如此,所以在主队列中同步向主队列添加任务会造成死锁)
异步
dispatch_queue_t Aqueue = ~;
dispatch_async(Aqueue, ^{
NSLog(@"2");
});
* Calls to dispatch_async() always return immediately after the block has
* been submitted, and never wait for the block to be invoked.
(立刻返回block结果,不需要等待block中的任务被调度)
注意:
1. 会创建一个新的线程去执行任务
2. 异步和同步一样,影响的是当前的队列,对Aqueue队列没有影响。
对同步异步、队列、任务的概念理解讲完了,下面通过例子来更进一步地理解。
- (void)test2{
dispatch_queue_t serialQueue = dispatch_queue_create("com.lai.www", DISPATCH_QUEUE_SERIAL);//串行
//代码段1
dispatch_async(serialQueue, ^{
NSLog(@"1");
sleep(3);
});
//代码段2
for(int i = 0; i < 5; i ++){
NSLog(@"i = %d", i);
}
NSLog(@"0");
//代码段3
dispatch_sync(serialQueue, ^{
NSLog(@"2");
sleep(1);
});
//代码段4
NSLog(@"4");
//代码段5
dispatch_async(serialQueue, ^{
NSLog(@"3");
});
//代码段6
NSLog(@"5");
}
输出结果:
2017-02-27 15:38:14.945 test[2527:158657] i = 0
2017-02-27 15:38:14.945 test[2527:158694] 1
2017-02-27 15:38:14.945 test[2527:158657] i = 1
2017-02-27 15:38:14.945 test[2527:158657] i = 2
2017-02-27 15:38:14.945 test[2527:158657] i = 3
2017-02-27 15:38:14.946 test[2527:158657] i = 4
2017-02-27 15:38:14.946 test[2527:158657] 0
2017-02-27 15:38:17.950 test[2527:158657] 2
2017-02-27 15:38:18.952 test[2527:158657] 4
2017-02-27 15:38:18.952 test[2527:158657] 5
2017-02-27 15:38:18.952 test[2527:158694] 3
推断:serialQueue为一个串行队列,在串行队列中的任务执行FIFO。然后我看分析的代码:
代码1,异步为serialQueue添加一个任务(会开辟一个子线程执行),直接返回结果,所以同步执行代码段2。这里的执行结果返回顺序(也只能看结果)会随着任务消耗的时间来确定。此时主线程:代码段2;serialQueue线程:代码段1
代码3同步为serialQueue添加了打印2任务(不会开辟新的线程,所以在当前的主线程中执行),所以会阻碍当前队列的其他任务,主队列无法添加其他任务,等待打印2任务完成后执行代码4
码段5异步在serialQueue队列添加打印3任务,所以会同时执行代码6 。输出结果随着代码消耗时间决定,这里添加到子线程中执行的速度<执行代码6的速度。
列子二
打印结果:1 ,3, 4, 2
// 17/2/28
解释:
- app启动的时候,会默认创建一个串行队列dispatch_get_main_queue(),我们称之为主队列,在主线程中调用。当前主队列有1个任务:打印1,异步为为全局队列添加一个A任务,打印3,4。
- A任务为异步在主线程中添加一个打印任务,所以当前主线程有2个任务(如果打印3,4没有结束的话)。根据FIFO,输出结果1,3,4 ;2。
例子3:
- (void)test6{
dispatch_queue_t queue = dispatch_queue_create("金永峰", DISPATCH_QUEUE_CONCURRENT);
//代码1
dispatch_async(queue, ^{
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
for(int i = 0; i < 20; i ++){
[NSThread sleepForTimeInterval:0.5];
NSLog(@"%@++++++++, %d", [NSThread currentThread], i);
}
});
});
//代码2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//代码4
dispatch_sync(queue, ^{
for(int i = 0; i < 10; i ++){
[NSThread sleepForTimeInterval:0.5];
NSLog(@"%@--------%d", [NSThread currentThread],i);
}
});
});
}
打印结果:
2017-03-20 16:33:26.669 GCC[84715:2932251] <NSThread: 0x600000264a40>{number = 3, name = (null)}--------0
2017-03-20 16:33:26.669 GCC[84715:2932230] <NSThread: 0x60800007eac0>{number = 4, name = (null)}++++++++, 0
2017-03-20 16:33:27.171 GCC[84715:2932230] <NSThread: 0x60800007eac0>{number = 4, name = (null)}++++++++, 1
2017-03-20 16:33:27.171 GCC[84715:2932251] <NSThread: 0x600000264a40>{number = 3, name = (null)}--------1
2017-03-20 16:33:27.675 GCC[84715:2932230] <NSThread: 0x60800007eac0>{number = 4, name = (null)}++++++++, 2
2017-03-20 16:33:27.675 GCC[84715:2932251] <NSThread: 0x600000264a40>{number = 3, name = (null)}--------2
2017-03-20 16:33:28.178 GCC[84715:2932251] <NSThread: 0x600000264a40>{number = 3, name = (null)}--------3
推断:
1.代码1为异步为queue添加任务,所以直接返回block结果,可以直接执行代码2,为全局创建1个任务。所以创建了2个任务(这一点要明确)。
2.queu队列里面有2个任务:1. 同步创建一个打印任务;2:打印---;2个任务在不同的线程中,并且queue是并发对; ,所以可以同时执行
3.全局队列中也有2个任务:1.同步创建1个打印任务,2.打印+++;2个任务在不同的线程中,并且全局队列是并发的,所以可以同时执行
例子4:
- (void)test7{
dispatch_queue_t queue = dispatch_queue_create("金永峰", DISPATCH_QUEUE_CONCURRENT);
//代码1
dispatch_async(queue, ^{
/** 的代码是queue中的一个完整的任务:打印\,同步在全局队列中创建打印+任务 */
for(int i = 0; i < 20; i ++){
[NSThread sleepForTimeInterval:0.5];
NSLog(@"%@\\\\\\", [NSThread currentThread]);
}
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
for(int i = 0; i < 20; i ++){
[NSThread sleepForTimeInterval:0.5];
NSLog(@"%@++++++++, %d", [NSThread currentThread], i);
}
});
});
//代码2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//代码4
dispatch_sync(queue, ^{
for(int i = 0; i < 10; i ++){
[NSThread sleepForTimeInterval:0.5];
NSLog(@"%@--------%d", [NSThread currentThread],i);
}
});
});
}
推断:
1. 代码1为异步执行,所以直接返回block结果,可以直接执行代码2,所以创建了2个任务(这一点要明确,称为A、B任务)
首先:
2. queue队列中的任务有2个:1、打印\\\, 同步在全局队列中添加任务,2、打印+++。
3. 全局队列中的任务1个:同步创建1个任务
所以:
* 首先,queue(并发)中的执行代码:同时打印\\\和打印---;
* 等到打印\\\结束(此时queue开始执行打印---任务),再同步在全局队列中添加任务;
*最后全局队列执行任务打印++,queue执行任务打印--
推荐的一篇文章:并发 并行 同步 异步 多线程的区别, 多线程只是我们实现异步的一种手段
2017/09/26
dispatch_async(dispatch_get_main_queue(), ^{
[self logTask:@"mainQueue"];
});
dispatch_queue_t queue = dispatch_queue_create("cx", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
[self logTask:@"sync-queue"];
});
//打印-- (模拟结果)
sync-queue
sync-queue
sync-queue
sync-queue
sync-queue
·
·
·
mainQueue
mainQueue
mainQueue
mainQueue
队列优先级
1. 自己创建的队列优先级要高于主队列
出现以上的结果是因为:
上面这段代码有创建了2个线程任务,其中有一个是同步,导致了主队列的死锁。所以第一个线程任务需要等待他的完成才能继续执行。
下面的例子中因为阻塞的不是主队列,所以可以打印任务是同步进行的。
dispatch_async(queue, ^{
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
[self logTask:@"async-DISPATCH_QUEUE_PRIORITY_LOW"];
});
});
dispatch_async(dispatch_get_main_queue(), ^{
[self logTask:@"mainQueue2"];
});
//打印-- (模拟结果)
mainQueue2
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_LOW
mainQueue2
DISPATCH_QUEUE_PRIORITY_LOW
·
·
·
mainQueue2
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_LOW
mainQueue2
DISPATCH_QUEUE_PRIORITY_LOW
另外,可以看出下面2个代码是不等价的
dispatch_async(dispatch_get_main_queue(), ^{
[self logTask:@"mainQueue2"];
});
[self logTask:@"mainQueue2"];
- 全局队列的优先级
DISPATCH_QUEUE_PRIORITY_HIGH >= DISPATCH_QUEUE_PRIORITY_DEFAULT > DISPATCH_QUEUE_PRIORITY_LOW > DISPATCH_QUEUE_PRIORITY_BACKGROUND
ps: 长耗时任务可能不会严格遵循优先级