iOS开发中多线程间关于锁的使用
为什么需要使用锁,当然熟悉多线程的你,自然不会感到陌生。
那你在代码中是否很好的使用了锁的机制呢?你又知道几种实现锁的方法呢?
main.m

1 int main(int argc, const char * argv[]) {
2 @autoreleasepool {
3 //普通用法;会看到线程1锁住之后,线程2会一直等待,直到线程1执行完,线程2才执行
4 NSLog(@"使用NSLock(普通锁;已实现NSLocking协议)实现锁");
5 [LockByNSLock executeLock];
6
7 NSLog(@"使用Synchronized指令实现锁");
8 [LockBySynchronized executeLock];
9
10 NSLog(@"使用C语言的pthread_mutex_t实现锁");
11 [LockByPthreadMutexT executeLock];
12
13 NSLog(@"使用GCD的dispatch_semaphore_t(信号量)实现锁");
14 [LockByDispatchSemaphoreT executeLock];
15
16
17 //高级用法
18 NSLog(@"使用NSRecursiveLock(递归锁;已实现NSLocking协议)实现锁;可以在递归场景中使用。如果使用NSLock,会出现死锁");
19 [LockByNSRecursiveLock executeLock];
20
21 NSLog(@"使用NSConditionLock(条件锁;已实现NSLocking协议)实现锁;可以在需要符合条件才进行锁操作的场景中使用");
22 [LockByNSConditionLock executeLock];
23
24 NSLog(@"使用NSDistributedLock(分布式锁;区别其他锁类型,它没有实现NSLocking协议)实现锁;它基于文件系统,会自动创建用于标识的临时文件或文件夹,执行完后自动清除临时文件或文件夹;可以在多个进程或多个程序之间需要构建互斥的场景中使用");
25 [LockByNSDistributedLock executeLock]; //这里看不出效果,具体测试应该是线程1和线程2同时进行,即是在多个进程或多个程序之间需要构建互斥的场景下
26 }
27 return 0;
28 }

今天一起来探讨一下 iOS 中实现锁的几种不同方式,在这之前我们先构建一个测试用的类,假想它是我们的一个共享资源,firstMethod 与 secondMethod 是互斥的,代码如下:

1 #import "TestObj.h"
2
3 @implementation TestObj
4
5 - (void)firstMethod {
6 NSLog(@"Execute %@", NSStringFromSelector(_cmd));
7 }
8
9 - (void)secondMethod {
10 NSLog(@"Execute %@", NSStringFromSelector(_cmd));
11 }
12
13 @end

1.使用 NSLock 实现的锁

1 #import "LockByNSLock.h"
2 #import "TestObj.h"
3
4 @implementation LockByNSLock
5
6 + (void)executeLock {
7 //主线程中
8 TestObj *obj = [[TestObj alloc] init];
9 NSLock *lock = [[NSLock alloc] init];
10
11 //线程1
12 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
13 [lock lock];
14 [obj firstMethod];
15 sleep(2); //线程1执行挂起2秒
16 [lock unlock];
17 });
18
19 //线程2
20 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
21 sleep(1); //线程2执行挂起1秒,以保证一定是线程1先执行
22 [lock lock];
23 [obj secondMethod];
24 [lock unlock];
25 });
26 }
27
28 @end

看到打印的结果了吗,你会看到线程1锁住之后,线程2会一直等待走到线程1将锁置为 unlock 后,才会执行 secondMethod 方法。
NSLock 是 Cocoa 提供给我们最基本的锁对象,这也是我们经常所使用的,除 lock 和 unlock 方法外,NSLock 还提供了 tryLock 和 lockBeforeDate: 两个方法,前一个方法会尝试加锁,如果锁不可用(已经被锁住),并不会阻塞线程,直接返回 NO。lockBeforeDate: 方法会在所指定 Date 之前尝试加锁,如果在指定时间之前都不能加锁,则返回 NO。
2.使用 synchronized 关键字构建的锁
当然在 Objective-C 中你还可以用 @synchronized 指令快速的实现锁:

1 #import "LockBySynchronized.h"
2 #import "TestObj.h"
3
4 @implementation LockBySynchronized
5
6 + (void)executeLock {
7 //主线程中
8 TestObj *obj = [[TestObj alloc] init];
9
10 //线程1
11 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
12 @synchronized(obj) {
13 [obj firstMethod];
14 sleep(2); //线程1执行挂起2秒
15 }
16 });
17
18 //线程2
19 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
20 @synchronized(obj) {
21 sleep(1); //线程2执行挂起1秒,以保证一定是线程1先执行
22 [obj secondMethod];
23 }
24 });
25 }
26
27 @end

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

1 #import "LockByPthreadMutexT.h"
2 #import "TestObj.h"
3 #include <pthread.h>
4
5 @implementation LockByPthreadMutexT
6
7 + (void)executeLock {
8 //主线程中
9 TestObj *obj = [[TestObj alloc] init];
10 __block pthread_mutex_t mutex;
11 pthread_mutex_init(&mutex, NULL);
12
13 //线程1
14 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
15 pthread_mutex_lock(&mutex);
16 [obj firstMethod];
17 sleep(2); //线程1执行挂起2秒
18 pthread_mutex_unlock(&mutex);
19 });
20
21 //线程2
22 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
23 sleep(1); //线程2执行挂起1秒,以保证一定是线程1先执行
24 pthread_mutex_lock(&mutex);
25 [obj secondMethod];
26 pthread_mutex_unlock(&mutex);
27 });
28 }
29
30 @end

pthread_mutex_t 定义在pthread.h,所以记得 #include
4.使用 GCD 来实现的”锁”
以上代码构建多线程我们就已经用到了 GCD 的 dispatch_async 方法,其实在 GCD 中也已经提供了一种信号机制,使用它我们也可以来构建一把”锁”
从本质意义上讲,信号量与互斥锁是有区别:
(1)作用域
信号量:进程间或线程间(linux 仅线程间)
互斥锁:线程间
(2)上锁时
信号量:只要信号量的 value 大于0,其他线程就可以 sem_wait 成功,成功后信号量的 value 减一。若 value 值不大于0,则 sem_wait 阻塞,直到 sem_post 释放后 value 值加一。一句话,信号量的 value>=0。
互斥锁:只要被锁住,其他任何线程都不可以访问被保护的资源。如果没有锁,获得资源成功,否则进行阻塞等待资源可用。一句话,线程互斥锁的 vlaue 可以为负数。

1 #import "LockByDispatchSemaphoreT.h"
2 #import "TestObj.h"
3
4 @implementation LockByDispatchSemaphoreT
5
6 + (void)executeLock {
7 //主线程中
8 TestObj *obj = [[TestObj alloc] init];
9 dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
10
11 //线程1
12 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
13 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
14 [obj firstMethod];
15 sleep(2); //线程1执行挂起2秒
16 dispatch_semaphore_signal(semaphore);
17 });
18
19 //线程2
20 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
21 sleep(1); //线程2执行挂起1秒,以保证一定是线程1先执行
22 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
23 [obj secondMethod];
24 dispatch_semaphore_signal(semaphore);
25 });
26 }
27
28 @end

至于代码产生的效果当然和前面的是一模一样的,当然锁大多数情况下也是配合多线程一起使用的。
锁的高级用法
1.NSRecursiveLock递归锁
平时我们在代码中使用锁的时候,最容易犯的一个错误就是造成死锁,而容易造成死锁的一种情形就是在递归或循环中,如下代码:

1 #import "LockByNSRecursiveLock.h"
2 #import "TestObj.h"
3
4 @implementation LockByNSRecursiveLock
5
6 + (void)executeLock {
7 //主线程中
8 TestObj *obj = [[TestObj alloc] init];
9 //NSLock *lock = [[NSLock alloc] init]; //NSLock在递归场景会出现死锁的情况,这里就得用NSRecursiveLock(递归锁)
10 NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
11
12 //线程1
13 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
14 static void(^recursiveMethod)(NSUInteger);
15 recursiveMethod = ^(NSUInteger val) {
16 [lock lock];
17 if (val > 0) {
18 NSLog(@"递归中,val的值为:%lu", (unsigned long)val);
19 [obj firstMethod];
20 sleep(2); //线程1执行挂起2秒
21 recursiveMethod(val-1);
22 }
23 [lock unlock];
24 };
25
26 recursiveMethod(5);
27 });
28
29 //线程2
30 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
31 sleep(1); //线程2执行挂起1秒,以保证一定是线程1先执行
32 [lock lock];
33 [obj secondMethod];
34 [lock unlock];
35 });
36 }
37
38 @end

以上的代码中,就是一种典型的死锁情况,因为在线程1中的递归 block 中,锁会被多次的 lock,所以自己也被阻塞了,由于以上的代码非常的简短,所以很容易能识别死锁,但在较为复杂的代码中,就不那么容易发现了,那么如何在递归或循环中正确的使用锁呢?此处的 lock 变量如果换用 NSRecursiveLock 对象,问题便得到解决了,NSRecursiveLock 类定义的锁可以在同一线程多次 lock,而不会造成死锁。递归锁会跟踪它被多少次 lock。每次成功的 lock 都必须平衡调用 unlock 操作。只有所有的锁住和解锁操作都平衡的时候,锁才真正被释放给其他线程获得。
2.NSConditionLock 条件锁
当我们在使用多线程的时候,有时一把只会 lock 和 unlock 的锁未必就能完全满足我们的使用。因为普通的锁只能关心锁与不锁,而不在乎用什么钥匙才能开锁,而我们在处理资源共享的时候,多数情况是只有满足一定条件的情况下才能打开这把锁:

1 #import "LockByNSConditionLock.h"
2 #import "TestObj.h"
3
4 @implementation LockByNSConditionLock
5
6 + (void)executeLock {
7 //主线程中
8 TestObj *obj = [[TestObj alloc] init];
9 NSConditionLock *lock = [[NSConditionLock alloc] init];
10
11 //线程1
12 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
13 for (NSUInteger i=0; i<3; i++) {
14 [lock lock];
15 NSLog(@"循环中,i的值为:%lu", (unsigned long)i);
16 [obj firstMethod];
17 sleep(2); //线程1执行挂起2秒
18 [lock unlockWithCondition:i];
19 }
20 });
21
22 //线程2
23 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
24 sleep(1); //线程2执行挂起1秒,以保证一定是线程1先执行
25 //[lock lockWhenCondition:2]; //需要标识为2的钥匙才能进行加锁操作,这里等待线程1对lock的循环操作达到它需要的加锁条件;如果最终不符合条件(例如:线程1的条件判断是i<4时),将阻塞线程内容向下执行;这里就得用tryLockWhenCondition:方法控制
26 //[obj secondMethod];
27 //[lock unlock];
28
29 BOOL isLocked = [lock tryLockWhenCondition:2];
30 [obj secondMethod];
31 if (isLocked) { //加锁解锁必须成对操作,否则会报错
32 [lock unlock];
33 }
34 });
35 }
36
37 @end

在线程1中的加锁使用了 lock,所以是不需要条件的,所以顺利的就锁住了,但在 unlock 的使用了一个整型的条件,它可以开启其他线程中正在等待这把钥匙的临界地,而线程2则需要一把被标识为2的钥匙,所以当线程1循环到最后一次的时候,才最终打开了线程2中的阻塞。但即便如此,NSConditionLock 也跟其他的锁一样,是需要 lock 与 unlock 对应的,只是 lock,lockWhenCondition: 与 unlock,unlockWithCondition: 是可以随意组合的,当然这是与你的需求相关的。
3.NSDistributedLock 分布式锁
以上所有的锁都是在解决多线程之间的冲突,但如果遇上多个进程或多个程序之间需要构建互斥的情景该怎么办呢?这个时候我们就需要使用到 NSDistributedLock 了,从它的类名就知道这是一个分布式的 Lock,NSDistributedLock 的实现是通过文件系统的,所以使用它才可以有效的实现不同进程之间的互斥,但 NSDistributedLock 并非继承于 NSLock,它没有 lock 方法,它只实现了 tryLock, unlock, breakLock,所以如果需要 lock 的话,你就必须自己实现一个 tryLock 的轮询,下面通过代码简单的演示一下吧:

1 #import "LockByNSDistributedLock.h"
2 #import "TestObj.h"
3
4 @implementation LockByNSDistributedLock
5
6 + (void)executeLock {
7 //主线程中
8 TestObj *obj = [[TestObj alloc] init];
9 NSDistributedLock *lock = [[NSDistributedLock alloc] initWithPath:@"/Users/Kenmu/Desktop/Temp/LockByNSDistributedLock"];
10
11 //线程1
12 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
13 [lock breakLock];
14 [lock tryLock];
15 [obj firstMethod];
16 sleep(5); //线程1执行挂起5秒
17 [lock unlock];
18 });
19
20 //线程2
21 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
22 while (![lock tryLock]) {
23 NSLog(@"Waiting...");
24 sleep(1);
25 }
26 [obj secondMethod];
27 [lock unlock];
28 });
29 }
30
31 @end

实际场景应该如下:
程序A:

1 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
2 lock = [[NSDistributedLock alloc] initWithPath:@"/Users/Kenmu/Desktop/earning__"];
3 [lock breakLock];
4 [lock tryLock];
5 sleep(10);
6 [lock unlock];
7 NSLog(@"appA: OK");
8 });

程序B:

1 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
2 lock = [[NSDistributedLock alloc] initWithPath:@"/Users/Kenmu/Desktop/earning__"];
3 while (![lock tryLock]) {
4 NSLog(@"appB: waiting");
5 sleep(1);
6 }
7 [lock unlock];
8 NSLog(@"appB: OK");
9 });

先运行程序 A,然后立即运行程序 B,根据打印你可以清楚的发现,当程序 A 刚运行的时候,程序 B 一直处于等待中,当大概10秒过后,程序 B 便打印出了 appB:OK 的输出,以上便实现了两上不同程序之间的互斥。/Users/Kenmu/Desktop/earning __是一个文件或文件夹的地址,如果该文件或文件夹不存在,那么在 tryLock 返回 YES 时,会自动创建该文件/文件夹。在结束的时候该文件/文件夹会被清除,所以在选择的该路径的时候,应该选择一个不存在的路径,以防止误删了文件。
结果:

1 2015-05-15 00:29:43.046 OCLock[1675:61800] 使用NSLock(普通锁;已实现NSLocking协议)实现锁
2 2015-05-15 00:29:43.047 OCLock[1675:61800] Execute firstMethod
3 2015-05-15 00:29:46.055 OCLock[1675:61800] Execute secondMethod
4 2015-05-15 00:29:46.055 OCLock[1675:61800] 使用Synchronized指令实现锁
5 2015-05-15 00:29:46.055 OCLock[1675:61800] Execute firstMethod
6 2015-05-15 00:29:49.060 OCLock[1675:61800] Execute secondMethod
7 2015-05-15 00:29:49.061 OCLock[1675:61800] 使用C语言的pthread_mutex_t实现锁
8 2015-05-15 00:29:49.061 OCLock[1675:61800] Execute firstMethod
9 2015-05-15 00:29:52.067 OCLock[1675:61800] Execute secondMethod
10 2015-05-15 00:29:52.067 OCLock[1675:61800] 使用GCD的dispatch_semaphore_t(信号量)实现锁
11 2015-05-15 00:29:52.068 OCLock[1675:61800] Execute firstMethod
12 2015-05-15 00:29:55.078 OCLock[1675:61800] Execute secondMethod
13 2015-05-15 00:29:55.079 OCLock[1675:61800] 使用NSRecursiveLock(递归锁;已实现NSLocking协议)实现锁;可以在递归场景中使用。如果使用NSLock,会出现死锁
14 2015-05-15 00:29:55.079 OCLock[1675:61800] 递归中,val的值为:5
15 2015-05-15 00:29:55.079 OCLock[1675:61800] Execute firstMethod
16 2015-05-15 00:29:57.080 OCLock[1675:61800] 递归中,val的值为:4
17 2015-05-15 00:29:57.080 OCLock[1675:61800] Execute firstMethod
18 2015-05-15 00:29:59.083 OCLock[1675:61800] 递归中,val的值为:3
19 2015-05-15 00:29:59.084 OCLock[1675:61800] Execute firstMethod
20 2015-05-15 00:30:01.089 OCLock[1675:61800] 递归中,val的值为:2
21 2015-05-15 00:30:01.089 OCLock[1675:61800] Execute firstMethod
22 2015-05-15 00:30:03.095 OCLock[1675:61800] 递归中,val的值为:1
23 2015-05-15 00:30:03.095 OCLock[1675:61800] Execute firstMethod
24 2015-05-15 00:30:06.106 OCLock[1675:61800] Execute secondMethod
25 2015-05-15 00:30:06.106 OCLock[1675:61800] 使用NSConditionLock(条件锁;已实现NSLocking协议)实现锁;可以在需要符合条件才进行锁操作的场景中使用
26 2015-05-15 00:30:06.107 OCLock[1675:61800] 循环中,i的值为:0
27 2015-05-15 00:30:06.107 OCLock[1675:61800] Execute firstMethod
28 2015-05-15 00:30:08.112 OCLock[1675:61800] 循环中,i的值为:1
29 2015-05-15 00:30:08.113 OCLock[1675:61800] Execute firstMethod
30 2015-05-15 00:30:10.115 OCLock[1675:61800] 循环中,i的值为:2
31 2015-05-15 00:30:10.115 OCLock[1675:61800] Execute firstMethod
32 2015-05-15 00:30:13.121 OCLock[1675:61800] Execute secondMethod
33 2015-05-15 00:30:13.121 OCLock[1675:61800] 使用NSDistributedLock(分布式锁;区别其他锁类型,它没有实现NSLocking协议)实现锁;它基于文件系统,会自动创建用于标识的临时文件或文件夹,执行完后自动清除临时文件或文件夹;可以在多个进程或多个程序之间需要构建互斥的场景中使用
34 2015-05-15 00:30:13.127 OCLock[1675:61800] Execute firstMethod
35 2015-05-15 00:30:18.129 OCLock[1675:61800] Execute secondMethod
iOS开发中多线程间关于锁的使用的更多相关文章
- iOS开发中多线程基础
耗时操作演练 代码演练 编写耗时方法 - (void)longOperation { for (int i = 0; i < 10000; ++i) { NSLog(@"%@ %d&q ...
- IOS开发中多线程的使用
一.创建多线程的五种方式 1.开启线程的方法一 NSThread * thread=[[NSThread alloc] initWithTarget:self selector:@selector(_ ...
- iOS开发中多线程断点下载大文件
主要思想,就是创建一个与目标文件等大小的空白文件,然后分段往这个空白文件中写入数据. 可以通过发送HEAD请求,获得服务器中文件的具体大小,然后再将这样的长度分割成若干等大的数据块,在发送get请求时 ...
- iOS开发之多线程技术
本篇争取一篇讲清讲透,依然将通过四大方面清晰的对iOS开发中多线程的用法进行详尽的讲解: 一.什么是多线程 1)多线程执行原理 2)线程与进程 3)多线程的优缺点 二.我们为什么要用多线程编程技术 三 ...
- ios开发之多线程---GCD
一:基本概念 1:进程:正在运行的程序为进程. 2:线程:每个进程要想执行任务必须得有线程,进程中任务的执行都是在线程中. 3:线程的串行:一条线程里任务的执行都是串行的,假如有一个进程开辟了一条线程 ...
- 多线程在iOS开发中的应用
多线程基本概念 01 进程 进程是指在系统中正在运行的一个应用程序.每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内. 02 线程 2-1 基本概念 1个进程要想执行任务,必须得有线程 ...
- 深入理解 iOS 开发中的锁
来源:伯乐在线 - 夏天然后 链接:http://ios.jobbole.com/89474/ 点击 → 申请加入伯乐在线专栏作者 摘要 本文的目的不是介绍 iOS 中各种锁如何使用,一方面笔者没有大 ...
- iOS开发之线程间的MachPort通信与子线程中的Notification转发
如题,今天的博客我们就来记录一下iOS开发中使用MachPort来实现线程间的通信,然后使用该知识点来转发子线程中所发出的Notification.简单的说,MachPort的工作方式其实是将NSMa ...
- iOS开发中各种关键字的区别
1.一些概念 1.浅Copy:指针的复制,只是多了一个指向这块内存的指针,共用一块内存. 深Copy:内存的复制,两块内存是完全不同的, 也就是两个对象指针分别指向不同的内存,互不干涉. 2.atom ...
随机推荐
- vs2010下C++调用lib或dll文件
注: DLL:表示链接库,包含dll,lib文件: dll: 表示my.dll文件 lib: 表示my.lib文件 C++ 调用.lib的方法: 一: 隐式的加载时链接,有三种方法 1 设置工程的 ...
- java 遍历map 方法 集合 五种的方法
package com.jackey.topic; import java.util.ArrayList;import java.util.HashMap;import java.util.Itera ...
- Visual Studio最好用的快捷键
当然每个人常用的一般都会有些不一样,欢迎大家评论说出自己常用或最常用的快捷键吧,比比看谁用的巧~~~ ctrl+-(shift+ctrl+-):移动光标到上次位置或相反,比如定位一个函数,转到函数定义 ...
- 使用Javah 生成C/C++头文件
注意:编写java的接口文件. 注意native代码端一定不要有大括号,且要有“:”结尾. public native int add(int x ,int y); 1. 需要让eclipse自动编译 ...
- C语言fmod()函数:对浮点数取模(求余)
头文件:#include <math.h> fmod() 用来对浮点数进行取模(求余),其原型为: double fmod (double x); 设返回值为 ret,那么 x = ...
- SQL Server 2008通过LinkServer连接MySQL
链接过程就不过多描述了,搜索下都有一大堆的内容. 链接成功以后,如何调用的问题,通过“编写select脚本”的方式生成的脚本如下: [备注:asset_manager是数据库名,admin是表名] - ...
- Extjs 百度地图扩展
var bmapps; Bsprint.EditMapInfoWindow = Ext.extend(Ext.Window, { form: null, constructor: function ( ...
- 【小错误】SP2-0618: Cannot find the Session Identifier. Check PLUSTRACE role is enabled
1.今天在scott用户下执行语句跟踪时报了如下错误: SCOTT@ORA11GR2>set autotrace on SP2: Cannot find the Session Identifi ...
- iOS 为iPhone和iPad创建不同的storyboard
复制Main.storyboard,重命名为Main_iPad.storyboard 在info.plist文件中添加 Main storyboard file base name (iPad) -- ...
- Ubuntu Kylin 14.04-修改IP固定地址
前言:今天我们办公室的网络不稳定,隔一会时间就断掉,后来请负责网络的同事来处理了一下,说是路由器的有些配置起冲突了,所以他将IP地址做了一些修改,比如:原IP是192.168.0.110 ...