您好,欢迎来到二三娱乐。
搜索
您的当前位置:首页多线程中的锁

多线程中的锁

来源:二三娱乐

ios中锁的种类

  1. NSLock
  2. synchronized 关键字
  3. pthread_mutex_t
  4. dispatch_semaphore_t (信号量)
  5. NSRecursiveLock递归锁
  6. NSConditionLock 条件锁
  7. NSCondition
  8. OSSpinLock 自旋锁
  9. pthread_mutex(recursive)]10. NSDistributedLock 分布式锁

NSLock

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    NSLock * lck = [[NSLock alloc]init];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        [lck lock ]; //上锁

        [self firstMethod];

        sleep(30);

        [lck unlock]; //解锁

    });
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        sleep(1); //确保线程1先执行

        [lck lock];

        [self secondMethod];

        [lck unlock];

    });

}
- (void)firstMethod
{
    NSLog(@"----%s",__func__);
}
- (void)secondMethod
{
    NSLog(@"----%s",__func__);
}
@end
  • 结果是secondMethod方法在线程1执行完毕30秒后才执行
  • NSLock 是 Cocoa 提供给我们最基本的锁对象,这也是我们经常所使用的,除 lock 和 unlock 方法外,NSLock 还提供了 tryLock 和 lockBeforeDate: 两个方法,前一个方法会尝试加锁,如果锁不可用(已经被锁住),并不会阻塞线程,直接返回 NO。lockBeforeDate: 方法会在所指定 Date 之前尝试加锁,如果在指定时间之前都不能加锁,则返回 NO

synchronized 关键字构建的锁

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        @synchronized(self) {
            [self firstMethod];

             sleep(30);
        }

    });
   //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        sleep(1); //确保线程1先执行

        @synchronized(self) {

            [self secondMethod];
        }

    });

}

- (void)firstMethod
{
    NSLog(@"----%s",__func__);
}
- (void)secondMethod
{
    NSLog(@"----%s",__func__);
}

  • @synchronized(objc) 使用objc为唯一标识,这个标识可以是任何对象,只有当标识相同时,才为满足互斥,如果线程2中的@synchronized(obj) 改为@synchronized(other) 时,线程2就不会被阻塞,@synchronized 指令实现锁的优点就是我们不需要在代码中显式的创建锁对象,便可以实现锁的机制,但作为一种预防措施,@synchronized 块会隐式的添加一个异常处理例程来保护代码,该处理例程会在异常抛出的时候自动的释放互斥锁。所以如果不想让隐式的异常处理例程带来额外的开销,你可以考虑使用锁对象。

使用C语言的 pthread_mutex_t 实现的锁

#import "ViewController.h"

#import <pthread.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    __block pthread_mutex_t mutex;

    pthread_mutex_init(&mutex,NULL);
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        pthread_mutex_lock(&mutex);

            [self firstMethod];

             sleep(30);
        pthread_mutex_unlock(&mutex);



    });
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        sleep(1); //确保线程1先执行

        pthread_mutex_lock(&mutex);

        [self secondMethod];

        pthread_mutex_unlock(&mutex);

    });

}
- (void)firstMethod
{
    NSLog(@"----%s",__func__);
}
- (void)secondMethod
{
    NSLog(@"----%s",__func__);
}
@end

使用 GCD 来实现的”锁”,信号量

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

    [super viewDidLoad];

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);


            [self firstMethod];

             sleep(5);

        dispatch_semaphore_signal(semaphore);

    });
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        sleep(1); //确保线程1先执行
         dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);


        [self secondMethod];

        sleep(5);

        dispatch_semaphore_signal(semaphore);

    });
    //线程3
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        sleep(2); //确保线程1,2先执行
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);


        NSLog(@"hello world");

        dispatch_semaphore_signal(semaphore);

    });
}
- (void)firstMethod
{
    NSLog(@"----%s",__func__);
}
- (void)secondMethod
{
    NSLog(@"----%s",__func__);
}
@end

output:
2016-07-18 23:40:10.562 test2[14274:1725000] -----[ViewController firstMethod]
2016-07-18 23:40:15.563 test2[14274:1725001] -----[ViewController secondMethod]
2016-07-18 23:40:20.568 test2[14274:1725002] hello world
  • dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); 如果semaphore计数大于等于1.计数-1,返回,程序继续运行。如果计数为0,则等待。这里设置的等待时间是一直等待。dispatch_semaphore_signal(semaphore);计数+1.在这两句代码中间的执行代码,每次只会允许一个线程进入,这样就有效的保证了在多线程环境下,只能有一个线程进入。
  • dispatch_semaphore_create的声明为:

dispatch_semaphore_t dispatch_semaphore_create(long value);传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量。

值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL。

  • dispatch_semaphore_signal的声明为:

    long dispatch_semaphore_signal(dispatch_semaphore_t dsema)

这个函数会使传入的信号量dsema的值加1

  • dispatch_semaphore_wait的声明为:

ong dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

  这个函数会使传入的信号量dsema的值减1;

  这个函数的作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;

  如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,

  不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,

  且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。

  如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。
- dispatch_semaphore_signal的返回值

    ispatch_semaphore_signal的返回值为long类型,当返回值为0时表示当前并没有线程等待其处理的信号量,其处理

  的信号量的值加1即可。当返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一

  个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。

  dispatch_semaphore_wait的返回值也为long型。当其返回0时表示在timeout之前,该函数所处的线程被成功唤醒。

  当其返回不为0时,表示timeout发生

##NSRecursiveLock递归锁
 - 平时我们在代码中使用锁的时候,最容易犯的一个错误就是造成死锁,而容易造成死锁的一种情形就是在递归或循环中

```objc
#import "ViewController.h"
@interface ViewController ()
@end

@implementation ViewController
- (void)viewDidLoad {

    [super viewDidLoad];

    NSRecursiveLock *lck = [[NSRecursiveLock alloc]init];

    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
    ^{

        static void(^blk)(NSInteger);

        blk = ^(NSInteger i)
        {
            [lck lock];

            if (i >0)
            {
                NSLog(@"递归,当前i的值为%zd",i);

                sleep(2);

                 blk(i-1);
            }

            [lck unlock];
        };
        blk(3);
      });
     //线程2
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
     ^{

        sleep(1); //确保线程1先执行

        [lck lock];

        [self secondMethod];

        [lck unlock];

    });
}
- (void)firstMethod
{
    NSLog(@"----%s",__func__);
}
- (void)secondMethod
{
    NSLog(@"----%s",__func__);
}
@end

output:
2016-07-19 00:17:04.633 test2[14346:1748726] 递归,当前i的值为3
2016-07-19 00:17:06.638 test2[14346:1748726] 递归,当前i的值为2
2016-07-19 00:17:08.642 test2[14346:1748726] 递归,当前i的值为1
2016-07-19 00:17:10.644 test2[14346:1748727] -----[ViewController secondMethod]

  • 上的代码中,就是一种典型的死锁情况,因为在线程1中的递归 block 中,锁会被多次的 lock,所以自己也被阻塞了,由于以上的代码非常的简短,所以很容易能识别死锁,但在较为复杂的代码中,就不那么容易发现了,那么如何在递归或循环中正确的使用锁呢?此处的 lock 变量如果换用 NSRecursiveLock 对象,问题便得到解决了,NSRecursiveLock 类定义的锁可以在同一线程多次 lock,而不会造成死锁。递归锁会跟踪它被多少次 lock。每次成功的 lock 都必须平衡调用 unlock 操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。

NSConditionLock 条件锁

  • 当我们在使用多线程的时候,有时一把只会 lock 和 unlock 的锁未必就能完全满足我们的使用。因为普通的锁只能关心锁与不锁,而不在乎用什么钥匙才能开锁,而我们在处理资源共享的时候,多数情况是只有满足一定条件的情况下才能打开这把锁
  • NSConditionLock 是锁,一旦一个线程获得锁,其他线程一定等待
  • [xxxx lock]; 表示 xxx 期待获得锁,如果没有其他线程获得锁(不需要判断内部的condition) 那它能执行此行以下代码,如果已经有其他线程获得锁(可能是条件锁,或者无条件锁),则等待,直至其他线程解锁
  • [xxx lockWhenCondition:A条件]; 表示如果没有其他线程获得该锁,但是该锁内部的condition不等于A条件,它依然不能获得锁,仍然等待。如果内部的condition等于A条件,并且没有其他线程获得该锁,则进入代码区,同时设置它获得该锁,其他任何线程都将等待它代码的完成,直至它解锁。
  • [xxx unlockWithCondition:A条件]; 表示释放锁,同时把内部的condition设置为A条件
  • return = [xxx lockWhenCondition:A条件 beforeDate:A时间]; 表示如果被锁定(没获得锁),并超过该时间则不再阻塞线程。但是注意:返回的值是NO,它没有改变锁的状态,这个函数的目的在于可以实现两种状态下的处理
  • test:
- (void)viewDidLoad {
    [super viewDidLoad];

    NSInteger HAS_DATA = 1;
    NSInteger NO_DATA = 0;

    NSMutableArray *products = [NSMutableArray array];

    NSConditionLock *lock = [[NSConditionLock alloc]initWithCondition:NO_DATA];

    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (1) {
            sleep(0.1); //让线程2先执行
            [lock lockWhenCondition:NO_DATA];
            [products addObject:[[NSObject alloc] init]];
            NSLog(@"produce a product,总量:%zi",products.count);
            [lock unlockWithCondition:HAS_DATA];

        }

    });
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (1) {
            NSLog(@"wait for product");
            [lock lockWhenCondition:HAS_DATA];
            [products removeAllObjects];
            NSLog(@"custome a product");
            [lock unlockWithCondition:NO_DATA];
        }

    });


}
output:
2016-07-19 12:22:26.299 test2[16530:2209739] wait for product
2016-07-19 12:22:26.299 test2[16530:2209742] produce a product,总量:1
2016-07-19 12:22:26.374 test2[16530:2209739] custome a product
2016-07-19 12:22:26.375 test2[16530:2209739] wait for product
2016-07-19 12:22:26.375 test2[16530:2209742] produce a product,总量:1
2016-07-19 12:22:26.376 test2[16530:2209739] custome a product
2016-07-19 12:22:26.376 test2[16530:2209739] wait for product
2016-07-19 12:22:26.376 test2[16530:2209742] produce a product,总量:1
2016-07-19 12:22:26.377 test2[16530:2209739] custome a product
...

NSCondition

  • 一种 最基本的条件锁。手动控制线程wait和signal。
  • [condition lock];一般用于多线程同时访问、修改同一个数据源,保证在同一时间内数据源只被访问、修改一次,其他线程的命令需要在lock 外等待,只到unlock ,才可访问
  • [condition unlock]与lock 同时使用
  • condition wait];让当前线程处于等待状态
  • [condition signal];CPU发信号告诉线程不用在等待,可以继续执行
  • test
- (void)viewDidLoad {
    [super viewDidLoad];
    NSCondition *condition = [[NSCondition alloc] init];

    NSMutableArray *products = [NSMutableArray array];
    //线程1
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (1) {
            [condition lock];
            if ([products count] == 0) {
                NSLog(@"wait for product");
                [condition wait];
            }
            [products removeObjectAtIndex:0];
            NSLog(@"custome a product");
            [condition unlock];
        }

    });
    //线程2
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (1) {
            sleep(1);// 让线程1线先执行
            [condition lock];
            [products addObject:[[NSObject alloc] init]];
            NSLog(@"produce a product,总量:%zi",products.count);
            [condition signal];
            sleep(5);
            NSLog(@"1");//测试NSlog先执行还是线程1
            [condition unlock];

        }

    });

}
output:
2016-07-19 14:30:39.360 test2[16886:2307736] wait for product
2016-07-19 14:30:40.362 test2[16886:2307733] produce a product,总量:1
2016-07-19 14:30:45.366 test2[16886:2307733] 1
2016-07-19 14:30:45.367 test2[16886:2307736] custome a product
2016-07-19 14:30:45.367 test2[16886:2307736] wait for product
2016-07-19 14:30:46.369 test2[16886:2307733] produce a product,总量:1
2016-07-19 14:30:51.374 test2[16886:2307733] 1
2016-07-19 14:30:51.374 test2[16886:2307736] custome a product
2016-07-19 14:30:51.375 test2[16886:2307736] wait for product
2016-07-19 14:30:52.376 test2[16886:2307733] produce a product,总量:1

OSSpinLock

  • OSSpinLock 自旋锁,性能最高的锁。原理很简单,就是一直 do while 忙等。它的缺点是当等待时会消耗大量 CPU 资源,所以它不适用于较长时间的任务
  • test
#import "ViewController.h"
#import <libkern/OSAtomic.h>
@interface ViewController ()
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    __block OSSpinLock theLock = OS_SPINLOCK_INIT;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        OSSpinLockLock(&theLock);
        NSLog(@"hello world 开始");
        sleep(3);
        NSLog(@"hello world 结束");
        OSSpinLockUnlock(&theLock);

    });

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        OSSpinLockLock(&theLock);
        NSLog(@"see goodbye");
        OSSpinLockUnlock(&theLock);

    });


}
@end
output:
2016-07-19 15:07:13.985 test2[17294:2334857] hello world 开始
2016-07-19 15:07:16.986 test2[17294:2334857] hello world 结束
2016-07-19 15:07:16.988 test2[17294:2334858] see goodbye

pthread_mutex(recursive)]

  • pthread_mutex锁的递归版本
  • test :
#import "ViewController.h"
#import <pthread/pthread.h>

@interface ViewController ()
@end
@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    __block pthread_mutex_t theLock;
    //pthread_mutex_init(&theLock, NULL);

    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&theLock, &attr);
    pthread_mutexattr_destroy(&attr);

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        static void (^RecursiveMethod)(int);

        RecursiveMethod = ^(int value) {

            pthread_mutex_lock(&theLock);
            if (value > 0) {

                NSLog(@"value = %d", value);
                sleep(1);
                RecursiveMethod(value - 1);
            }
            pthread_mutex_unlock(&theLock);
        };

        RecursiveMethod(5);
    });


}
@end
output:
2016-07-19 15:13:43.205 test2[17304:2339141] value = 4
2016-07-19 15:13:44.208 test2[17304:2339141] value = 3
2016-07-19 15:13:45.213 test2[17304:2339141] value = 2
2016-07-19 15:13:46.218 test2[17304:2339141] value = 1

NSDistributedLock 分布式锁

  • 个时候我们就需要使用到 NSDistributedLock 了,从它的类名就知道这是一个分布式的 Lock,NSDistributedLock 的实现是通过文件系统的,所以使用它才可以有效的实现不同进程之间的互斥,但 NSDistributedLock 并非继承于 NSLock,它没有 lock 方法,它只实现了 tryLock, unlock, breakLock,所以如果需要 lock 的话,你就必须自己实现一个 tryLock
  • 暂时没遇到,以后补充实例用法

效率

  • 除了 OSSpinLock 外,dispatch_semaphore 和 pthread_mutex 性能是最高的,但是OSSpinLock不安全,dispatch_semaphore 不能递归,@synchronized和NSConditionLock效率较差

  • 图个方便的可以使用@synchronized

Copyright © 2019- yule263.com 版权所有 湘ICP备2023023988号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务