iOS - 了解一点pthread的使用

2019-07-24  本文已影响0人  Longshihua

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 是 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 即可。

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递归锁类似

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

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

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

一次性初始化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的信号量不同于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.
上一篇 下一篇

猜你喜欢

热点阅读