线程池,多线程使用方法,demo

/* *什么时候需要创建线程池呢?简单的说,如果一个应用需要频繁的创建和销毁线程,而任务执行的时间又非常短,
    这样线程创建和销毁的带来的开销就不容忽视,这时也是线程池该出场的机会了。如果线程创建和销毁时间相比任务执行时间可以忽略不计,则没有必要使用线程池了。

   下面是Linux系统下用C语言创建的一个线程池。线程池会维护一个任务链表(每个CThread_worker结构就是一个任务)。
   pool_init()函数预先创建好max_thread_num个线程,每个线程执thread_routine ()函数。该函数中

    while (pool->cur_queue_size == 0)
    {
           pthread_cond_wait (&(pool->queue_ready),&(pool->queue_lock));
    }

表示如果任务链表中没有任务,则该线程出于阻塞等待状态。否则从队列中取出任务并执行。
 
   pool_add_worker()函数向线程池的任务链表中加入一个任务,加入后通过调用pthread_cond_signal (&(pool->queue_ready))
   唤醒一个出于阻塞状态的线程(如果有的话)。
 
   pool_destroy ()函数用于销毁线程池,线程池任务链表中的任务不会再被执行,但是正在运行的线程会一直把任务运行完后再退出。
**/    


    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <pthread.h>
    #include <assert.h>

    /*
    *线程池里所有运行和等待的任务都是一个CThread_worker
    *由于所有任务都在链表里,所以是一个链表结构
    */
    typedef struct worker
    {
        /*回调函数,任务运行时会调用此函数,注意也可声明成其它形式*/
        void *(*process) (void *arg);
        void *arg;/*回调函数的参数*/
        struct worker *next;

    } CThread_worker;


    /*线程池结构*/
    typedef struct
    {
        pthread_mutex_t queue_lock;
        pthread_cond_t queue_ready;

        /*链表结构,线程池中所有等待任务*/
        CThread_worker *queue_head;

        /*是否销毁线程池*/
        int shutdown;
        pthread_t *threadid;
        /*线程池中允许的活动线程数目*/
        int max_thread_num;
        /*当前等待队列的任务数目*/
        int cur_queue_size;

    } CThread_pool;


    int pool_add_worker (void *(*process) (void *arg), void *arg);
    void *thread_routine (void *arg);


    static CThread_pool *pool = NULL;
    void pool_init (int max_thread_num)
    {
        pool = (CThread_pool *) malloc (sizeof (CThread_pool));

        pthread_mutex_init (&(pool->queue_lock), NULL);
        pthread_cond_init (&(pool->queue_ready), NULL);
        /*
            函数原型: int pthread_mutex_init(pthread_mutex_t *restrict mutex,const
                                                pthread_mutexattr_t *restrict attr);
          该函数用于C函数的多线程编程中,互斥锁的初始化。
          pthread_mutex_init() 函数是以动态方式创建互斥锁的,参数attr指定了新建互斥锁的属性。
           如果参数attr为空,则使用默认的互斥锁属性,默认属性为快速互斥锁 。
                互斥锁的属性在创建锁的时候指定,在        
            LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时
            表现不同。
          pthread_mutexattr_init() 函数成功完成之后会返回零,其他任何返回值都表示出现了错误。
          函数成功执行后,互斥锁被初始化为锁住态
        */
        /*
            条件变量和互斥锁一样,都有静态动态两种创建方式,静态方式使用PTHREAD_COND_INITIALIZER常量;
            动态方式调用pthread_cond_init()函数,API定义如下:
              int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)
             
            注销一个条件变量需要调用pthread_cond_destroy(),只有在没有线程在该条件变量上等待的时候才能
                注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,
                所以注销动作只包括检查是否有等待线程。API定义如下:
          int pthread_cond_destroy(pthread_cond_t *cond)
        */

        pool->queue_head = NULL;

        pool->max_thread_num = max_thread_num;
        pool->cur_queue_size = 0;

        pool->shutdown = 0;
        
        //存储线程ID的数组
        pool->threadid =(pthread_t *) malloc (max_thread_num * sizeof (pthread_t));
        int i = 0;
        for (i = 0; i < max_thread_num; i++)
        {    //创建线程
            pthread_create (&(pool->threadid[i]), NULL, thread_routine, NULL);
        }
    }


    /*向线程池中加入任务*/
    int pool_add_worker (void *(*process) (void *arg), void *arg)
    {
        /*构造一个新任务*/
        CThread_worker *newworker =
             (CThread_worker *) malloc (sizeof (CThread_worker));
        newworker->process = process;
        newworker->arg = arg;
        newworker->next = NULL;/*别忘置空*/

        //lock
        pthread_mutex_lock (&(pool->queue_lock));
        /*将任务加入到等待队列中*/
        CThread_worker *member = pool->queue_head;
        if (member != NULL)
        {
            while (member->next != NULL)
                member = member->next;
            member->next = newworker;
        }
        else
        {
            pool->queue_head = newworker;
        }

        assert (pool->queue_head != NULL);

        pool->cur_queue_size++;
        //unlock
        pthread_mutex_unlock (&(pool->queue_lock));
        /*好了,等待队列中有任务了,唤醒一个等待线程;
         注意如果所有线程都在忙碌,这句没有任何作用*/
        pthread_cond_signal (&(pool->queue_ready));
        return 0;
    }


    /*销毁线程池,等待队列中的任务不会再被执行,但是正在运行的线程会一直
    把任务运行完后再退出*/
    int pool_destroy ()
    {
        if (pool->shutdown)
            return -1;/*防止两次调用*/
        pool->shutdown = 1;

        /*唤醒所有等待线程,线程池要销毁了*/
        pthread_cond_broadcast (&(pool->queue_ready));

        /*阻塞等待线程退出,否则就成僵尸了*/
        int i;
        for (i = 0; i < pool->max_thread_num; i++)
            pthread_join (pool->threadid[i], NULL);
        free (pool->threadid);

        /*销毁等待队列*/
        CThread_worker *head = NULL;
        while (pool->queue_head != NULL)
        {
            head = pool->queue_head;
            pool->queue_head = pool->queue_head->next;
            free (head);
        }
        /*条件变量和互斥量也别忘了销毁*/
        pthread_mutex_destroy(&(pool->queue_lock));
        pthread_cond_destroy(&(pool->queue_ready));
        
        free (pool);
        /*销毁后指针置空是个好习惯*/
        pool=NULL;
        return 0;
    }


    void *thread_routine (void *arg)
    {
        printf ("starting thread 0x%0x\n", pthread_self ());
        while (1)
        {
            pthread_mutex_lock (&(pool->queue_lock));
            /*如果等待队列大小为0并且不销毁线程池,则处于阻塞状态; 注意
            pthread_cond_wait是一个原子操作,等待前会解锁,唤醒后会加锁*/
            while (pool->cur_queue_size == 0 && !pool->shutdown)//防止意外被唤醒,采用while循环
            {
                printf ("thread 0x%x is waiting\n", pthread_self ());
                pthread_cond_wait (&(pool->queue_ready), &(pool->queue_lock));
                /*
                    条件变量是利用线程间共享的全局变量进行同步的一种机制,
                    主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;
                                另一个线程使"条件成立"(给出条件成立信号)。
                    为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
                */
            }

            /*线程池要销毁了*/
            if (pool->shutdown)
            {
                /*遇到break,continue,return等跳转语句,千万不要忘记先解锁*/
                pthread_mutex_unlock (&(pool->queue_lock));
                printf ("thread 0x%x will exit\n", pthread_self ());
                pthread_exit (NULL);
            }

            printf ("thread 0x%x is starting to work\n", pthread_self ());

            /*assert是调试的好帮手*/
             assert (pool->cur_queue_size != 0);
             assert (pool->queue_head != NULL);
            
            /*等待队列长度减去1,并取出链表中的头元素*/
             pool->cur_queue_size--;
             CThread_worker *worker = pool->queue_head;
             pool->queue_head = worker->next;
             pthread_mutex_unlock (&(pool->queue_lock));

            /*调用回调函数,执行任务*/
             (*(worker->process)) (worker->arg);
             free (worker);
             worker = NULL;
         }
        /*这一句应该是不可达的*/
         pthread_exit (NULL);
    }


    void *myprocess (void *arg)
    {
        printf ("threadid is 0x%x, working on task %d\n", pthread_self (),*(int *) arg);
        sleep (1);/*休息一秒,延长任务的执行时间*/
        return NULL;
    }

    int main (int argc, char **argv)
    {
        pool_init (3);/*线程池中最多三个活动线程*/
        
        /*连续向池中投入10个任务*/
        int *workingnum = (int *) malloc (sizeof (int) * 10);
        int i;
        for (i = 0; i < 10; i++)
         {
             workingnum[i] = i;
             pool_add_worker (myprocess, &workingnum[i]);
         }
        /*等待所有任务完成*/
         sleep (5);
        /*销毁线程池*/
         pool_destroy ();

         free (workingnum);
        return 0;
    }
    
//互斥锁
/*
互斥锁是共享资源的入口,在这一机制中,线程需要通过互斥锁才能实现对共享资源的访问。互斥锁的操作步骤如下:
互斥锁初始化:pthread_mutex_init
互斥锁上锁:pthread_mutex_lock
互斥锁判断上锁:pthread_mutex_trylock
互斥锁解锁:pthread_mutex_unlock
消除互斥锁:pthread_mutex_destroy
有三种类型的互斥锁:快速互斥锁、递归互斥锁、检错互斥锁。
快速互斥锁,是指一个线程访问一个互斥锁,如果发现该锁已经上锁,则线程阻塞,直到解锁为止。

这里就有有趣的概念:死锁。

设有3个线程A、B、C,并有3个共享资源a、b、c。
现在资源的占有情况以及上锁情况如下:

A---mutexa---a
B---mutexb---b
C---mutexc---c

如果

线程A的继续执行需要资源b,在获得资源b之前是不会放弃资源a的,即不会将mutexa开锁;
线程B的继续执行需要资源c,在获得c之前不放弃b,即不开锁mutexb;
线程C的继续执行需要资源a,在获得a之前不放弃c,即不开锁mutexc;

并且,三把锁mutexa、mutexb、mutexc都是快速互斥锁,即不可被抢占,

那么
就会发生有趣的死锁现象,也就是三个线程A、B、C都阻塞。

防止发生死锁现象的方法有两种:1,不要形成环状的资源访问情况;
                              2,互斥锁不要设置成不可抢占的快速互斥锁。

*/
 --------------------------------------------------------------  
 0、条件变量
    条件变量是利用线程间共享的全局变量进行同步的一种机制,
    主要包括两个动作:
        一个线程等待"条件变量的条件成立"而挂起;
        另一个线程使"条件成立"(给出条件成立信号)。
    为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。  
    
1.初始化条件变量pthread_cond_init

    #include <pthread.h>
    int pthread_cond_init(pthread_cond_t *cv, const pthread_condattr_t *cattr);
    返回值:函数成功返回0;任何其他返回值都表示错误

    初始化一个条件变量。当参数cattr为空指针时,函数创建的是一个缺省的条件变量。
    否则条件变量的属性将由cattr中的属性值来决定。调用 pthread_cond_init函数时,
    参数cattr为空指针等价于cattr中的属性为缺省属性,只是前者不需要cattr所占用的内存开销。
    这个函数返回时,条件变量被存放在参数cv指向的内存中。
-----------------------------------------------------------------------------------------------------------
  尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr值通常为NULL,且被忽略。  |
-----------------------------------------------------------------------------------------------------------
    可以用宏PTHREAD_COND_INITIALIZER来初始化静态定义的条件变量,使其具有缺省属性。
    这和用pthread_cond_init函数动态分配的效果是一样的。初始化时不进行错误检查。如:

    pthread_cond_t cv = PTHREAD_COND_INITIALIZER;

    不能由多个线程同时初始化一个条件变量。当需要重新初始化或释放一个条件变量时,
    应用程序必须保证这个条件变量未被使用。

2.阻塞在条件变量上pthread_cond_wait

-----------------------------------------------------------------------
等待条件有两种方式:
    无条件等待pthread_cond_wait()和计时等待pthread_cond_timedwait(),
    其中计时等待方式如果在给定时刻前条件没有满足,
    则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。  
   
   无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。
   mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),
   且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),
   而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。
   在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()
   前的加锁动作对应.
   
   激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;
   而pthread_cond_broadcast()则激活所有等待线程。
-----------------------------------------------------------------------
    #include <pthread.h>
    int pthread_cond_wait(pthread_cond_t *cv, pthread_mutex_t *mutex);
    返回值:函数成功返回0;任何其他返回值都表示错误
    
    函数将解锁mutex参数指向的互斥锁,并使当前线程阻塞在cv参数指向的条件变量上。
    
    被阻塞的线程可以被pthread_cond_signal函数,pthread_cond_broadcast函数唤醒,
    也可能在被信号中断后被唤醒。

    pthread_cond_wait函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。

    pthread_cond_wait函数返回时,相应的互斥锁将被当前线程锁定,即使是函数出错返回。

    该函数总是和互斥锁一起使用,此函数pthread_cond_wait(cond, mutex)先自动释放指定的锁,
    然后等待条件变量的变化。如果在此之前互斥锁没有被锁住,则函数的执行结果不确定。
    在返回原调用函数之前,此函数自动将指定的互斥量重新锁住。所以通常使用方法:
-----------------------------------------------------------
    pthread_mutex_lock(&mutex);                        |
    //                                                |
    //进行的相关操作                                |
    //                                                |
    if ( pthread_cond_wait(cond, mutex) != 0 )        |
    {                                                |
        pthread_mutex_unlock(&mutex);                |
        //错误处理                                    |
    }                                                |
    //                                                |
    //进行的相关操作                                |
    //                                                |
    pthread_mutex_unlock(&mutex);                    |
------------------------------------------------------

    一般一个条件表达式都是在一个互斥锁的保护下被检查。当条件表达式未被满足时,
    线程将仍然阻塞在这个条件变量上。当另一个线程改变了条件的值并向条件变量发出信号时,
    等待在这个条件变量上的一个线程或所有线程被唤醒,接着都试图再次占有相应的互斥锁。

    阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的
    互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用pthread_cond_wait函数,
    并把满足条件的表达式置为循环的终止条件。如:

    pthread_mutex_lock();
    while (condition_is_false)
        pthread_cond_wait();
    pthread_mutex_unlock();

    阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。

    注意:pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,
    这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。

 
3.解除在条件变量上的阻塞pthread_cond_signal

    #include <pthread.h>
    int pthread_cond_signal(pthread_cond_t *cv);
    返回值:函数成功返回0;任何其他返回值都表示错误

    函数被用来释放被阻塞在指定条件变量上的一个线程。

    必须在互斥锁的保护下使用相应的条件变量。否则对条件变量的解锁有可能发生在锁定条件变量之前,
    从而造成死锁。如下使用:
-----------------------------------------
    pthread_mutex_lock(&found->lock);    |
    pthread_cond_signal(&found->cond);    |
    pthread_mutex_unlock(&found->lock);    |
----------------------------------------

    唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,
    系统将根据线程的优先级唤醒线程。

    如果没有线程被阻塞在条件变量上,那么调用pthread_cond_signal()将没有作用。

 
4.阻塞直到指定时间pthread_cond_timedwait

    #include <pthread.h>
    #include <time.h>
    int pthread_cond_timedwait(pthread_cond_t *cv,
                pthread_mutex_t *mp, const structtimespec * abstime);
    返回值:函数成功返回0;任何其他返回值都表示错误

    函数到了一定的时间,即使条件未发生也会解除阻塞。这个时间由参数abstime指定。
    函数返回时,相应的互斥锁往往是锁定的,即使是函数出错返回。

    注意:pthread_cond_timedwait函数也是退出点。

    超时时间参数是指一天中的某个时刻。使用举例:

    pthread_timestruc_t to;
    to.tv_sec = time(NULL) + TIMEOUT;
    to.tv_nsec = 0;

    超时返回的错误码是ETIMEDOUT。

 
5.释放阻塞的所有线程pthread_cond_broadcast

    #include <pthread.h>
    int pthread_cond_broadcast(pthread_cond_t *cv);
    返回值:函数成功返回0;任何其他返回值都表示错误

    函数唤醒所有被pthread_cond_wait函数阻塞在某个条件变量上的线程,参数cv被用来指定这个条件变量。
    当没有线程阻塞在这个条件变量上时,pthread_cond_broadcast函数无效。

    由于pthread_cond_broadcast函数唤醒所有阻塞在某个条件变量上的线程,
    这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用pthread_cond_broadcast函数。

 
6.释放条件变量pthread_cond_destroy

    #include <pthread.h>
    int pthread_cond_destroy(pthread_cond_t *cv);
    返回值:函数成功返回0;任何其他返回值都表示错误

    释放条件变量:
        注意:条件变量占用的空间并未被释放。

 
7.唤醒丢失问题

    在线程未获得相应的互斥锁时调用pthread_cond_signal或pthread_cond_broadcast函数可能会引起唤醒丢失问题。

    唤醒丢失往往会在下面的情况下发生:

        一个线程调用pthread_cond_signal或pthread_cond_broadcast函数;
        另一个线程正处在测试条件变量和调用pthread_cond_wait函数之间;
    没有线程正在处在阻塞等待的状态下。

猜你喜欢

转载自blog.csdn.net/qianniu2meiyi/article/details/7635299