美文网首页
iOS开发之多线程(5)—— Pthreads

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

作者: 看影成痴 | 来源:发表于2020-07-10 18:16 被阅读0次

    文集

    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

    相关文章

      网友评论

          本文标题:iOS开发之多线程(5)—— Pthreads

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