iOS多线程Pthreads篇

3,079 阅读20分钟

本篇涉及内容

  • Pthreads基础释义
  • Pthreads线程释义与使用
  • 锁(互斥锁、自旋锁、读写锁、条件锁)的基础释义
  • Pthreads使用
  • 锁(互斥锁、自旋锁、读写锁、条件锁)的使用

Pthreads

百度百科:POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX标 准。该标准定义了创建和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都使用Pthreads作为操作系统的线程。Windows操作系统也有其移植版pthreads-win32。

维基百科:POSIX线程(英语:POSIX Threads,常被缩写为Pthreads)是POSIX的线程标准,定义了创建和操纵线程的一套API。实现POSIX 线程标准的库常被称作Pthreads,一般用于Unix-like POSIX 系统,如Linux、Solaris。但是Microsoft Windows上的实现也存在,例如直接使用Windows API实现的第三方库pthreads-w32;而利用Windows的SFU/SUA子系统,则可以使用微软提供的一部分原生POSIX API。

简单来说就是操作系统级别使用的线程,基于c语言实现,使用难度较大,需要手动管理线程生命周期,下边是一些基础使用代码。

Pthreads常用函数与功能

使用函数前需导入头文件

#import <pthread.h>

一. pthread_t

pthread_t用于表示Thread ID,具体内容根据实现的不同而不同,有可能是一个Structure,因此不能将其看作为整数.

二. pthread_equal

pthread_equal函数用于比较两个pthread_t是否相等.

int pthread_equal(pthread_t tid1, pthread_t tid2)

三. pthread_self

pthread_self函数用于获得本线程的thread id

pthread _t pthread_self(void);

四. 创建线程

创建线程使用pthread_create函数

int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, 
  1. create函数解析
    void *(*start_rtn)(void *), void *restrict arg);
    pthread_t *restrict tidp:返回最后创建出来的Thread的Thread ID
    const pthread_attr_t *restrict attr:指定线程的Attributes,后面会讲道,现在可以用NULL
    void *(*start_rtn)(void *):指定线程函数指针,该函数返回一个void *,参数也为void*
    void *restrict arg:传入给线程函数的参数
    返回错误值。
    
  2. pthread函数在出错的时候不会设置errno,而是直接返回错误值
  3. 在Linux 系统下面,在老的内核中,由于Thread也被看作是一种特殊,可共享地址空间和资源的Process,因此在同一个Process中创建的不同 Thread具有不同的Process ID(调用getpid获得)。而在新的2.6内核之中,Linux采用了NPTL(Native POSIX Thread Library)线程模型(可以参考这里这里),在该线程模型下同一进程下不同线程调用getpid返回同一个PID。
  4. 对创建的新线程和当前创建者线程的运行顺序作出任何假设

五. 终止线程

  1. exit, _Exit, _exit用于中止当前进程,而非线程

  2. 中止线程可以有三种方式:

    a. 在线程函数中return

    b. 被同一进程中的另外的线程Cancel掉

    c. 线程调用pthread_exit函数

  3. pthread_exit和pthread_join函数的用法:

    a. 线程A调用pthread_join(B,&rval_ptr),被Block,进入Detached状态(如果已经进入Detached状态,则pthread_join函数返回EINVAL)。如果对B的结束代码不感兴趣,rval_ptr可以传NULL。

    b. 线程B调用pthread_exit(rval_ptr),退出线程B,结束代码为rval_ptr。注意rval_ptr指向的内存的生命周期,不应该指向B的Stack中的数据。

    c. 线程A恢复运行,pthread_join函数调用结束,线程B的结束代码被保存到rval_ptr参数中去。如果线程B被Cancel,那么rval_ptr的值就是PTHREAD_CANCELLED

    两个函数原型如下

    void pthread_exit(void *rval_ptr);
    int pthread_join(pthread_t thread, void **rval_ptr);
    
  4. 一个Thread可以要求另外一个Thread被Cancel,通过调用pthread_cancel函数:

    void pthread_cancel(pthread_t tid)

    该函数会使指定线程如同调用了pthread_exit(PTHREAD_CANCELLED)。不过,指定线程可以选择忽略或者进行自己的处理,在后面会讲到。此外,该函数不会导致Block,只是发送Cancel这个请求。

  5. 线程可以安排在它退出的时候,某些函数自动被调用,类似atexit()函数。 需要调用如下函数:

    void pthread_cleanup_push(void (*rtn)(void *), void *arg); void pthread_cleanup_pop(int execute);

    这两个函数维护一个函数指针的Stack,可以把函数指针和函数参数值push/pop。执行的顺序则是从栈顶到栈底,也就是和push的顺序相反。

    在下面情况下pthread_cleanup_push所指定的thread cleanup handlers会被调用:

    a.调用pthread_exit

    b.相应cancel请求

    c.以非0参数调用pthread_cleanup_pop()。(如果以0调用pthread_cleanup_pop(),那么handler不会被调用

    有一个比较怪异的要求是,由于这两个函数可能由宏的方式来实现,因此这两个函数的调用必须得是在同一个Scope之中,并且配对,因为在pthread_cleanup_push的实现中可能有一个{,而pthread_cleanup_pop可能有一个}。因此,一般情况下,这两个函数是用于处理意外情况用的,举例如下:

    void *thread_func(void *arg)
    {
        pthread_cleanup_push(cleanup, “handler”)
       // do something
       Pthread_cleanup_pop(0);
        return((void *)0);
    }
    
  6. 挂起状态

    缺省情况下,一个线程A的结束状态被保存下来直到pthread_join为该线程被调用过,也就是说即使线程A已经结束,只要没有线程B调用 pthread_join(A),A的退出状态则一直被保存。而当线程处于Detached状态之时,党线程退出的时候,其资源可以立刻被回收,那么这个退出状态也丢失了。在这个状态下,无法为该线程调用pthread_join函数。我们可以通过调用pthread_detach函数来使指定线程进入Detach状态:

    int pthread_detach(pthread_t tid);
    

    通过修改调用pthread_create函数的attr参数,我们可以指定一个线程在创建之后立刻就进入Detached状态

  7. 进程函数和线程函数的相关性

Process Primitive Thread Primitive Description
fork pthread_create 创建新的控制流
exit pthread_exit 退出已有的控制流
waitpid pthread_join 等待控制流并获得结束代码
atexit pthread_cleanup_push 注册在控制流退出时候被调用的函数
getpid pthread_self 获得控制流的id
abort pthread_cancel 请求非正常退出

六. 锁

  1. 互斥锁:pthread_mutex_

    a.用于互斥访问

    b.类型:pthread_mutex_t,必须被初始化为PTHREAD_MUTEX_INITIALIZER(用于静态分配的mutex,等价于 pthread_mutex_init(…, NULL))或者调用pthread_mutex_init。Mutex也应该用pthread_mutex_destroy来销毁。这两个函数原型如下:

    int pthread_mutex_init(
        pthread_mutex_t *restrict mutex,
        const pthread_mutexattr_t *restrict attr)
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    

    c.pthread_mutex_lock 用于Lock Mutex,如果Mutex已经被Lock,该函数调用会Block直到Mutex被Unlock,然后该函数会Lock Mutex并返回。pthread_mutex_trylock类似,只是当Mutex被Lock的时候不会Block,而是返回一个错误值EBUSY。 pthread_mutex_unlock则是unlock一个mutex。这三个函数原型如下:

    int pthread_mutex_lock(pthread_mutex_t *mutex);
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    int pthread_mutex_unlock(pthread_mutex_t *mutex);
    
  2. 自旋锁:Spin lock

    首先要提的是OSSpinLock已经出现了BUG,导致并不能完全保证是线程安全的。

    新版 iOS 中,系统维护了 5 个不同的线程优先级/QoS: background,utility,default,user-initiated,user-interactive。高优先级线程始终会在低优先级线程前执行,一个线程不会受到比它更低优先级线程的干扰。这种线程调度算法会产生潜在的优先级反转问题,从而破坏了 spin lock。 具体来说,如果一个低优先级的线程获得锁并访问共享资源,这时一个高优先级的线程也尝试获得这个锁,它会处于 spin lock 的忙等状态从而占用大量 CPU。此时低优先级线程无法与高优先级线程争夺 CPU 时间,从而导致任务迟迟完不成、无法释放 lock。这并不只是理论上的问题,libobjc 已经遇到了很多次这个问题了,于是苹果的工程师停用了 OSSpinLock。 苹果工程师 Greg Parker 提到,对于这个问题,一种解决方案是用 truly unbounded backoff 算法,这能避免 livelock 问题,但如果系统负载高时,它仍有可能将高优先级的线程阻塞数十秒之久;另一种方案是使用 handoff lock 算法,这也是 libobjc 目前正在使用的。锁的持有者会把线程 ID 保存到锁内部,锁的等待者会临时贡献出它的优先级来避免优先级反转的问题。理论上这种模式会在比较复杂的多锁条件下产生问题,但实践上目前还一切都好。 OSSpinLock 自旋锁,性能最高的锁。原理很简单,就是一直 do while 忙等。它的缺点是当等待时会消耗大量 CPU 资源,所以它不适用于较长时间的任务。对于内存缓存的存取来说,它非常合适。 -摘自ibireme

  3. 读写锁:pthread_rwlock_

    a. 多个线程可以同时获得读锁(Reader-Writer lock in read mode),但是只有一个线程能够获得写锁(Reader-writer lock in write mode).

    b. 读写锁有三种状态

    i.    一个或者多个线程获得读锁,其他线程无法获得写锁
    ii.   一个线程获得写锁,其他线程无法获得读锁
    iii.  没有线程获得此读写锁
    

    c. 类型为pthread_rwlock_t

    d. 创建和关闭方法如下:

    int pthread_rwlock_init(
          pthread_rwlock_t *restrict rwlock,
          const pthread_rwlockattr_t *restrict attr)
    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    

    e. 获得读写锁的方法如下:

    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
    pthread_rwlock_rdlock:获得读锁
    pthread_rwlock_wrlock:获得写锁
    pthread_rwlock_unlock:释放锁,不管是读锁还是写锁都是调用此函数
    

    注意具体实现可能对同时获得读锁的线程个数有限制,所以在调用 pthread_rwlock_rdlock的时候需要检查错误值,而另外两个pthread_rwlock_wrlockpthread_rwlock_unlock则一般不用检查,如果我们代码写的正确的话。

  4. 条件锁:Conditional Variable

    如果一个线程需要等待某一条件才能继续执行,而这个条件是由别的线程产生的,这时候只用锁就有点捉襟见肘了。要么不停的轮询,消耗资源,要么每隔一段时间查询一次,丧失了及时性。 条件变量就是为了满足这种场景而生的,它可以让一个线程等待某一条件,当条件满足时,会收到通知。 在获取条件变量并等待条件发生的过程中,也会产生多线程的竞争,所以条件变量通常会和互斥锁一起工作。

    a.条件必须被Mutex保护起来

    b.类型为:pthread_cond_t,必须被初始化为PTHREAD_COND_INITIALIZER(用于静态分配的条件,等价于pthread_cond_init(…, NULL))或者调用pthread_cond_init

    int pthread_cond_init(
          pthread_cond_t *restrict cond,
          const pthread_condxattr_t *restrict attr)
    int pthread_cond_destroy(pthread_cond_t *cond);
    

    c. pthread_cond_wait

    函数用于等待条件发生(=true)。pthread_cond_timedwait类似,只是当等待超时的时候返回一个错误值ETIMEDOUT。超时的时间用timespec结构指定。此外,两个函数都需要传入一个Mutex用于保护条件

    int pthread_cond_wait(
           pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex);
    int pthread_cond_timedwait(
           pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex,
           const struct timespec *restrict timeout);
    

    d. timespec结构定义如下:

    struct timespec {
        time_t tv_sec;       /* seconds */
        long   tv_nsec;      /* nanoseconds */
    };
    

    注意timespec的时间是绝对时间而非相对时间,因此需要先调用gettimeofday函数获得当前时间,再转换成timespec结构,加上偏移量。

    e. 有两个函数用于通知线程条件被满足(=true):

    int pthread_cond_signal(pthread_cond_t *cond);
    int pthread_cond_broadcast(pthread_cond_t *cond);
    

    两者的区别是前者会唤醒单个线程,而后者会唤醒多个线程

Pthreads使用方法

一.首先包含头文件 #import <pthread.h> 二.基础使用测试代码

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 创建线程——定义一个pthread_t类型变量
    pthread_t thread;
    // 开启线程——执行任务
    /*
    pthread_create(&thread, NULL, run, NULL); 中各项参数含义:
    第一个参数&thread是线程对象
    第二个和第四个是线程属性,可赋值NULL
    第三个run表示指向函数的指针(run对应函数里是需要在新线程中执行的任务)
     */
    pthread_create(&thread, NULL, threadStart, NULL);
}
/// > 新线程调用方法,里边为需要执行的任务
void *threadStart (void *data) {
    NSLog(@"啦啦啦啦多线程测试啦~:%@",[NSThread currentThread]);
    return NULL;
}

三.结束当前持有线程

在任务结束的时候调用
pthread_detach(thread);
pthread_cancel(thread);
或者
pthread_exit ( ) 
  1. Mutex(互斥锁)的使用

    a. 锁的创建

    锁可以被动态或静态创建,可以用宏PTHREAD_MUTEX_INITIALIZER来静态的初始化锁,采用这种方式比较容易理解,互斥锁是pthread_mutex_t的结构体,而这个宏是一个结构常量,如下可以完成静态的初始化锁:

    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
    

    另外锁可以用pthread_mutex_init函数动态的创建,函数原型如下:

    int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr)
    

    b. 锁的属性

    互斥锁属性可以由pthread_mutexattr_init(pthread_mutexattr_t *mattr);来初始化,然后可以调用其他的属性设置方法来设置其属性. 互斥锁的范围:可以指定是该进程与其他进程的同步还是同一进程内不同的线程之间的同步。可以设置为PTHREAD_PROCESS_SHARE和PTHREAD_PROCESS_PRIVATE。默认是后者,表示进程内使用锁。可以使用

    int pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr, int pshared)
    pthread_mutexattr_getshared(pthread_mutexattr_t *mattr,int *pshared)
    

    用来设置与获取锁的范围.

    c. 互斥锁的类型:

    PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。 PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。 PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样就保证当不允许多次加锁时不会出现最简单情况下的死锁。 PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。 可以用 pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type) pthread_mutexattr_gettype(pthread_mutexattr_t *attr , int *type) 获取或设置锁的类型。

    d. 锁的释放

    调用pthread_mutex_destory之后,可以释放锁占用的资源,但这有一个前提上锁当前是没有被锁的状态。

    e. 锁操作

    对锁的操作主要包括加锁 pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个。

    int pthread_mutex_lock(pthread_mutex_t *mutex)
    int pthread_mutex_unlock(pthread_mutex_t *mutex)
    int pthread_mutex_trylock(pthread_mutex_t *mutex)
    

    pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待

    f. 代码

    常用锁

    __block pthread_mutex_t theLock;
    pthread_mutex_init(&theLock, NULL); 
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        pthread_mutex_lock(&theLock);
        NSLog(@"需要线程同步的操作1 开始");
        sleep(3);
        NSLog(@"需要线程同步的操作1 结束");
        pthread_mutex_unlock(&theLock);
        
    });
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        sleep(1);
        pthread_mutex_lock(&theLock);
        NSLog(@"需要线程同步的操作2");
        pthread_mutex_unlock(&theLock);
        });
    

    递归锁(NSRecursiveLock)

    __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);
    });
    
  2. Spin Lock(自旋锁)的使用

  • 自旋锁与互斥锁类似
  • 但不同的是:自旋锁是非阻塞的,当一个线程无法获取自旋锁时,会自旋,直到该锁被释放,等待的过程中线程并不会挂起。(实质上就是,如果自旋锁已经被别的执行单元保持,调用者就一直循环在等待该自旋锁的保持着已经释放了锁)。
  • 自旋锁的使用者一般保持锁的时间很短,此时其效率远高于互斥锁。
  • 自旋锁保持期间是抢占失效的
    // 初始化自旋锁
    static OSSpinLock myLock = OS_SPINLOCK_INIT;
    // 自旋锁的使用
    -(void)SpinLockTest{
        OSSpinLockLock(&myLock);
        // to do something
        OSSpinLockUnlock(&myLock);
    }
    

OSSpinLock的效率是很高的,可惜在16年1月的时候被发现存在优先级反转问题,具体文章可以戳 不再安全的 OSSpinLock

  1. pthread_con_(条件锁)的使用 在iOS中,条件锁的实现方式有两种:

a. POSIX方式

POSIX 提供的相关函数如下:

  • pthread_cond_init 初始化
  • pthread_cond_wait 等待条件
  • pthread_cond_broadcast 发送广播,唤醒所有正在等待的线程
  • pthread_cond_signal 发送信号,唤醒第一个线程
  • pthread_cond_destroy 销毁

POSIX实例

条件变量和互斥锁一样,都有静态动态两种创建方式,静态方式使用PTHREAD_COND_INITIALIZER常量,如下: 
pthread_cond_t cond=PTHREAD_COND_INITIALIZER 
动态方式调用pthread_cond_init()函数,API定义如下: 
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr) 

尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。

注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。

等待条件有两种方式:无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。 API如下:

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) 
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)

无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。

激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。

以下是从网上找到的一个经典的小程序:

初始化代码:

static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

  struct node {
    int n_number;
    struct node *n_next;
} *head = NULL;

  - (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    self.title = @"Pthread_Con_ViewController";
    
    UIButton *startChildButton = [UIButton buttonWithType:UIButtonTypeSystem];
    startChildButton.frame = CGRectMake(0, 300, 200, 40);
    [startChildButton setTitle:@"开启A测试(POSIX)" forState:UIControlStateNormal];
    [startChildButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [startChildButton addTarget:self action:@selector(aButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:startChildButton];
}

实现代码:

  - (void)aButtonClick:(UIButton *)sender {
    pthread_t tid;
    int i;
    struct node *p;
    pthread_create(&tid, NULL, thread_func, NULL);   //子线程会一直等待资源,类似生产者和消费者,但是这里的消费者可以是多个消费者,而不仅仅支持普通的单个消费者,这个模型虽然简单,但是很强大
    /*[tx6-main]*/
    for (i = 0; i < 10; i++) {
        p = malloc(sizeof(struct node));
        p->n_number = i;
        pthread_mutex_lock(&mtx);             //需要操作head这个临界资源,先加锁,
        p->n_next = head;
        head = p;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mtx);           //解锁
        sleep(1);
    }
    printf("thread 1 wanna end the line.So cancel thread 2./n");
    pthread_cancel(tid);             //关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,子线程会在最近的取消点,退出线程,而在我们的代码里,最近的取消点肯定就是pthread_cond_wait()了。
    pthread_join(tid, NULL);
    printf("All done -- exiting/n");
}

  // [thread_func]
static void cleanup_handler(void *arg)
{
    NSLog(@"Cleanup handler of second thread.");
    free(arg);
    (void)pthread_mutex_unlock(&mtx);
}
static void *thread_func(void *arg)
{
    struct node *p = NULL;
    
    pthread_cleanup_push(cleanup_handler, p);
    while (1) {
        //这个mutex主要是用来保证pthread_cond_wait的并发性
        pthread_mutex_lock(&mtx);
        //这个while要特别说明一下,单个pthread_cond_wait功能很完善,为何这里要有一个while (head == NULL)呢?因为pthread_cond_wait里的线程可能会被意外唤醒,如果这个时候head != NULL,则不是我们想要的情况。这个时候,应该让线程继续进入pthread_cond_wait
        while (head == NULL)   {
            // pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,然后阻塞在等待对列里休眠,直到再次被唤醒(大多数情况下是等待的条件成立而被唤醒,唤醒后,该进程会先锁定先pthread_mutex_lock(&mtx);,再读取资源
            pthread_cond_wait(&cond, &mtx);
            //用这个流程是比较清楚的/*block-->unlock-->wait() return-->lock*/
        }
        p = head;
        head = head->n_next;
        NSLog(@"Got %d from front of queue/n",p->n_number);
        free(p);
        pthread_mutex_unlock(&mtx);             //临界区数据操作完毕,释放互斥锁
    }
    pthread_cleanup_pop(0);
    return 0;
}

b. NSCondition方式

  • NSCondition:是互斥锁和条件锁的结合,即一个线程在等待signal而阻塞时,可以被另一个线程唤醒,由于操作系统实现的差异,即使没有发送signal消息,线程也有可能被唤醒,所以需要增加谓词变量来保证程序的正确性。关于NSCondition苹果官方有一篇教程,地址在这Threading Programming Guide
  • NSConditionLock:与NSCondition的实现机制不一样,当定义的条件成立的时候会获取锁,反之,释放锁。

NSCondition常用API:

[condition lock];//一般用于多线程同时访问、修改同一个数据源,保证在同一时间内数据源只被访问、修改一次,其他线程的命令需要在lock 外等待,只到unlock ,才可访问
[condition unlock];//与lock 同时使用
[condition wait];//让当前线程处于等待状态
[condition signal];//CPU发信号告诉线程不用在等待,可以继续执行

NSCondition测试代码:

    // 创建锁
    NSCondition *condition = [[NSCondition alloc] init];
    static int count = 0;
    // 生产者
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while(count<20)
        {
            [condition lock];
            // 生产
            count ++;
            NSLog(@"生产 = %d",count);
            [condition signal];
            [condition unlock];
        }
    });
    // 消费者
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (count>0)
        {
            [condition lock];
            // 消耗
            count --;
            NSLog(@"消耗剩余 = %d",count);
            [condition unlock];
        }
    });

NSConditionLock常用API:

  //初始化一个NSConditionLock对象
  - (id)initWithCondition:(NSInteger)condition
  //返回一个Condition
  - (NSInteger)condition
  //在指定时间前尝试获取锁,若成功则返回YES 否则返回NO
  1、– (BOOL)lockBeforeDate:(NSDate *)limit
  //尝试获取锁。在加锁成功前接收对象的Condition必须与参数Condition 相同
  2、– (void)lockWhenCondition:(NSInteger)condition
  //同上,只是又加上了一个时间
  3、– (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate   *)limit
  //尝试着获取锁
  4、– (BOOL)tryLock 
  //如果接收对象的condition与给定的condition相等,则尝试获取锁
  5、– (BOOL)tryLockWhenCondition:(NSInteger)condition
  //解锁并设置接收对象的condition
  6、– (void)unlockWithCondition:(NSInteger)condition

NSConditionLock测试代码:

// 创建锁
    NSConditionLock *condLock = [[NSConditionLock alloc] initWithCondition:0];
    static int count = 0;
    // 生产者
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while(true)
        {
            [condLock lock];
            // 生产
            count ++;
            NSLog(@"生产 = %d",count);
            [condLock unlockWithCondition:(count >= 10 ? 10 : 0)];
            if (count >= 10) {
                break;
            }
            sleep(1);
        }
    });
    
    // 消费者
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        while (true)
        {
            [condLock lockWhenCondition:10];
            // 消耗
            count --;
            NSLog(@"消耗剩余 = %d",count);
            [condLock unlockWithCondition:(count<=0 ? 0 : 10)];
            sleep(1);
        }
    });
  1. pthread_rwlock_(读写锁)的使用 a. 作用   读写锁将访问者分为读出和写入两种,当读写锁在读加锁模式下,所有以读加锁方式访问该资源时,都会获得访问权限,而所有试图以写加锁方式对其加锁的线程都将阻塞,直到所有的读锁释放。当在写加锁模式下,所有试图对其加锁的线程都将阻塞。   当读写锁被一个线程以读模式占用的时候,写操作的其他线程会被阻塞,读操作的其他线程还可以继续进行。   当读写锁被一个线程以写模式占用的时候,写操作的其他线程会被阻塞,读操作的其他线程也被阻塞。

b.创建

pthread_rwlock_t rwlock;

c.初始化

pthread_rwlock_init(&rwlock, NULL);

d.使用

    UIButton *startChildButton = [UIButton buttonWithType:UIButtonTypeSystem];
    startChildButton.frame = CGRectMake(0, 300, 200, 40);
    [startChildButton setTitle:@"开启A测试" forState:UIControlStateNormal];
    [startChildButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [startChildButton addTarget:self action:@selector(aButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:startChildButton];
    
    UIButton *endChildButton = [UIButton buttonWithType:UIButtonTypeSystem];
    endChildButton.frame = CGRectMake(0, 400, 200, 40);
    [endChildButton setTitle:@"关闭B测试" forState:UIControlStateNormal];
    [endChildButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
    [endChildButton addTarget:self action:@selector(bButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:endChildButton];
  - (void)aButtonClick:(UIButton *)sender {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readBookWithTag:0];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readBookWithTag:1];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readBookWithTag:2];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self writeBook:3];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readBookWithTag:4];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self writeBook:5];
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self readBookWithTag:6];
    });
}

  - (void)bButtonClick:(UIButton *)sender {
    __block int i;
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        i = 5;
        while (i>=0) {
            NSString *temp = [NSString stringWithFormat:@"%d", i];
            [self writingLock:temp];
            i--;
        }
    });
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        i = 5;
        while (i>=0) {
            [self readingLock];
            i--;
        }
    });
}

  - (void)readBookWithTag:(NSInteger )tag {
    pthread_rwlock_rdlock(&rwlock);
    // read
    NSLog(@"读%tu",tag);
    sleep(3);
    pthread_rwlock_unlock(&rwlock);
}

  - (void)writeBook:(NSInteger)tag {
    pthread_rwlock_wrlock(&rwlock);
    // write
    NSLog(@"写%tu",tag);
    sleep(3);
    pthread_rwlock_unlock(&rwlock);
}

  -(void)writingLock:(NSString *)temp{
    pthread_rwlock_wrlock(&rwlock);
    // writing
    self.rwStr = temp;
    NSLog(@"写 == %@", temp);
    sleep(1);
    pthread_rwlock_unlock(&rwlock);
  }

  -(NSString *)readingLock{
    pthread_rwlock_rdlock(&rwlock);
    // reading
    NSString *str = self.rwStr;
    NSLog(@"读 == %@",self.rwStr);
    pthread_rwlock_unlock(&rwlock);
    return str;
  }

以上,就是Pthread调研结果



有志者、事竟成,破釜沉舟,百二秦关终属楚;

苦心人、天不负,卧薪尝胆,三千越甲可吞吴.