iOS 多线程系列 -- pthread

iOS 多线程系列 -- 基础概述
iOS 多线程系列 -- pthread
iOS 多线程系列 -- NSThread
iOS 多线程系列 -- GCD全解一(基础)
iOS 多线程系列 -- GCD全解二(常用方法)
iOS 多线程系列 -- GCD全解三(进阶)
iOS 多线程系列 -- NSOperation
测试Demo的GitHub地址

1. pthread概述

  • pthread 是 POSIX 多线程开发框架,是跨平台的 C 语言框架,需要自己管理线程的创建销毁等操作。
  • pthread_t ,用于标识一个线程,不能单纯看成整数,通过头文件可以看到是_opaque_pthread_t 类型的结构体指针
  • pthread_attr_t,线程的属性,通过头文件可以看到是一个_opaque_pthread_attr_t类型结构体

2. pthread中常用API

2.1 pthread_create

int pthread_create(pthread_t _Nullable * _Nonnull __restrict, const pthread_attr_t * _Nullable __restrict,void * _Nullable (* _Nonnull)(void * _Nullable),void * _Nullable __restrict);
  • 创建一个线程,创建成功返回0,失败返回对应错误码
  • 参数解析:
    • 第一个参数为指向线程标识符的指针
    • 第二个参数用来设置线程属性,中介绍过线程的结构,我们可以初始化一个pthread_attr_t变量指定优先级等属性,一般可以传入Null,采用缺省值
    • 第三个参数是线程运行函数的起始地址
    • 最后一个参数是传给运行函数的参数,如使用示例中的run2方法

2.2 pthread_join

int pthread_join(pthread_t , void * _Nullable * _Nullable)
  • 等待某个线程执行完毕,这个函数是一个线程阻塞的函数,将一直阻塞到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回
  • 参数解析:
    • 第一个参数为被等待的线程标识符
    • 第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值

2.3 pthread_detach

int pthread_detach(pthread_t);
  • 将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源
  • pthread_detach 和 pthread_join 回收线程资源的区别:
    • pthread_join 会同步等待子线程任务结束后回收其资源,如果该子线程没有运行结束,父线程会被阻塞,在有些情况下我们并不希望如此,就可以用pthread_detach
    • pthread_detach 不会阻塞调用线程
  • 使用:
    • 父线程调用: pthread_detach(thread2);
    • 也可以在子线程中调用: pthread_detach(pthread_self());
      pthread_self()获取当前线程ID

2.4 pthread_kill

int pthread_kill(pthread_t, int);
  • 该函数可以用于向指定的线程发送信号:

  • 如果线程内不对信号进行处理,则调用默认的处理程式,如SIGQUIT信号会退出终止线程,SIGKILL会杀死线程等等,可以调用signal(SIGQUIT, sig_process_routine); 来自定义信号的处理程序。

  • 参数解析:

    • 第一个参数表示线程的标识符
    • 第二个参数表示传递的signal参数,一般都是大于0的,这时系统默认或者自定义的都是有相应的处理程序。常用信号量宏可以在这里查看,#import <signal.h> .signal为0时,是一个被保留的信号,一般用这个保留的信号测试线程是否存在。
  • pthread_kill 返回值如下:

    • 0:调用成功。
    • ESRCH:线程不存在。
    • EINVAL:信号不合法
    • 测试线程是否存在/终止的方法
  - (void)testThreadLife:(pthread_t)thread
{
    int kill_ret = pthread_kill(thread,0);// 系统定义的信号量宏,如:SIGUSR2,
    if(kill_ret == ESRCH)
        NSLog(@"指定的线程不存在或者是已经终止\n");
    else if(kill_ret == EINVAL)
        NSLog(@"调用传递一个无用的信号\n");
    else
        NSLog(@"线程存在\n");

}

2.5 线程取消相关的pthread函数

2.5.1 pthread_cancel
int pthread_cancel(pthread_t) __DARWIN_ALIAS(pthread_cancel);
  • 发送终止信号给thread线程,如果发送成功则返回0,否则为非0值。注意:发送成功并不意味着thread会终止,原因看下面pthread_setcancelstate!
2.5.2 pthread_setcancelstate
int pthread_setcancelstate(int , int * _Nullable)
  • 设置本线程对Cancel信号的反应state,有两种值:PTHREAD_CANCEL_ENABLE(缺省)和 PTHREAD_CANCEL_DISABLE;old_state如果不为NULL则存入原来的Cancel状态以便恢复。
  • 两种反应state解析:
    • PTHREAD_CANCEL_ENABLE:表示可以接收处理取消信号,设置线程状态为CANCEl,并终止任务执行
    • PTHREAD_CANCEL_DISABLE : 忽略cancel信号,继续执行任务
2.5.3 pthread_setcanceltype
int pthread_setcanceltype (int type, int *oldtype) 
  • 设置本线程取消动作的执行时机,type有两种取值仅当Cancel状态为Enable时有效:
    • PTHREAD_CANCEL_DEFFERED ,表示收到信号后继续运行至下一个取消点再退出,推荐做法,因为在终止线程之前必须要处理好内存回收防止内存泄漏,而手动设置取消点这种方式就可以让我们很自由的处理内存回收时机
    • PTHREAD_CANCEL_ASYCHRONOUS,立即执行取消动作(退出),不推荐这样操作,可能造成内存泄漏等问题
  • oldtype如果不为NULL则存入运来的取消动作类型值
void * cancelRun (void *prama)
{
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); // 忽略取消信号,即使收到其他线程调用pthread_cancel,也会继续执行任务
    pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);// 设置收到取消信号,立即取消
    if (!prama) {
        NSLog(@"run1 prama = null");
    } else {
        NSLog(@"run1 prama = %d\n", (int)(*((int*)prama)));
    }
    for (int i = 0; i<5000; i++) {
        NSLog(@"---run1--%d---%@",i,[NSThread currentThread]);
    }
    return &a;

}
2.5.4 pthread_testcancel 设置取消点
  • 线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定.
  • 线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出
  • 常用设置取消点的方法有:pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()
  • 详情可以去看demo中下图所示测试:


3 API总结

3.1 操作函数

pthread_create():创建一个线程
pthread_exit():终止当前线程
pthread_cancel():中断另外一个线程的运行
pthread_join():阻塞当前的线程,直到另外一个线程运行结束
pthread_attr_init():初始化线程的属性
pthread_attr_setdetachstate():设置脱离状态的属性(决定这个线程在终止时是否可以被结合)
pthread_attr_getdetachstate():获取脱离状态的属性
pthread_attr_destroy():删除线程的属性
pthread_kill():向线程发送一个信号
pthread_equal(): 对两个线程的线程标识号进行比较
pthread_detach(): 分离线程
pthread_self(): 查询线程自身线程标识号

3.2 同步函数

数据类型:
  • pthread_mutex_t 互斥量,用于互斥访问,用pthread_mutex_init方法进行初始化
  • pthread_cond_t 条件变量,调用pthread_cond_init初始化
  • 互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线程间的同步
方法
  • pthread_mutex_lock():占有互斥锁(阻塞操作),函数调用会阻塞直到互斥量被unlock,
  • pthread_mutex_init() 初始化互斥锁
  • pthread_mutex_destroy() 删除互斥锁
  • pthread_mutex_trylock():试图占有互斥锁(不阻塞操作)。即当互斥锁空闲时,将占有该锁;否则,立即返回一个错误值EBUSY。常用于死锁检测,根据返回的错误信息程序员对死锁做出相应的处理
  • pthread_mutex_unlock(): 释放互斥锁
其他方法

pthread_cond_init():初始化条件变量
pthread_cond_destroy():销毁条件变量
pthread_cond_signal(): 唤醒第一个调用pthread_cond_wait()而进入睡眠的线程
pthread_cond_wait(): 等待条件变量的特殊条件发生
pthread_key_create(): 分配用于标识进程中线程特定数据的键
pthread_setspecific(): 为指定线程特定数据键设置线程特定绑定
pthread_getspecific(): 获取调用线程的键绑定,并将该绑定存储在 value 指向的位置中
pthread_key_delete(): 销毁现有线程特定数据键
pthread_attr_getschedparam();获取线程优先级
pthread_attr_setschedparam();设置线程优先级

4. 使用示例:

void即“无类型”,void *则为“无类型指针”,可以指向任何数据类型

- (void)testCreatJoinDetach
{
    pthread_t thread1;
    pthread_attr_t att;
    pthread_attr_init(&att);
    int result = pthread_create(&thread1, &att, run1, nil);//创建一个线程
    if (result == 0) {
        NSLog(@"创建线程 OK");
    } else {
        NSLog(@"创建线程失败 %d", result);
    }
    NSLog(@"111(*thread1).__sig = %ld ,(*thread1).__opaque = %s, thread1 = %p",(*thread1).__sig ,(*thread1).__opaque , thread1);
    
    NSLog(@"before pthread_join ");
    void * thread1Return;
    pthread_join(thread1, &thread1Return);//当前线程被阻塞,等待线程1结束后恢复
    NSLog(@"after pthread_join ; thread1Return = %zd ",(!thread1Return) ? 0 : (int)(*((int *)thread1Return)));

    pthread_t thread2;
    int a = 2;
    pthread_create(&thread2, NULL, (void *)run2, &a);
    pthread_detach(thread2); // 或者在run2中调用pthread_detach(pthread_self());
}

int a = 88;
void * run1 (void *prama)
{
    if (!prama) {
        NSLog(@"run1 prama = null");
    } else {
        NSLog(@"run1 prama = %d\n", (int)(*((int*)prama)));
    }
    for (int i = 0; i<3; i++) {
        NSLog(@"---run1--%d---%@",i,[NSThread currentThread]);
    }
    return &a;
}

void run2 (void *prama)
{
    if (!prama) {
        NSLog(@"run2 prama = null");
    } else {
        NSLog(@"run2 prama = %d\n", (int)(*((int*)prama)));
    }
    for (int i = 0; i<3; i++) {
        NSLog(@"--run2---%d---%@",i,[NSThread currentThread]);
    }
//    pthread_detach(pthread_self());
}

打印结果如下:

**2017-06-27 11:28:42.393 Test - ****多线程****[38965:5126933] pthread_join ****前**
**2017-06-27 11:28:42.394 Test - ****多线程****[38965:5127082] ---run--0---<NSThread: 0x600000271980>{number = 3, name = (null)}**
**2017-06-27 11:28:42.394 Test - ****多线程****[38965:5127082] ---run--1---<NSThread: 0x600000271980>{number = 3, name = (null)}**
**2017-06-27 11:28:42.394 Test - ****多线程****[38965:5127082] ---run--2---<NSThread: 0x600000271980>{number = 3, name = (null)}**
**2017-06-27 11:28:42.395 Test - ****多线程****[38965:5126933] pthread_join ****后**** thread1Return = 0**
**prama from thread2 2**
**2017-06-27 11:28:42.395 Test - ****多线程****[38965:5127083] --run2---0---<NSThread: 0x60800026f7c0>{number = 4, name = (null)}**
**2017-06-27 11:28:42.395 Test - ****多线程****[38965:5127083] --run2---1---<NSThread: 0x60800026f7c0>{number = 4, name = (null)}**

**2017-06-27 11:28:42.396 Test - ****多线程****[38965:5127083] --run2---2---<NSThread: 0x60800026f7c0>{number = 4, name = (null)}**

5. 参考资料

iOS开发 - 多线程实现方案之Pthread篇

linux多线程全面解析

pthread_cancel引起的死锁

推荐阅读更多精彩内容

  • 线程基础 线程是进程的一个执行单元,执行一段程序片段,线程共享全局变量;线程的查看可以使用命令或者文件来进行查看;...
    秋风弄影阅读 302评论 0 0
  • 创建线程 C99新增restrict用于限定指针;该关键字用于告诉编译器,所有修改该指针所指向的内容的操作全部都是...
    Joe_HUST阅读 332评论 0 0
  • 转自:Youtherhttps://www.cnblogs.com/youtherhome/archive/201...
    njukay阅读 1,093评论 0 52
  • 线程 在linux内核那一部分我们知道,线程其实就是一种特殊的进程,只是他们共享进程的文件和内存等资源,无论如何对...
    大雄good阅读 252评论 0 2
  • linux线程同步 信号灯:与互斥锁和条件变量的主要不同在于"灯"的概念,灯亮则意味着资源可用,灯灭则意味着不可用...
    鲍陈飞阅读 413评论 0 2