1、程安全隐患

当多个线程同时访问一块资源时,很容易产生数据错乱和数据安全问题,比如多个线程读取/写入同一个对象、同一个变量、同一个文件等。

2、问题举例

2.1、存钱取钱问题

当多条线程同时进行存钱、取钱行为,就会出现余额不正确的问题,如图所示:
image.png

  1. - (void)moneyTest {
  2. self.money = 500;
  3. dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
  4. // 每次存入50,存10次,共500快
  5. dispatch_async(queue, ^{
  6. for (int i = 0; i < 10; i++) {
  7. [self saveMoney];
  8. }
  9. });
  10. // 每次取出20,存10次,共200快
  11. dispatch_async(queue, ^{
  12. for (int i = 0; i < 10; i++) {
  13. [self drawMoney];
  14. }
  15. });
  16. }
  17. - (void)saveMoney {
  18. int oldMoney = self.money;
  19. sleep(.2);
  20. oldMoney += 50;
  21. self.money = oldMoney;
  22. NSLog(@"存入50还剩%d元",self.money);
  23. }
  24. - (void)drawMoney {
  25. int oldMoney = self.money;
  26. sleep(.2);
  27. oldMoney -= 20;
  28. self.money = oldMoney;
  29. NSLog(@"取出20还剩%d元",self.money);
  30. }

打印结果:

  1. ~: 取出20还剩480
  2. ~: 存入50还剩550
  3. ~: 取出20还剩530
  4. ~: 存入50还剩580
  5. ~: 取出20还剩560
  6. ~: 存入50还剩610
  7. ~: 取出20还剩590
  8. ~: 存入50还剩640
  9. ~: 取出20还剩620
  10. ~: 存入50还剩670
  11. ~: 取出20还剩650
  12. ~: 存入50还剩700
  13. ~: 取出20还剩680
  14. ~: 存入50还剩730
  15. ~: 取出20还剩710
  16. ~: 存入50还剩760
  17. ~: 取出20还剩740
  18. ~: 存入50还剩790
  19. ~: 取出20还剩770
  20. ~: 存入50还剩820

期望剩余应该是 500 + 500 - 200 = 800元。

2.2、卖票问题

当多条线程同时进行卖票操作时,就会出现余票不正确问题,如图所示:
image.png

  1. - (void)saleTickets {
  2. self.ticketsCount = 15;
  3. dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
  4. dispatch_async(queue, ^{
  5. for (int i = 0; i < 5; i++) {
  6. [self saleTicket];
  7. }
  8. });
  9. dispatch_async(queue, ^{
  10. for (int i = 0; i < 5; i++) {
  11. [self saleTicket];
  12. }
  13. });
  14. dispatch_async(queue, ^{
  15. for (int i = 0; i < 5; i++) {
  16. [self saleTicket];
  17. }
  18. });
  19. }
  20. - (void)saleTicket {
  21. int oldTicketsCount = self.ticketsCount;
  22. sleep(.2);
  23. oldTicketsCount--;
  24. self.ticketsCount = oldTicketsCount;
  25. NSLog(@"剩余%d张票", self.ticketsCount);
  26. }

打印结果:

  1. ~: 剩余14张票
  2. ~: 剩余13张票
  3. ~: 剩余14张票
  4. ~: 剩余12张票
  5. ~: 剩余12张票
  6. ~: 剩余12张票
  7. ~: 剩余11张票
  8. ~: 剩余10张票
  9. ~: 剩余9张票
  10. ~: 剩余8张票
  11. ~: 剩余7张票
  12. ~: 剩余6张票
  13. ~: 剩余5张票
  14. ~: 剩余4张票
  15. ~: 剩余3张票

期望余票应该是15 - 5 - 5 - 5 = 0张票。

3、解决方案

使用线程同步技术(同步,就是协同步调,按预定的先后次序进行),常见的线程同步技术是:加锁
比如上面提到的存钱取钱问题,在存钱之前先加锁,保证数据只有当前线程可以访问,在存钱结束时解锁。这时取钱线程访问数据,也进行加锁解锁操作,这样就能保证数据读写的安全性。
image.png