我们常说线程安全,那么什么是线程安全呢?
其实线程安全主要指我们在多线程处理过程中,让结果与我们的预期一致,不导致异常case。
譬如
static NSUInteger index = 0;
for (NSInteger i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
index++;
});
}
我们预期最后得到的结果是1000,但实际结果肯定不是1000,所以这是线程不安全的
这里为什么结果不是1000呢?
因为index++
实际上可以翻译为index = index + 1
,这里是分为三步走的,先读,再计算,在写入,那么同时开辟大量线程来执行的话,那么可能存在多个线程第一步读取的时候读的是同一个值,计算后得到的也是同一个值,再存储还是同一个值,这里没有保证每个线程第一步读取的时候拿到的值肯定是上一个线程写入后的值,所以导致最后的结果异常。
怎么保证线程安全呢?
加锁,这里不赘述了
线程不安全会引起程序crash吗?
其实要理解这个问题,首先要理解在对一个变量做操作的时候都干了什么事情,接下来先简单阐述下index++
这行代码,在代码运行的时候都做了什么事情,如下:
1.运行到这里的时候,index分配在栈内存上,这里是个虚拟内存
2.从虚拟内存读取到寄存器
3.进行加法计算
4.将结果写回到虚拟内存
在index++
处加个断点,输入dis -p
查看汇编可以得到汇编指令如下:
-> 0x10228c2e7 <+55>: movq -0x28(%rbp), %rcx 写回去
0x10228c2eb <+59>: addq $0x1, %rcx 计算+1
0x10228c2ef <+63>: movq %rcx, -0x28(%rbp) 先读取
那么回到问题本身,对于一个int类型的数据,在多线程里反复读写,其实是不是产生crash的。
因为对于一个寄存器来说,它都是根据电信号来处理,也就是说它处理数据肯定都是线性的,而且写入和读取也不会因为多线程而造成异常。
但是 对于一些线程不安全的数据需要客户端保证线程安全,否则会crash
为什么这么说呢?这里举一个数组的例子:
NSMutableArray *count = @[@(1)].mutableCopy;
dispatch_queue_t queue = dispatch_queue_create("writeThread", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 1000; i++) {
dispatch_async(queue, ^{
[count insertObject:@(0) atIndex:0];
});
}
这里会产生 signal SIGABRT
崩溃,输出如下:
test(67696,0x700004c08000) malloc: *** error for object 0x7faf7bd02a00: pointer being freed was not allocated
test(67696,0x700004c08000) malloc: *** set a breakpoint in malloc_error_break to debug
thread #4, queue = 'writeThread', stop reason = signal SIGABRT
frame #0: 0x00007fff523bc2c6 libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x00007fff52462bf1 libsystem_pthread.dylib`pthread_kill + 284
frame #2: 0x00007fff5234ba5c libsystem_c.dylib`abort + 120
这里可以看到系统调用了 abort 方法,结束了这种异常。为什么系统不让对数组进行多线程读写呢?
因为多线程读写访问的是同一个变量,如果某个线程移除了某个对象,而另外一个线程因为用的临时数据还是原来的,那么就会造成数组越界等crash,所以对于一些类型,如果多线程读写会造成一些crash问题。