1、程安全隐患
当多个线程同时访问一块资源时,很容易产生数据错乱和数据安全问题,比如多个线程读取/写入同一个对象、同一个变量、同一个文件等。
2、问题举例
2.1、存钱取钱问题
当多条线程同时进行存钱、取钱行为,就会出现余额不正确的问题,如图所示:
- (void)moneyTest {
self.money = 500;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 每次存入50,存10次,共500快
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self saveMoney];
}
});
// 每次取出20,存10次,共200快
dispatch_async(queue, ^{
for (int i = 0; i < 10; i++) {
[self drawMoney];
}
});
}
- (void)saveMoney {
int oldMoney = self.money;
sleep(.2);
oldMoney += 50;
self.money = oldMoney;
NSLog(@"存入50还剩%d元",self.money);
}
- (void)drawMoney {
int oldMoney = self.money;
sleep(.2);
oldMoney -= 20;
self.money = oldMoney;
NSLog(@"取出20还剩%d元",self.money);
}
打印结果:
~: 取出20还剩480元
~: 存入50还剩550元
~: 取出20还剩530元
~: 存入50还剩580元
~: 取出20还剩560元
~: 存入50还剩610元
~: 取出20还剩590元
~: 存入50还剩640元
~: 取出20还剩620元
~: 存入50还剩670元
~: 取出20还剩650元
~: 存入50还剩700元
~: 取出20还剩680元
~: 存入50还剩730元
~: 取出20还剩710元
~: 存入50还剩760元
~: 取出20还剩740元
~: 存入50还剩790元
~: 取出20还剩770元
~: 存入50还剩820元
期望剩余应该是 500 + 500 - 200 = 800元。
2.2、卖票问题
当多条线程同时进行卖票操作时,就会出现余票不正确问题,如图所示:
- (void)saleTickets {
self.ticketsCount = 15;
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
}
- (void)saleTicket {
int oldTicketsCount = self.ticketsCount;
sleep(.2);
oldTicketsCount--;
self.ticketsCount = oldTicketsCount;
NSLog(@"剩余%d张票", self.ticketsCount);
}
打印结果:
~: 剩余14张票
~: 剩余13张票
~: 剩余14张票
~: 剩余12张票
~: 剩余12张票
~: 剩余12张票
~: 剩余11张票
~: 剩余10张票
~: 剩余9张票
~: 剩余8张票
~: 剩余7张票
~: 剩余6张票
~: 剩余5张票
~: 剩余4张票
~: 剩余3张票
3、解决方案
使用线程同步技术(同步,就是协同步调,按预定的先后次序进行),常见的线程同步技术是:加锁
比如上面提到的存钱取钱问题,在存钱之前先加锁,保证数据只有当前线程可以访问,在存钱结束时解锁。这时取钱线程访问数据,也进行加锁解锁操作,这样就能保证数据读写的安全性。