iOS开发之多线程(5)—— Pthreads

文集

iOS开发之多线程(1)—— 概述
iOS开发之多线程(2)—— Thread
iOS开发之多线程(3)—— GCD
iOS开发之多线程(4)—— Operation
iOS开发之多线程(5)—— Pthreads
iOS开发之多线程(6)—— 线程安全与各种锁

API介绍

1. 创建

/**
 thread: 线程ID
 attr: 线程属性, 一般为NULL
 start_routine: 新线程入口函数
 arg: 入口函数start_routine的参数 (例如使用C++编程时的this指针)
 返回值int: 创建成功返回0, 失败返回错误码
 */
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(start_routine)(void*), void *arg);

2. 终止

线程终止有一下几种方法:

  1. 从主函数返回 (调用return之后);
  2. 自己调用pthread_exit();
  3. 其他线程调用pthread_cancel();
  4. 进程中任意地方调用exit()导致进程结束.

3. 线程的分离与合并 (资源管理)

pthread有两种状态:

  • joinable状态 (默认状态)
  • unjoinable状态

我们创建一个线程时, pthread_create()里有个pthread_attr_t *attr属性, 其中有一个分离属性 (另外还有绑定属性, 调度属性等), 我们一般设置这个属性为null, 即使用默认属性, 此时pthread的状态是joinable状态. joinable顾名思义为可合并的, unjoinable则为不可合并的.
所谓合并是指在线程A中等待线程B结束, 称B合并于A.

什么时候线程可合并什么时候又不可合并呢?
这就要和线程的资源管理有关了. 线程属于系统资源 (如与之相似的内存资源), 有创建就要有销毁回收, 不然会有资源泄漏 (如与之相似的内存泄漏).

回收资源有两种方式:

  • 系统自动回收
  • 程序员编写代码主动回收

默认状态(joinable)下, 资源是由程序员主动回收的 (使用pthread_join()方法); 而unjoinable状态是系统自动回收的, 程序员不能手动合并 (回收资源).

怎样切换至unjoinable状态, 让系统自动回收资源呢?
可以在创建的时候设置好分离属性; 或者在使用默认属性 (传null) 创建线程后调用pthread_detach()方法, 这时就变成unjoinable状态由系统自动回收资源了. 也可以说, 分离是将资源的管理交由系统来处理.

综上, 线程的分离与合并有如下方法(API):

// 分离线程, 由系统自动管理资源, 之后不需再调用pthread_join()
int pthread_detach(pthread_t);

// 合并线程, 即手动管理(回收)资源
int pthread_join(pthread_t , void * _Nullable * _Nullable)

对了, 还要注意, pthread_join()会阻塞当前线程, 等到要合并的线程结束时, 才继续往下执行.

示例1

#import <pthread.h>

pthread_t   m_threadID;  // 线程ID (全局变量)


- (void)viewDidLoad {
    [super viewDidLoad];

    // 创建线程
    [self createPthread];
    sleep(1);
    // 手动取消线程
    [self cancelThread];
}


// 创建线程
- (void)createPthread {
    
    /**
     参数1: 线程ID
     参数2: 线程属性, 一般为NULL
     参数3: 新线程入口函数
     参数4: 入口函数的参数
     */
    int ret = pthread_create(&m_threadID, NULL, myThread, NULL);
    if (ret != 0) {
        NSLog(@"!!! 创建失败 err:%d", ret);
        return;
    }
    
    // 分离线程 (自动管理资源, 后面不需调用pthread_join()来回收)
    pthread_detach(m_threadID);
}


// 线程入口函数
void *myThread(void *param)
{
    NSLog(@"1, %s, thread:%@", __func__, [NSThread currentThread]);
    
    sleep(3);
    
    NSLog(@"2, %s, thread:%@", __func__, [NSThread currentThread]);
    
//    pthread_exit(NULL);   // 这里作用与return差不多
    return NULL;
}


// 取消线程
- (void)cancelThread {
    
    pthread_cancel(m_threadID);     // 取消线程
//    pthread_join(m_threadID, NULL); // 如已分离线程则不需此步骤
}

本例中, 由于主线程在创建子线程1秒后马上取消了子线程, 所以只会打印log1而不会执行到log2.

log:
1, myThread, thread:<NSThread: 0x600000a01a80>{number = 6, name = (null)}

线程同步

线程的同步机制有很多种: 互斥锁, 事件, 信号量等等.
这里我们仅讨论最简单的: 互斥锁.
锁的三种操作:

  • 加锁pthread_mutex_lock()
  • 解锁pthread_mutex_unlock()
  • 尝试加锁pthread_mutex_trylock()

一个线程加锁成功, 就会独自享有锁里面的线程资源, 其他线程无法访问, 直至解锁. 但如果获取失败(比如别的线程已经获取过了), 这时获取失败的线程就会被挂起, 直至锁被释放(解锁)后, 才能恢复运行. 如果我们不想等待, 就可以使用尝试加锁pthread_mutex_trylock(), 和pthread_mutex_lock()唯一不同的是, 尝试加锁失败了线程不会被挂起, 而是由我们决定线程继续等待还是做其他任务.

示例2

pthread_mutex_t m_mutex;    // 互斥锁
int m_count;                // 测试数

// 同步两个线程
- (void)syncPthread {
    
    pthread_mutex_init(&m_mutex, NULL);             // 创建锁
    
    pthread_t pth1,pth2;
    pthread_create(&pth1, NULL, thread1, NULL);     // 创建线程1
    pthread_create(&pth2, NULL, thread2, NULL);     // 创建线程2
    pthread_join(pth1, NULL);                       // 等待回收线程1
    pthread_join(pth2, NULL);                       // 等待回收线程2
        
    pthread_mutex_destroy(&m_mutex);                // 销毁锁
}


void *thread1(void *arg)
{
    for (int i=0; i<10; i++)
    {
        pthread_mutex_lock(&m_mutex);
        NSLog(@"%s, count=%d", __func__, m_count);
        m_count++;
        pthread_mutex_unlock(&m_mutex);
        sleep(1);
    }
    NSLog(@"%s, end", __func__);
        
    return NULL;
}


void* thread2(void *arg)
{
    for (int i=0; i<10; i++)
    {
        pthread_mutex_lock(&m_mutex);
        NSLog(@"%s, count=%d", __func__, m_count);
        m_count++;
        pthread_mutex_unlock(&m_mutex);
        sleep(2);
    }
    NSLog(@"%s, end", __func__);
        
    return NULL;
}

可以看到, 虽然两个线程交替执行, 但是由于互斥锁不会争夺资源, m_count都是按顺序+1.

log:
thread2, count=0
thread1, count=1
thread1, count=2
thread1, count=3
thread2, count=4
thread1, count=5
thread1, count=6
thread2, count=7
thread1, count=8
thread1, count=9
thread2, count=10
thread1, count=11
thread1, count=12
thread2, count=13
thread1, count=14
thread1, end
thread2, count=15
thread2, count=16
thread2, count=17
thread2, count=18
thread2, count=19
thread2, end

demo
https://github.com/LittleLittleKang/KKThreadsDemo

最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351