iOS多线程Pthreads篇

本篇涉及内容html

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

Pthreads

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

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

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

Pthreads经常使用函数与功能

使用函数前需导入头文件算法

#import <pthread.h>
复制代码

一. pthread_t小程序

pthread_t用于表示Thread ID,具体内容根据实现的不一样而不一样,有多是一个Structure,所以不能将其看做为整数.缓存

二. pthread_equal安全

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

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
  一、– (BOOL)lockBeforeDate:(NSDate *)limit
  //尝试获取锁。在加锁成功前接收对象的Condition必须与参数Condition 相同
  二、– (void)lockWhenCondition:(NSInteger)condition
  //同上,只是又加上了一个时间
  三、– (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate   *)limit
  //尝试着获取锁
  四、– (BOOL)tryLock 
  //若是接收对象的condition与给定的condition相等,则尝试获取锁
  五、– (BOOL)tryLockWhenCondition:(NSInteger)condition
  //解锁并设置接收对象的condition
  六、– (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调研结果



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

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

相关文章
相关标签/搜索