美文网首页
iOS - 了解一点pthread的使用

iOS - 了解一点pthread的使用

作者: Longshihua | 来源:发表于2019-07-24 08:19 被阅读0次

    pthread

    POSIX Threads, usually referred to as pthreads, is an execution model that exists independently from a language, as well as a parallel execution model. It allows a program to control multiple different flows of work that overlap in time. Each flow of work is referred to as a thread, and creation and control over these flows is achieved by making calls to the POSIX Threads API.

    POSIX(可移植操作系统接口)线程,即pthreads,是一种不依赖于语言的执行模型,也称作并行(Parallel)执行模型。其允许一个程序控制多个时间重叠的不同工作流。每个工作流即为一个线程,通过调用 POSIX 线程 API 创建并控制这些流。

    pthread全称POSIX thread,是一套跨平台的多线程API,各个平台对其都有实现。pthread是一套非常强大的多线程锁,可以创建互斥锁(普通锁)、递归锁、信号量、条件锁、读写锁、once锁等,基本上所有涉及的锁,都可以使用pthread来实现。

    创建线程

    int pthread_create(pthread_t _Nullable * _Nonnull __restrict,
                       const pthread_attr_t * _Nullable __restrict,
                       void * _Nullable (* _Nonnull)(void * _Nullable),
                       void * _Nullable __restrict);
    

    _Nullable_NonnullObj-C 桥接 Swift可选(Optional)类型的标志;__restrict 是 C99 标准引入的关键字,类似于restrict,可以用在指针声明处,用于告诉编译器只有该指针本身才能修改指向的内容,便于编译器优化。

    去掉这些不影响函数本身的标志,补全参数名,即

     int pthread_create(pthread_t *thread,
                        const pthread_attr_t *attr,
                        void *(*start_routine) (void *),
                        void *arg);
    

    函数的四个参数

    其中第一个参数是 pthread_t *thread。关于该参数实际意义,一种指整型(Integer)四字节的线程 ID,另一种指包含值和其他的抽象结构体,这种抽象有助于在一个进程(Process)中实现扩展到数千个线程,后者也可以称为 pthread的句柄(Handle)。

    Obj-C 中,pthread_t 属于后者,其本质是 _opaque_pthread_t,一个不透明类型(Opaque Type)的结构体,即一种外界只需要知道其存在,而无需关心内部实现细节的数据类型。在函数中,该参数是作为指针传入的,总之我们可以简单将这个参数理解为线程的引用即可,通过它能找到线程的 ID 或者其他信息。

    typedef __darwin_pthread_t pthread_t;
    
    typedef struct _opaque_pthread_t *__darwin_pthread_t;
    
    struct _opaque_pthread_t {
        long __sig;
        struct __darwin_pthread_handler_rec  *__cleanup_stack;
        char __opaque[__PTHREAD_SIZE__];
    };
    

    第二个参数是 const pthread_attr_t *attrpthread_attr_t 本质也是一个不透明类型的结构体。可以使用 int pthread_attr_init(pthread_attr_t *); 初始化该结构体。该参数为 NULL 时将使用默认属性来创建线程。

    typedef __darwin_pthread_attr_t pthread_attr_t;
    
    typedef struct _opaque_pthread_attr_t __darwin_pthread_attr_t;
    
    struct _opaque_pthread_attr_t {
        long __sig;
        char __opaque[__PTHREAD_ATTR_SIZE__];
    };
    

    最后两个参数是 void *(*start_routine) (void *), void *argstart_routine() 是新线程运行时所执行的函数,arg 是传入 start_routine() 的参数。当 start_routine() 执行终止或者线程被明确杀死,线程也将会终止。

    返回值是int 类型,当返回0时,创建成功,否则将返回错误码。

    简单使用

    #import "pthread.h"
    
    void * runForPthreadCreate(void * arg) {
        // 打印参数 & 当前线程 __opaque 属性的地址
        printf("%s (%p) is running.\n", arg, &pthread_self()->__opaque);
        exit(2);
    }
    
    - (void)pthreadCreate {
        // 声明 thread_1 & thread_2
        pthread_t thread_1, thread_2;
    
        // 创建 thread_1
        int result_1 = pthread_create(&thread_1, NULL, runForPthreadCreate, "thread_1");
    
        // 打印 thread_1 创建函数返回值 & __opaque 属性的地址
        printf("result_1 - %d - %p\n", result_1, &thread_1->__opaque);
    
        // 检查线程是否创建成功
        if (result_1 != 0) {
            perror("pthread_create thread_1 error.");
            // 线程创建失败退出码为 1
            exit(1);
        }
    
        // 创建 thread_2
        int result_2 = pthread_create(&thread_2, NULL, runForPthreadCreate, "thread_2");
    
        // 打印 thread_2 创建函数返回值 & __opaque 属性的地址
        printf("result_2 - %d - %p\n", result_2, &thread_2->__opaque);
    
        if (result_2 != 0) {
            perror("pthread_create thread_2 error.");
            // 线程创建失败退出码为 1
            exit(1);
        }
    
        // sleep(1);
        // 主线程退出码为 3
        exit(3);
    }
    

    多线程技术实践之pthreads

    • 互斥锁(pthread_mutex)

    pthread_mutex表示互斥锁。互斥锁的实现原理与信号量非常相似,不是使用忙等,而是阻塞线程并睡眠,需要进行上下文切换。pthread_mutex 是 C 语言下多线程加互斥锁的方式

    typedef __darwin_pthread_mutex_t pthread_mutex_t;
    
    typedef struct _opaque_pthread_mutex_t __darwin_pthread_mutex_t;
    
    struct _opaque_pthread_mutex_t {
        long __sig;
        char __opaque[__PTHREAD_MUTEX_SIZE__];
    };
    

    常用函数

    // 初始化锁变量mutex。attr为锁属性,NULL值为默认属性。
    int pthread_mutex_init(pthread_mutex_t * __restrict, 
                 const pthread_mutexattr_t * __restrict); 
    
    // 加锁
    int pthread_mutex_lock(pthread_mutex_t *);  
    // 加锁,但是与2不一样的是当锁已经在使用的时候,返回为EBUSY,而不是挂起等待。
    int pthread_mutex_trylock(pthread_mutex_t *); 
    // 解锁
    int pthread_mutex_unlock(pthread_mutex_t *); 
    
    // 使用完后释放
    pthread_mutex_destroy(pthread_mutex_t* *mutex); 
    

    简单使用

    #import <pthread.h>
    
    static pthread_mutex_t theLock;
    
    -(void)pthread {
        pthread_mutex_init(&theLock, NULL);
    
        pthread_t thread1;
        pthread_create(&thread1, NULL, threadMethod1, NULL);
    
        pthread_t thread2;
        pthread_create(&thread2, NULL, threadMethod2, NULL);
    }
    
    void *threadMethod1() {
        pthread_mutex_lock(&theLock);
        printf("线程1\n");
        sleep(2);
        pthread_mutex_unlock(&theLock);
        printf("线程1解锁成功\n");
        return 0;
    }
    
    void *threadMethod2() {
        sleep(1);
        pthread_mutex_lock(&theLock);
        printf("线程2\n");
        pthread_mutex_unlock(&theLock);
        return 0;
    }
    

    运行输出

    线程1
    线程1解锁成功
    线程2
    

    一般情况下,一个线程只能申请一次锁,也只能在获得锁的情况下才能释放锁,多次申请锁或释放未获得的锁都会导致崩溃。假设在已经获得锁的情况下再次申请锁,线程会因为等待锁的释放而进入睡眠状态,因此就不可能再释放锁,从而导致死锁。

    然而这种情况经常会发生,比如某个函数申请了锁,在临界区内又递归调用了自己。辛运的是 pthread_mutex 支持递归锁,也就是允许一个线程递归的申请锁,只要把 attr 的类型改成 PTHREAD_MUTEX_RECURSIVE 即可。

    • 递归锁(pthread_mutexattr_t)

    Mutex可以分为递归锁(recursive mutex)和非递归锁(non-recursive mutex)。可递归锁也可称为可重入锁(reentrant mutex),非递归锁又叫不可重入锁(non-reentrant mutex)。

    二者唯一的区别是,同一个线程可以多次获取同一个递归锁,不会产生死锁。而如果一个线程多次获取同一个非递归锁,则会产生死锁。

    typedef __darwin_pthread_mutexattr_t pthread_mutexattr_t;
    
    typedef struct _opaque_pthread_mutexattr_t __darwin_pthread_mutexattr_t;
    
    struct _opaque_pthread_mutexattr_t {
        long __sig;
        char __opaque[__PTHREAD_MUTEXATTR_SIZE__];
    };
    

    递归锁的创建方法跟普通锁是同一个方法,不过需要传递一个attr参数.

    锁的类型

    /*
     * Mutex type attributes
     */
    #define PTHREAD_MUTEX_NORMAL        0
    #define PTHREAD_MUTEX_ERRORCHECK    1
    #define PTHREAD_MUTEX_RECURSIVE     2
    #define PTHREAD_MUTEX_DEFAULT       PTHREAD_MUTEX_NORMAL
    

    1、PTHREAD_MUTEX_NORMAL

    缺省类型,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后先进先出原则获得锁。

    2、PTHREAD_MUTEX_ERRORCHECK

    检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与普通锁类型动作相同。这样就保证当不允许多次加锁时不会出现嵌套情况下的死锁。

    3、PTHREAD_MUTEX_RECURSIVE

    递归锁,允许同一个线程对同一个锁成功获得多次,并通过多次 unlock 解锁。

    4、PTHREAD_MUTEX_DEFAULT

    互斥锁,动作最简单的锁类型,仅等待解锁后重新竞争,没有等待队列。

    简单使用

    -(void)pthread_mutexattr {
        __block pthread_mutex_t theLock;
        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);
        });
    }
    

    运行输出

    value = 5
    value = 4
    value = 3
    value = 2
    value = 1
    

    这是pthread_mutex为了防止在递归的情况下出现死锁而出现的递归锁。作用和NSRecursiveLock递归锁类似

    • 条件锁(pthread_cond_t)
    typedef __darwin_pthread_cond_t pthread_cond_t;
    
    typedef struct _opaque_pthread_cond_t __darwin_pthread_cond_t;
    
    struct _opaque_pthread_cond_t {
        long __sig;
        char __opaque[__PTHREAD_COND_SIZE__];
    };
    

    常用函数

    // 等待条件锁
    int pthread_cond_wait(pthread_cond_t * __restrict,
                          pthread_mutex_t * __restrict);
    
    // 激动一个相同条件的条件锁
    int pthread_cond_signal(pthread_cond_t *);
    

    简单使用

    - (void)conditionLock {
        // 初始化锁变量mutex
        __block pthread_mutex_t mutex;
        pthread_mutex_init(&mutex, NULL);
    
        // 条件锁
        __block pthread_cond_t cond;
        pthread_cond_init(&cond, NULL);
    
        // 线程1
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 加锁
            pthread_mutex_lock(&mutex);
            pthread_cond_wait(&cond, &mutex);
            NSLog(@"thread 1");
            // 解锁
            pthread_mutex_unlock(&mutex);
        });
    
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            pthread_mutex_lock(&mutex);
            NSLog(@"thread 2");
            pthread_cond_signal(&cond);
            pthread_mutex_unlock(&mutex);
        });
    }
    

    运行结果

    thread 2
    thread 1
    
    • 读写锁(pthread_rwlock_t)

    读写锁是一种特殊的自旋锁,将对资源的访问分为读者和写者,顾名思义,读者对资源只进行读取,而写者对资源只有写访问,相对于自旋锁来说,这中锁能提高并发性。在多核处理器操作系统中,允许多个读者访问同一资源,却只能有一个写者执行写操作,并且读写操作不能同时进行

    typedef __darwin_pthread_rwlock_t pthread_rwlock_t;
    
    typedef struct _opaque_pthread_rwlock_t __darwin_pthread_rwlock_t;
    
    struct _opaque_pthread_rwlock_t {
        long __sig;
        char __opaque[__PTHREAD_RWLOCK_SIZE__];
    };
    

    常用函数

    // 初始化都写锁
    int pthread_rwlock_init(pthread_rwlock_t * __restrict,
                            const pthread_rwlockattr_t * _Nullable __restrict);
    
    // 读操作上锁
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *);
    
    // 写操作上锁
    int pthread_rwlock_trywrlock(pthread_rwlock_t *);
    
    // 获取写锁
    int pthread_rwlock_wrlock(pthread_rwlock_t *);
    
    // 获取读锁
    int pthread_rwlock_rdlock(pthread_rwlock_t *);
    
    // 解锁
    int pthread_rwlock_unlock(pthread_rwlock_t *);
    
    // 释放锁
    int pthread_rwlock_destroy(pthread_rwlock_t * );
    

    简单使用

    static pthread_rwlock_t rwlock;
    
    // 读写锁
    - (void)rwlock {
        pthread_rwlock_init(&rwlock, NULL);
    
        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 writeBook:2];
        });
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self readBookWithTag:3];
        });
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [self writeBook:4];
        });
    }
    
    - (void)readBookWithTag:(NSInteger )tag {
        pthread_rwlock_rdlock(&rwlock);
        NSLog(@"读%tu",tag);
        sleep(3);
        pthread_rwlock_unlock(&rwlock);
    }
    
    - (void)writeBook:(NSInteger)tag {
        pthread_rwlock_wrlock(&rwlock);
        NSLog(@"写%tu",tag);
        sleep(3);
        pthread_rwlock_unlock(&rwlock);
    }
    

    运行输出

    读0
    读1
    写2
    读3
    读4
    
    • once锁(pthread_once)

    一次性初始化pthread_once

    static pthread_once_t once = PTHREAD_ONCE_INIT;
    static pthread_mutex_t mutex;
    
    void init() {    
        pthread_mutex_init(&mutex, NULL);
    }
    
    void performWork() {
        pthread_once(&once, init); // Correct
        pthread_mutex_lock(&mutex);
        // ...
        pthread_mutex_unlock(&mutex);
    }
    
    • pthread(semaphore)

    pthread的信号量不同于GCD自带的信号量,如前面所说,pthread是一套跨平台的多线程API,对信号量也提供了相应的使用。其大概原理和使用方法与GCD提供的信号量机制类似,使用起来也比较方便。

    信号量机制通过信号量的值控制可用资源的数量。线程访问共享资源前,需要申请获取一个信号量,如果信号量为0,说明当前无可用的资源,线程无法获取信号量,则该线程会等待其他资源释放信号量(信号量加1)。如果信号量不为0,说明当前有可用的资源,此时线程占用一个资源,对应信号量减1。

    // 该函数申请一个信号量,当前无可用信号量则等待,有可用信号量时占用一个信号量,对信号量的值减1。
    int sem_wait(sem_t *sem);       
    
    // 该函数释放一个信号量,信号量的值加1。
    int sem_post(sem_t *sem);
    

    简单使用

    #import "pthread.h"
    #import <sys/semaphore.h>
    
    static sem_t *semaphore;
    
    void *runForSemaphoreDemo1(void * arg) {
        // 等待信号
        sem_wait(semaphore);
    
        printf("Running - %s.\n", arg);
        return 0;
    }
    
    void *runForSemaphoreDemo2(void * arg) {
        printf("Running - %s.\n", arg);
    
        sleep(1);
        // 发送信号
        sem_post(semaphore);
        return 0;
    }
    
    - (void)semaphoreDemo {
        // sem_init 初始化匿名信号量在 macOS 中已被废弃
        // semaphore = sem_init(&semaphore, 0, 0);
        semaphore = sem_open("sem", 0, 0);
    
        pthread_t thread_1, thread_2;
        void * result;
    
        if (pthread_create(&thread_1, NULL, runForSemaphoreDemo1, "Thread 1") != 0) {
            perror("pthread_create thread_1 error.");
            exit(1);
        }
    
        if (pthread_create(&thread_2, NULL, runForSemaphoreDemo2, "Thread 2") != 0) {
            perror("pthread_create thread_2 error.");
            exit(1);
        }
    
        // thread_join用来等待一个线程的结束
        pthread_join(thread_1, &result);
        pthread_join(thread_2, &result);
    }
    

    运行输出

    Running - Thread 2.
    Running - Thread 1.
    

    相关文章

      网友评论

          本文标题:iOS - 了解一点pthread的使用

          本文链接:https://www.haomeiwen.com/subject/djxalctx.html