总结: linux下的多线程API (POSIX线程)

    技术2024-12-26  14

     

    POSIX线程:

    一个完成的线程/进程包括三部分,代码+数据+内存栈;子线程和子进程在被创建的时候,

    对于fork()创建子进程,三部分都要复制一份,数据包括比如文件描述符,虚拟内存,子进程关闭文件描述符不会影响父进程中的描述符;

    对于pthread_create()创建子线程的时候,只有内存栈被复制,其他的部分(代码,数据都是共享的),如果一个线程改变了某变量的值,其他所有的线程都调用的是改变之后的值;

    头文件#include <pthread.h>

    编译参数: -lpthread

     

     

    (一)涉及到的类型:

      pthread_t, pthread_attr_t, pthread_cond_t, pthread_mutexattr_t, void* (*)(void*), 

     

    (二)涉及到的函数:

     

      pthread_cancel,pthread_wait,

      pthread_create, pthread_self, pthread_detach, pthread_join, pthread_exit, 

      pthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock, pthread_mutex_destory; //参数都是pthread_mutex_t*

      int pthread_attr_init(pthread_attr_t*), int pthread_attr_destory(pthread_attr_t*),

      int pthread_attr_setdetachstatus(pthread_attr_t*,int); //设置属性

      int pthread_attr_getdetacstatus(const pthread_attr_t*, int*); //获取属性

      pthread_cond_wait,pthread_cond_timewait,pthread_cond_signal,pthread_cond_broadcast,pthread_cond_destory; 

     

      1.设置线程属性:

        一个线程属性对象,(例如pthread_attr_t attr_test),可以创建很多线程,创建线程之后没有必要保持此对象;

        在线程的所有属性中,最重要的是分离属性(detach status),一个线程可以是等待线程(joinable thread)or分离线程(detach thread),默认是joinable thread;

        对于一个非分离(joinable)线程, 类似于进程中的Zombies进程,joinbale thread在退出后,资源不会被立刻释放,直到被thread_join获取它的返回值;

        对于一个分离(detach)线程在退出之后, 资源会被立刻释放, 其他线程无法获悉其返回值;

        代码例子,main线程中:

     

        pthread_attr_t attr;      //(1)创建变量: 

        phtread_attr_init(&attr); //(2)初始化此属性变量;

        pthread_setdetachstatus(&attr,PTHREAD_CREATED_DETACHED);//(3)

        pthread_create(&t,&attr,&function,NULL);//(4)创建线程;

        pthread_destory(&attr);//(5)创建完后,即可销毁此属性变量;

        pthread_join(t,NULL); //错误!分离的线程无法被join

        return; //main线程结束

     

      2.创建线程:

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

         其中pthread_attr_t*和void* arg可设置为NULL;

     

      3.获取线程的pid:

        pthread_t pthread_self(void);

     

      4.线程分离:

         int pthread_detach(pthread_t thread);

      线程设置为分离, 当此线程退出时, 不会向任何其他线程传递返回值, 分离的线程无法被pthread_join();

      例子,把线程分离:

        void* myThread(void* param)

        {

            pthread_detach(pthread_self());

            pthread_exit(param);

        }

     

      5.线程等待(加入):

       pthread_join(pthread thread, void **status);

      main创建n个线程,无法确定main一定在所有线程都退出后再退出,所以要让main线程等待所有子线程退出后,再退出;

      调用者将等待第一参数标识的线程退出后,才能退出; 第二参数保存函数的返回值;

     

      6.线程退出和取消:  

       int pthread_exit(void* retval);

      线程内调用,调用线程退出,参数是线程函数的返回值, 或者可以把参数设为NULL;

      线程退出有三种方式,1线程函数返回,2调用pthread_exit,3调用pthread_cancel

     

      一个线程可以请求中止另一个线程,只需调用pthread_cancel(pid)即可;被cancel的线程,可以被pthread_wait(pid),此wait函数会释放线程占用的资源,除非是脱离(detach)线程;

      线程是否可以被取消(cancel)是线程的一个属性,类似"分离"属性.线程调用pthread_setcancelstatus()可以实现自身是否可被cancel; 注意区别pthread_attr_setXXX函数组;

      pthread_setcancelstatus(PTHREAD_CALCEL_DISABLE,NULL);//调用线程不可被取消!

      pthread_setcancelstatus(PTHREAD_CALCEL_ENABLE,NULL);//若第二参数不为NULL,则会存储线程前一个状态的可否取消状态;

      在一般应用中,pthread_setcancelstatus可以设置一个代码范围,在此范围内线程不可被取消,比如银行转帐,需要两步操作,A账户扣除,B账户增加,在这过程中不希望线程被取消的;

     

      7.互斥体 mutex:  

        //初始化

        int pthread_mutex_init(pthread_mutex_t* , pthread_mutexattr_t*);//第二个参数NULL是指向互斥锁类型的结构,代表默认属性的互斥锁;

        初始化互斥锁属性的代码如下:

        pthread_mutex_t mutex; 

        pthread_mutexattr_t attr;

        phtread_mutexattr_init(&attr);

        pthread_mutexattr_setkind_np(&attr,PTHREAD_MUTEX_ERRORCHECK_NP);//设置互斥体类型

        pthread_mutex_init(&mutex, &attr); //初始化互斥体

        pthread_mutex_destory(&attr); //变量pthread_mutexattr_t使用完成后,销毁

        //注:带_np结尾的是指"non-portable",不可移植

     

        或者更简单的初始化mutex方法:

        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //快速互斥

        PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP: 递归互斥,lock一个已锁互斥体时,不会阻塞,而是记录次数,unlock必须有相同的次数;

        PTHREAD_CHECKERROR_MUTEX_INITIALIZER_NP:纠错互斥,

     

        //互斥体锁定 

        int pthread_mutex_lock(pthread_mutex_t* mutex); //成功返0

        如果mutex已被线程A锁定后, 线程B尝试锁定mutex, 此时B线程会被阻塞,直到线程A解除了mutex的锁定, 同一时刻可能会有多个线程阻塞在一个互斥体上;

     

        //互斥体死锁

        互斥体的类型分为三种, fast mutex(默认类型), recursive mutex(递归互斥体), error-checking mutex(纠错互斥体),

        产生死锁的可能情况: 两个线程,两个互斥体交叉锁定,会出现互相等待的情况,即死锁, 代码例子:

     

    //线程A While(1){ mutex_lock(L1); mutex_lock(L2); do_jobA(); mutex_unlock(L2); mutex_unlock(L1); } //线程B While(1){ mutex_lock(L2); mutex_lock(L1); do_jobB(); mutex_unlock(L1); mutex_unlock(L2); } /* 进程A: mutex_lock(L1); 同时进程B:mutex_lock(L2); 将死锁 */ 

     

        //非阻塞锁定互斥体:

        int pthread_mutex_trylock(pthread_mutex_t* mutex);

        /*成功锁定互斥体,返回0; 如果互斥体已被其他的线程锁定,返回EBUSY,不会阻塞*/

     

        代码示例:

        int ret = pthread_mutex_trylock(&mutex);

        if(ret == EBUSY){

            /*互斥体已被锁定,不阻塞*/

        }

        else if(ret == EINVAL){

            assert(0); /*无效属性*/

        }

        else{

            /*解锁成功, 进行关键代码操作*/

            ret = pthread_mutex_unlock(&mutex);

        }

     

        //互斥体解锁

        int pthread_mutex_unlock(pthread_mutex_t* mutex); //成功返0

     

     

        //互斥体销毁

        int pthread_mutex_destory(pthread_mutex_t*); //在程序末尾或其他位置,清理互斥锁,此时互斥体不能是locked状态;

     

      8.线程条件变量

        在a情况下让线程继续执行,在b情况下让线程阻塞,当阻塞条件改变时,所有被这个条件变量阻塞的线程都能被激活;

        适用于的最简单情况,例如:线程thread1要循环执行从队列queue取出最后一个节点,线程thread2向queue增加节点;或者有更多的读写thread共同操作一个queue;

        一般的做法是,所有线程while(1)循环,pthread_mutex_lock和_unlock保护一个代码区域,在此检查queue是否为空,,,为什么这种的效率不高?

     

        (1)

        int pthread_cond_init(pthread_cond_t*, NULL);//第二参数为NULL,则创建默认属性 

     

        (2)条件变量等待:

        int pthread_cond_wait(pthread_cond_t*, pthread_mutex_t*);

        设ThreadA,执行pthread_cond_wait函数之前,必须锁定互斥体, pthread_cond_wait以一个原子操作[解锁互斥体]并[开始等待信号], 然后线程相当于wait状态或sleep状态, 处理器会在此时处理其他的线程;

        当某线程向ThreadA发送信号时, 线程ThreadA从pthread_cond_wait函数中返回并锁定互斥体;

     

        // cond代码示例(a)

        // work_load表示当前负载,当超过MAX_WORK_LOAD时,consumer线程调用process()函数进行work_load减; producer线程每次work_load++后发送一次信号;

        pthread_mutex_lock(&mutex);

        while(work_load < MAX_WORK_LOAD)

            pthread_cond_wait(&cond,&mutex);

        process(); //保护部分处理

        pthread_mutex_unlock(&mutex);

     

     

        // cond代码示例(b)

        // queue表示一个任务队列构成的链表,consumer线程从链表中取出节点并处理,producer向链表增加节点

        /*线程consumer*/

        void* consumer(void*){

            while(!exit){

                pthread_mutex_lock(&mutex);

                pthread_cond_wait(&cond); //互斥设为非锁定,等待条件信号

                /*收到信号,从wait函数返回时,互斥设为锁定,queue--操作 */

                pthread_mutex_unlock();

            }    

            printf("recv exit single/n");

            return;

        }

        /*线程producer*/

        void* producer(void*){

            pthread_mutex_lock(&mutex);

            /*queue++*/

            pthread_cond_signal(&cond);//或broadcast

            pthread_mutex_unlock(&mutex);

            return;

        }

        //注:为了代码健壮性,上面的wait函数应该assert检测返回值

     

        (3)条件变量等待函数的另外一种,指定最长等待时间,超过这个时间,即使没有条件信号到达,也从函数_wait中返回ETIMEOUT:

        原型: int pthread_cond_timewait(pthread_cond_t*, pthread_mutex_t*, const struct timespec*);

     

        pthread_mutex_lock(&mutex);

        int ret = pthread_cond_timewait(&cond,&mutex,&time);

        if(ret == ETIMEOUT) { /*未收到信号,超时*/}

        else { /*收到条件信号*/}

     

        (4)条件信号发送函数

        int pthread_cond_signal(pthread_cond_t*);

        int pthread_cond_broadcast(pthread_cond_t*);

     

        (5)销毁条件变量,在GUN/Linux中,并没有资源真正的被分配给条件变量,destroy只是简单的检查是否还有线程在等待这个条件变量;

        int pthread_cond_destory(pthread_cond_t*); 

     

     

      9.线程信号量

        涉及类型: sem_t

        涉及函数:sem_init, sem_destory, sem_getvalue;

        信号量也就是操作系统所应用到的PV原子操作,广泛应用于线程/进程的同步和互斥,信号量本质是一个非负的整数计数器;

        /*--PV原子操作的原理: 整数计数器sem, 一次p操作使sem减1,一次v操作使sem加1; 当sem大于等于0时, 线程拥有公共资源访问权,sem小于0时,该线程将阻塞直至sem>=0 */

        PV原子操作用于"互斥" or "同步"操作;

     

     

    (三)阻塞与非阻塞函数,调用函数后,代码是否阻塞在此等待函数返回?

        单线程+阻塞函数, 多线程+阻塞函数 ?

      pthread_join / pthread_mutex_lock / pthread_mutex_trylock / pthread_cancel / pthread_wait

      _mutex_trylock: 如果互斥已经锁定,不会阻断;

      _mutex_lock   : 如果互斥没有锁定,不阻断;如果互斥已经锁定,则阻塞在此;  

     

    最新回复(0)