编程随想录
CSDN拆迁户 @2014-04-07

导航

 

 

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(纠错互斥体),

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

 

 

 

    //非阻塞锁定互斥体:

    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   : 如果互斥没有锁定,不阻断;如果互斥已经锁定,则阻塞在此;  

 

posted on 2011-02-09 18:36  dos5gw  阅读(520)  评论(0)    收藏  举报