Linux线程与线程池

一,线程简介

1)什么是线程

线程是cpu调度的基本单位,是进程的内部的⼀条执⾏流。(线程再进程地址空间内运⾏),⼀切进程至少有⼀个执⾏线程。

2)线程的实现原理简介

1.线程就是⼀个执⾏流,创建⼀个线程就是在内核中创建⼀个PCB,这个PCB指向了进程的虚拟地址空间。所以真正去执⾏代码的是每⼀个进程内部的执⾏流,也就是线程。
2.创建出来的线程的PCB也是需要放在内核中双向链表上的,也就意味着操作系统可以通过链表来获取到线程的PCB,从⽽调度线程获取CPU资源,因此线程是操作系统调度的最⼩单位。

3)线程的优缺点

优点:
1.与进程相比,线程是一种非常“节俭”的多任务操作方式。
在Linux系统下,启动⼀个新的进程必须分配给它独⽴的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是⼀种"昂贵"的多任务⼯作⽅式。而运行于⼀个进程中的多个线程,它们彼此之间使⽤相同的地址空间,共享大部分数据,启动⼀个线程所花费的空间远远小于启动⼀个进程所花费的空间,⽽且,线程间彼此切换所需的时间也远远⼩于进程间切换所需要的时间,据统计⼀个进程的开销⼤概是⼀个线程开销的30倍左右。

2.线程间方便的通信机制
对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进⾏,这种方式不仅费时,⽽且很不⽅便。线程则不然,由于同⼀进程下的线程之间共享数据空间,所以⼀个线程的数据可以直接为其它线程所⽤,这不仅快捷,而且方便。当然,数据的共享也带来其他⼀些问题,比如线程间同步和互斥。

3.能充分利⽤多处理器的并⾏数量
CPU ⼯作的分配⼀般是以线程为级别的,只不过⼤部分程序⼀个进程只⽤⼀个线程,所以看起来好像 CPU 只能是以进程为粒度分配任务。

二,POSIX线程库函数

线程有⼀套完整的与线程相关的函数调⽤,它们中的⼤多函数都是以pthread_开头,为了使⽤这些函数调⽤,必须要包含头文件pthread.h.并且在编译程序时需要用选项-lpthread来链接线程库。
线程主要操作包括线程的创建,等待、取消退出等。

1)创建线程

线程创建实际上就是在确定线程函数调用的入口地址
功能:创建⼀个新的线程
原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
参数:
thread:返回线程ID,pthread_t是⼀个unsigned long int类型。
attr:设置线程的属性,attr为NULL表示使⽤默认属性。
start_routine:是个函数地址,线程启动后要执行的函数。
arg:传给线程启动函数的参数。
 
返回值:成功返回0;失败返回错误码。

2)获取线程ID

3)线程等待函数

在Linux中,默认情况下是在⼀个线程被创建后,必须使用pthread_join对创建的线程进⾏资源回收,但是可以设置线程来设置当⼀个线程结束时,直接回收此线程所占⽤的系统资源。
进程结束时,所有的线程都终止。
 

4)线程的退出

线程创建完成后开始执⾏线程函数,执行完成该函数就退出了,但还有⼀种退出线程的方法 调用pthread_exit 函数。

5)线程的取消

线程的终⽌主要有两种情况:
1、正常终止(线程函数执⾏完成或线程主动调⽤pthread_exit退出)
2、⾮正常终止
就是线程在其他线程的⼲预下,或⾃身运⾏出错⽽退出,这种退出⽅式是不可以预见的。
如果⼀个线程期望去终止另⼀个线程,可以使⽤pthread_cancel函数实现,与信号处理⼀样,线程可以被要求终止时改变其行为。

可以使⽤pthread_setcancelstate函数设置本线程对Cancel信号的反应,即是否接受其他线程发过来的取消要求。如果取消请求被接受,可以⽤pthread_setcanceltype设置取消类型,具体函数说明如下:
phtread_setcanceltype函数用来设置本线程取消动作的执⾏时机。
取消点有哪些?  
所谓“取消点”就是⼀个函数,例如sleep(),当线程体运⾏到sleep()的时候,就可以响应cancel。
1:通过pthread_testcancel 调⽤已编程⽅式建⽴线程取消点
2:线程等待pthread_cond_wait或pthread_cond_timewait中的特定条件
3:被sigwait阻塞的函数  
4:⼀些标准的库调⽤。通常这些调⽤包括线程可基于阻塞的函数    根据POSIX标准 pthread_join、pthread_testcancel,pthread_cond_wait、pthread_cond_timedwait、sem_wait、sigwait等函数以及   read()、write()等会引起阻塞的系统调⽤都是取消点。

6)线程的属性设置

如果我们对程序的性能提出更⾼的要求,就需要设置线程属性,比如可以通过设置线程栈的⼤⼩来降低内存的使⽤,加最⼤线程个数,设置线程回收资源的时机等。
其中创建线程函数的第⼆个参数就是⽤来设置线程的属性。
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
其中
线程属性pthread_attr_t的结构如下:
typedef struct
 {
 int detachstate; //线程的分离状态
 int schedpolicy; //线程调度策略
 struct sched_param schedparam; //线程的调度参数
 int inheritsched; //线程的继承性
 int scope; //线程的作⽤域
 size_t guardsize; //线程栈末尾的警戒缓冲区⼤⼩
 int stackaddr_set;
 void * stackaddr; //线程栈的位置
 size_t stacksize; //线程栈的⼤⼩
 }pthread_attr_t;

线程的属性很多,其中着重关注的几个属性是:

线程的分离状态detachstate属性说明:
线程的分离状态决定⼀个线程以什么样的⽅式来终止自己。
非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,⻢上释放系统资源。
线程的分离属性设置函数:
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
形参detachstate取值:
PTHREAD_CREATE_DETACHED(分离线程);
PTHREAD _CREATE_JOINABLE(⾮分离线程);
作⽤域 scope说明:
功能: 设置线程 __scope 属性。scope属性表示线程间竞争CPU的范围,也就是说线程优先级的有效范围。
POSIX的标准中定义了两个值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示与系统中所有线程⼀起竞争CPU时间,后者表示仅与同进程中的线程竞争CPU。
默认为PTHREAD_SCOPE_PROCESS

设置线程是否继承调度政策:

当需要给一个线程设置调度方面的属性时,必须先将线程的inheritsched设置为PTHREAD_EXPLICIT_SCHED。

以设置分离属性为例,设置线程属性的步骤如下:
1)定义线程属性变量: pthread_attr_t attr
初始化attr, pthread_attr_init(&attr) //成功:0;失败:错误号 ,初始化会为属性对象分配内存空间
2)设置线程为分离或⾮分离: pthread_attr_setdetachstate(&attr,detachstate);
3)创建线程pthread_create(&tid,&attr,thread_fun,NULL); 所有的系统都会⽀持线程的分离状态属性。
4)销毁线程属性所占⽤的资源——int pthread_attr_destroy(pthread_attr_t*attr); 成功:0;失败:错误号。
 


三,线程示例程序

thread.c
 

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>

// 共享缓冲区
#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int count = 0;

// 互斥锁和条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_full  = PTHREAD_COND_INITIALIZER;

// 生产者线程函数
void* producer(void* arg) {
    pthread_t tid = pthread_self();
    for (int i = 1; i <= 10; i++) {
        pthread_mutex_lock(&mutex);
        while (count == BUFFER_SIZE) {
            printf("[Producer %lu] Buffer full, waiting...\n", tid);
            pthread_cond_wait(&not_full, &mutex);
        }
        buffer[count++] = i;
        printf("[Producer %lu] Produced %d (count=%d)\n", tid, i, count);
        pthread_cond_signal(&not_empty);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    pthread_exit("Producer finished");
}

// 消费者线程函数
void* consumer(void* arg) {
    pthread_t tid = pthread_self();
    while (1) {
        pthread_mutex_lock(&mutex);
        while (count == 0) {
            printf("[Consumer %lu] Buffer empty, waiting...\n", tid);
            pthread_cond_wait(&not_empty, &mutex);
        }
        int item = buffer[--count];
        printf("[Consumer %lu] Consumed %d (count=%d)\n", tid, item, count);
        pthread_cond_signal(&not_full);
        pthread_mutex_unlock(&mutex);
        sleep(2);
    }
    pthread_exit("Consumer finished");
}

// 可被取消的线程
void* cancellable(void* arg) {
    pthread_t tid = pthread_self();
    while (1) {
        printf("[Cancellable %lu] Running...\n", tid);
        sleep(1); // 取消点
    }
    return NULL;
}

// 分离线程
void* detached_routine(void* arg) {
    pthread_t tid = pthread_self();
    printf("[Detached %lu] Started (detached thread)\n", tid);
    sleep(3);
    printf("[Detached %lu] Finished automatically\n", tid);
    return NULL;
}

int main() {
    pthread_t prod, cons, cancel_th, detached;
    void* retval;

    // 1. 创建生产者线程
    pthread_create(&prod, NULL, producer, NULL);

    // 2. 创建消费者线程
    pthread_create(&cons, NULL, consumer, NULL);

    // 3. 创建可取消线程
    pthread_create(&cancel_th, NULL, cancellable, NULL);

    // 4. 创建分离线程
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
    pthread_create(&detached, &attr, detached_routine, NULL);
    pthread_attr_destroy(&attr);

    // 等待生产者线程退出
    pthread_join(prod, &retval);
    printf("[Main] Producer exited: %s\n", (char*)retval);

    // 等待一会再取消 cancellable 线程
    sleep(5);
    printf("[Main] Cancelling cancellable thread...\n");
    pthread_cancel(cancel_th);
    pthread_join(cancel_th, &retval);
    if (retval == PTHREAD_CANCELED) {
        printf("[Main] Cancellable thread was canceled\n");
    }

    // 消费者线程是无限循环,这里演示强制退出
    pthread_cancel(cons);
    pthread_join(cons, &retval);
    printf("[Main] Consumer thread ended\n");

    printf("[Main] Program finished\n");
    return 0;
}

运行结果:

看到不同线程的执行情况:

生产者不断产生数据

消费者取走数据

一个线程被取消

分离线程自动结束

四,线程池

1)线程池的工作机制:

1.在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。

2.⼀个线程同时只能执行⼀个任务,但可以同时向⼀个线程池提交多个任务。

3.使用线程池的原因:
多线程运⾏时间,系统不断的启动和关闭新线程,成本非常高,会过渡消耗系统资源,以及过渡切换线程的危险,从而可能导致系统资源的崩溃。
这样就避免了繁琐的创建和结束线程的时间,有效的利⽤了CPU资源。
应用场景有大量的数据处理请求,需要执行流并行或并发处理。

2)线程池实现

涉及的重要函数主要有:
线程创建函数:pthread_create();
线程休眠函数:pthread_cond_wait();
线程唤醒函数:pthread_cond_signal ();
线程上锁函数:pthread_mutex_lock ();
线程解锁函数:pthread_mutex_unlock ();
程序函数说明:
CThread_worker :描述链表的结点结构
CThread_pool:描述线程池结构
void pool_init (int max_thread_num) :初始化线程池对象并给池⼦创建指定数量的线程
int pool_add_worker (void *(*process) (void *arg), void *arg) :给线程添加任务到链表中
void * thread_routine (void *arg) :线程函数通⽤接⼝,负责阻塞线程,取任务和执行函数
int pool_destroy (): 销毁线程池

线程池示例程序:

 
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

// 描述任务的结构(链表节点)
typedef struct CThread_worker {
    void *(*process) (void *arg);   // 任务函数指针
    void *arg;                      // 任务函数参数
    struct CThread_worker *next;    // 指向下一个节点的指针
} CThread_worker;

// 描述线程池的结构
typedef struct {
    pthread_mutex_t mutex;          // 互斥锁,用于保护任务队列
    pthread_cond_t cond;            // 条件变量,用于线程的休眠和唤醒

    CThread_worker *queue_head;     // 任务队列的头指针

    int shutdown;                   // 标志线程池是否销毁
    pthread_t *threads;             // 线程ID数组
    int thread_count;               // 线程数量
} CThread_pool;

// 全局线程池指针
CThread_pool *pool = NULL;

// 线程函数,负责阻塞线程,取任务和执行任务
void *thread_routine(void *arg) {
    printf("线程 0x%lx 启动...\n", (unsigned long)pthread_self());

    while (1) {
        pthread_mutex_lock(&(pool->mutex));

        // 如果任务队列为空并且线程池没有关闭,则线程休眠
        while (pool->queue_head == NULL && !pool->shutdown) {
            // pthread_cond_wait会原子地:1. 解锁mutex 2. 阻塞等待 3. 被唤醒后重新加锁
            pthread_cond_wait(&(pool->cond), &(pool->mutex));
        }

        // 如果线程池被标记为销毁且任务队列已空,则退出线程
        if (pool->shutdown && pool->queue_head == NULL) {
            pthread_mutex_unlock(&(pool->mutex));
            printf("线程 0x%lx 退出。\n", (unsigned long)pthread_self());
            pthread_exit(NULL);
        }

        // 从任务队列头部取出一个任务
        CThread_worker *worker = pool->queue_head;
        pool->queue_head = worker->next;

        pthread_mutex_unlock(&(pool->mutex));

        // 执行任务
        (worker->process)(worker->arg);
        free(worker); // 释放已完成的任务节点
    }
    return NULL;
}

// 初始化线程池对象并给池子创建指定数量的线程
void pool_init(int max_thread_num) {
    pool = (CThread_pool *) malloc(sizeof(CThread_pool));

    pthread_mutex_init(&(pool->mutex), NULL);
    pthread_cond_init(&(pool->cond), NULL);

    pool->queue_head = NULL;
    pool->thread_count = max_thread_num;
    pool->shutdown = 0;

    pool->threads = (pthread_t *) malloc(sizeof(pthread_t) * max_thread_num);
    for (int i = 0; i < max_thread_num; i++) {
        pthread_create(&(pool->threads[i]), NULL, thread_routine, NULL);
    }
}

// 给线程池添加任务到链表中(尾插法)
int pool_add_worker(void *(*process) (void *arg), void *arg) {
    // 创建新的任务节点
    CThread_worker *new_worker = (CThread_worker *) malloc(sizeof(CThread_worker));
    new_worker->process = process;
    new_worker->arg = arg;
    new_worker->next = NULL;

    // 加锁保护任务队列
    pthread_mutex_lock(&(pool->mutex));

    // 将任务添加到队列尾部
    CThread_worker *member = pool->queue_head;
    if (member != NULL) {
        while (member->next != NULL) {
            member = member->next;
        }
        member->next = new_worker;
    } else {
        pool->queue_head = new_worker;
    }

    pthread_mutex_unlock(&(pool->mutex));

    // 唤醒一个正在等待的线程
    pthread_cond_signal(&(pool->cond));

    return 0;
}

// 销毁线程池
int pool_destroy() {
    if (pool->shutdown) {
        return -1; // 防止重复销毁
    }
    
    // 1. 设置销毁标志位
    pool->shutdown = 1;

    // 2. 唤醒所有可能在休眠的线程,让他们自己退出
    pthread_cond_broadcast(&(pool->cond));

    // 3. 等待所有线程执行完毕并退出
    for (int i = 0; i < pool->thread_count; i++) {
        pthread_join(pool->threads[i], NULL);
    }

    // 4. 释放线程ID数组
    free(pool->threads);

    // 5. 销毁可能还残留在任务队列中的任务
    CThread_worker *head = pool->queue_head;
    while (head != NULL) {
        CThread_worker *temp = head;
        head = head->next;
        free(temp);
    }

    // 6. 销毁互斥锁和条件变量
    pthread_mutex_destroy(&(pool->mutex));
    pthread_cond_destroy(&(pool->cond));
    
    // 7. 释放线程池结构体
    free(pool);
    pool = NULL;

    return 0;
}

// ---- 示例任务函数 ----
void* my_task(void* arg) {
    int task_num = *(int*)arg;
    printf("正在执行任务 %d (线程ID: 0x%lx)\n", task_num, (unsigned long)pthread_self());
    sleep(1); // 模拟任务耗时
    free(arg); // 释放传递进来的参数内存
    return NULL;
}

// ---- 主函数 (测试) ----
int main() {
    // 初始化一个包含4个线程的线程池
    pool_init(4);
    printf("线程池初始化完成。\n");

    // 添加10个任务到线程池
    for (int i = 0; i < 10; i++) {
        int* task_num = (int*)malloc(sizeof(int));
        *task_num = i + 1;
        pool_add_worker(my_task, (void*)task_num);
        printf("添加了任务 %d。\n", i + 1);
    }

    // 等待一段时间让所有任务都有机会被执行
    printf("\n等待所有任务执行...\n");
    sleep(5);

    // 销毁线程池
    printf("\n准备销毁线程池...\n");
    pool_destroy();
    printf("线程池已销毁。\n");

    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值