linux C 线程池

1. 总结理解

线程池的是什么?

线程池就好比水龙头的水流入一个较大的水池,当需要水的时候,5个人挑着扁担,每个人同时可以去打10桶水(各自打各自的2通水,互不影响),然后挑着就走。如果没有水池,那么每个人都需要在从水龙头上接自己的2桶水,谁先到水就先打水,这样打水的效率极低,5个人都需要排队打10桶水。

所以:线程池,顾名思义就是由多个线程组成的“水池”,当有任务需要执行时,由空闲线程进行处理,若没有任务,则线程处于休眠等待的状态。

什么时候需要创建线程池呢?(为什么要使用线程池?)

如果一个应用程序需要频繁创建、销毁线程,而任务执行的时间又非常短,这样线程创建和销毁的带来的开销就不容忽视,这时也是线程池该出场的机会了;

如果线程创建和销毁时间相比任务执行时间可以忽略不计,则没有必要使用线程池了。

比如线程池有3个线程,那调用线程池的程序中,至少会有几个线程呢?(如下实例)

至少会有4个线程,因为主进程也是一个线程。

2. 参考blog:

线程池基本原理:

在传统服务器结构中,常是有一个总的 监听线程监听有没有新的用户连接服务器, 每当有一个新用户进入,服务器就开启一个新的线程用户处理这 个用户的数据包。这个线程只服务于这个用户,当 用户与服务器端关闭连接以后,服务器端销毁这个线程。然而频繁地开辟与销毁线程极大地占用了系统的资源。而且在大量用户的情况下,系统为了开辟和销毁线程将浪费大量的时间和资源。

        线程池提供了一个解决外部大量用户与服务器有限资源的矛盾,线程池和传统的一个用户对应一 个线程的处理方法不同,它的基本思想就是在程序开始时就在内存中开辟一些线程,线程的数目是固定的,他们独自形成一个类,屏蔽了对外的操作,而服务器只需要将数据包交给线程池就可以了。当有新的客户请求到达时,不是新创建一个线程为其服务,而是从“池子”中选择一个空闲的线程为新的客户请求服务,服务完毕后,线程进入空闲线程池中。如果没有线程空闲 的 话, 就将数据包 暂时积累 , 等待线程池内有线 程空闲以后再进行处理。通过对多个任务重用已经存在的线程对象 , 降低了对线程对象创建和销毁的开销。当客户请求时 , 线程对象已经存在 , 可以提高请求的响应时间 , 从而整体地提高了系统服务的表现。

另一种线程池描述

        大多数的网络服务器,包括Web服务器都具有一个特点,就是单位时间内必须处理数目巨大的连接请求,但是处理时间却是比较短的。在传统的多线程服务器模型中是这样实现的:一旦有个请求到达,就创建一个新的线程,由该线程执行任务,任务执行完毕之后,线程就退出。这就是"即时创建,即时销毁"的策略。尽管与创建进程相比,创建线程的时间已经大大的缩短,但是如果提交给线程的任务是执行时间较短,而且执行次数非常频繁,那么服务器就将处于一个不停的创建线程和销毁线程的状态。这笔开销是不可忽略的,尤其是线程执行的时间非常非常短的情况。
  线程池就是为了解决上述问题的,它的实现原理是这样的:在应用程序启动之后,就马上创建一定数量的线程,放入空闲的队列中。这些线程都是处于阻塞状态,这些线程只占一点内存,不占用CPU。当任务到来后,线程池将选择一个空闲的线程,将任务传入此线程中运行。当所有的线程都处在处理任务的时候,线程池将自动创建一定的数量的新线程,用于处理更多的任务。执行任务完成之后线程并不退出,而是继续在线程池中等待下一次任务。当大部分线程处于阻塞状态时,线程池将自动销毁一部分的线程,回收系统资源。

linux下C 线程池的原理讲解和代码实现(能自行伸缩扩展线程数)                      https://blog.51cto.com/lonelyc/1315434

一般来说实现一个线程池主要包括以下几个组成部分:

1)线程管理器:用于创建并管理线程池。
2)工作线程:线程池中实际执行任务的线程。在初始化线程时会预先创建好固定数目的线程在池中,这些初始化的线程一般处于空闲状态,一般不占用CPU,占用较小的内存空间。
3)任务接口:每个任务必须实现的接口,当线程池的任务队列中有可执行任务时,被空闲的工作线程调去执行(线程的闲与忙是通过互斥量实现的,跟前面文章中的设置标志位差不多),把任务抽象出来形成接口,可以做到线程池与具体的任务无关。
4)任务队列:用来存放没有处理的任务,提供一种缓冲机制,实现这种结构有好几种方法,常用的是队列,主要运用先进先出原理,另外一种是链表之类的数据结构,可以动态的为它分配内存空间,应用中比较灵活,下文中就是用到的链表。


在Linux系统下用C语言创建的一个线程池的步骤:

1) 线程池会维护一个任务链表(每个CThread_worker结构就是一个任务)。
2) pool_init()函数预先创建好max_thread_num个线程,每个线程执thread_routine ()函数。该函数中

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

https://blog.csdn.net/ygl840455828ygl/article/details/52351003

线程池的创建和使用(总结1) ???

https://blog.csdn.net/li646495946/article/details/106904631

3. 应用实例

#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 *g_pool = NULL;

void pool_init(int max_thread_num)
{
    g_pool = (CThread_pool *)malloc(sizeof (CThread_pool));
    pthread_mutex_init (&(g_pool->queue_lock), NULL);
    pthread_cond_init (&(g_pool->queue_ready), NULL);

    g_pool->queue_head = NULL;
    g_pool->max_thread_num = max_thread_num;
    g_pool->cur_queue_size = 0;
    g_pool->shutdown = 0;
    g_pool->threadid = (pthread_t *) malloc (max_thread_num * sizeof (pthread_t));

    int i = 0;
    for (i = 0; i < max_thread_num; i++) {
        pthread_create(&(g_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;

	pthread_mutex_lock (&(g_pool->queue_lock));
	/* 将任务加入到等待队列中 */
	CThread_worker *tmpHead = g_pool->queue_head;
	if (tmpHead != NULL) {
		while (tmpHead->next != NULL) {
			tmpHead = tmpHead->next;
		}
		tmpHead->next = newworker;
	} else {
		g_pool->queue_head = newworker;
	}
	assert (g_pool->queue_head != NULL);
	g_pool->cur_queue_size++;
	pthread_mutex_unlock (&(g_pool->queue_lock));

	printf("*** pthread 0x%x signal, arg: (%d) addr %p ***\n", pthread_self(), *(int *)arg, (int *)arg);
	/* 等待队列中有任务了,唤醒一个等待线程;
		注意如果所有线程都在忙碌,这句没有任何作用 */
	pthread_cond_signal (&(g_pool->queue_ready));
	return 0;
}
 
 
/* 销毁线程池,等待队列中的任务不会再被执行,
   但是正在运行的线程会一直把任务运行完后再退出 */
int pool_destroy(void)
{
    if (g_pool->shutdown) {
        return -1;/*防止两次调用*/
	}
    g_pool->shutdown = 1;
 
    /*唤醒所有等待线程,线程池要销毁了*/
    pthread_cond_broadcast (&(g_pool->queue_ready));
 
    /*阻塞等待线程退出,否则就成僵尸了*/
    int i;
    for (i = 0; i < g_pool->max_thread_num; i++) {
        pthread_join (g_pool->threadid[i], NULL);
	}
    free (g_pool->threadid);
 
    /*销毁等待队列*/
    CThread_worker *head = NULL;
    while (g_pool->queue_head != NULL) {
        head = g_pool->queue_head;
        g_pool->queue_head = g_pool->queue_head->next;
        free (head);
    }
    /*条件变量和互斥量也别忘了销毁*/
    pthread_mutex_destroy(&(g_pool->queue_lock));
    pthread_cond_destroy(&(g_pool->queue_ready));
 
    free (g_pool);
    /*销毁后指针置空是个好习惯*/
    g_pool=NULL;
    return 0;
}

void *thread_routine(void *arg)
{
    printf ("[starting thread] 0x%x\n", pthread_self());
    while (1) {
        pthread_mutex_lock (&(g_pool->queue_lock));
        /*如果等待队列为0并且不销毁线程池,则处于阻塞状态;
         * 注意: pthread_cond_wait是一个原子操作,等待前会解锁,唤醒后会加锁*/
        while (g_pool->cur_queue_size == 0 && !g_pool->shutdown) {
            printf ("thread      0x%x is waiting\n", pthread_self());
            pthread_cond_wait(&(g_pool->queue_ready), &(g_pool->queue_lock));
        }

        if (g_pool->shutdown) {
            pthread_mutex_unlock (&(g_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 (g_pool->cur_queue_size != 0);
        assert (g_pool->queue_head != NULL);
 
        g_pool->cur_queue_size--;
        CThread_worker *worker = g_pool->queue_head;
        g_pool->queue_head = worker->next;
        pthread_mutex_unlock (&(g_pool->queue_lock));
 
        /* 调用回调函数,执行任务 */
        (*(worker->process)) (worker->arg);
        free (worker);
        worker = NULL;
    }
	printf("pthread_exit (NULL)?????\n");
    /*这一句应该是不可达的*/
    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);/*线程池中最多三个活动线程*/

	sleep(1);
    /*连续向池中投入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,表示引用gcc编译使用了POSIX thread的库函数

(pthread是动态库,需要用-lpthread,所有的动态库都需要用-lxxx来引用gcc编译使用了POSIX thread的程序时通常需要加额外的选项)

程序执行结果如下:

[zll@zll linux_dataStruct]$ gcc 01_pool.c  -o 01_pool -pthread
[zll@zll linux_dataStruct]$ 
[zll@zll linux_dataStruct]$ 
[zll@zll linux_dataStruct]$ 
[zll@zll linux_dataStruct]$ ./01_pool
[starting thread] 0xb637fb70
thread      0xb637fb70 is waiting
[starting thread] 0xb6d80b70
thread      0xb6d80b70 is waiting
[starting thread] 0xb7781b70
thread      0xb7781b70 is waiting
*** pthread 0xb77826c0 signal, arg: (0) addr 0x8481228 ***
*** pthread 0xb77826c0 signal, arg: (1) addr 0x848122c ***
*** pthread 0xb77826c0 signal, arg: (2) addr 0x8481230 ***
*** pthread 0xb77826c0 signal, arg: (3) addr 0x8481234 ***
*** pthread 0xb77826c0 signal, arg: (4) addr 0x8481238 ***
*** pthread 0xb77826c0 signal, arg: (5) addr 0x848123c ***
*** pthread 0xb77826c0 signal, arg: (6) addr 0x8481240 ***
*** pthread 0xb77826c0 signal, arg: (7) addr 0x8481244 ***
*** pthread 0xb77826c0 signal, arg: (8) addr 0x8481248 ***
*** pthread 0xb77826c0 signal, arg: (9) addr 0x848124c ***
thread      0xb637fb70 is starting to work
threadid is 0xb637fb70, working on task 0
thread      0xb6d80b70 is starting to work
threadid is 0xb6d80b70, working on task 1
thread      0xb7781b70 is starting to work
threadid is 0xb7781b70, working on task 2
thread      0xb637fb70 is starting to work
threadid is 0xb637fb70, working on task 3
thread      0xb6d80b70 is starting to work
threadid is 0xb6d80b70, working on task 4
thread      0xb7781b70 is starting to work
threadid is 0xb7781b70, working on task 5
thread      0xb637fb70 is starting to work
threadid is 0xb637fb70, working on task 6
thread      0xb6d80b70 is starting to work
threadid is 0xb6d80b70, working on task 7
thread      0xb7781b70 is starting to work
threadid is 0xb7781b70, working on task 8
thread      0xb637fb70 is starting to work
threadid is 0xb637fb70, working on task 9
thread      0xb6d80b70 is waiting
thread      0xb7781b70 is waiting
thread      0xb637fb70 is waiting
thread      0xb6d80b70 will exit
thread      0xb7781b70 will exit
thread      0xb637fb70 will exit
[zll@zll linux_dataStruct]$ 

 

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值