C++ (week5):Linux系统编程3:线程

文章目录

    • 三、线程
      • 1.线程的基本概念
        • 线程相关概念
        • ②我的理解
      • 2.线程的基本操作 (API)
        • (1)获取线程的标识:pthread_self
        • (2)创建线程:pthread_create()
        • (3)终止线程
          • pthread_exit():当前线程终止,子线程主动退出
          • ②pthread_cancel():发送取消请求,用一个线程终止另一个线程
        • (4)线程等待:pthread_join():(无限期)等待子线程结束,并接收子线程的返回值
        • (5)线程游离:pthread_detach():主线程将子线程设置为分离状态
        • (6)线程资源的清理:线程清理函数
          • ①pthread_cleanup_push()
          • ②pthread_cleanup_pop()
          • ③线程清理函数
      • 3.线程的同步 (sync)
        • (1)术语
        • (2)互斥
          • 互斥锁 (Mutex)
          • ②死锁 (Deadlock)
        • (3)条件变量 (Condition Variable):pthread_cond_t
          • ①初始化条件变量:pthread_cond_init()
          • 等待条件变量:pthread_cond_wait()
          • ③通知条件变量
            • i.pthread_cond_signal()
            • ii.pthread_cond_broadcast()
          • ④销毁条件变量:pthread_cond_destroy()
          • ⑤生产者-消费者模型
            • i.阻塞队列
            • ii.线程池:生产者消费者模型
      • 4.线程安全
      • 5.可重入性
      • 6.线程池 (Thread Pool)
      • 7.其他
        • (1)第一性原理
        • (2)知识图谱

三、线程

1.线程的基本概念

线程相关概念

1.什么是线程
线程是进程的一条执行流程。
线程被称为轻量级进程(Light Weight Process, LWP)


2.为什么要引入线程?/ 引入线程的好处

简单解释详细解释
①创建和销毁相对进程而言,线程的创建销毁是轻量级的线程的创建和销毁的开销比进程小。进程需要获取和释放资源,而线程拥有的资源较少:
①进程的创建和销毁更耗时,因为涉及资源的获取和释放
②线程拥有的资源少,创建和销毁比较轻量级,耗时短
②切换切换线程的开销比切换进程小。①同一进程的线程之间的切换,Cache、TLB不会失效,不需要读内存重载Cache和TLB,只需要切换线程的上下文,开销小。
②切换进程时,Cache、TLB会失效,重新载入需要读内存,开销巨大。(进程上下文切换的开销其实和线程切换上下文开销差不多)
③通信线程之间通信更简单。进程通信需要打破隔离①进程间通信,需要打破隔离,通信的代价大
②同一进程的线程之间通信开销很小,几乎没有代价
④异步引入线程机制,可以实现异步。充分利用了多核CPU的性能异步编程的优势:提高应用程序的响应性和性能

3.引入线程后
进程是资源分配的最小单位,线程是调度的最小单位。
线程们共享进程的所有资源,其他线程也可以访问主线程的栈空间。


4.主线程其他线程
主线程的栈大小:8MB
其他线程的栈大小:2MB


在这里插入图片描述


线程是进程的一条执行流程:main是主线程的执行流程,start_routine是子线程的执行流程
在这里插入图片描述


②我的理解

1.主子线程与父子进程的区别:
①父进程死亡,子进程变为孤儿进程
②主线程终止,代表整个进程终止,所有其他线程会被终止

2.主线程与子线程
①主线程代表进程的主流程,是老板,分配任务。主线程拆分任务,将任务分配给子线程。
②其他线程是员工,只完成自己的部分

3.为什么需要引入线程?其他线程和函数调用的区别是什么?
其他线程和函数调用的功能很像。
但是引入其他线程,相当于多了几个流程,几条线同时往下走,可以同时异步并发执行,利用了多核CPU
若只有进程的函数调用,则只有一根流程线 同步执行,相当于老板自己干活。只用了一个CPU。


2.线程的基本操作 (API)

1.线程函数
在这里插入图片描述

2.编译链接时,要加选项 -pthread-lpthread

3.pthread设计原则:
返回值为int类型,标识调用成功或失败
成功:返回0
失败:返回错误码errno,所以不会设置errno


(1)获取线程的标识:pthread_self
#include <pthread.h>

pthread_t pthread_self(void);  //unsigned long, %lu

①若能返回,一定成功。
②若失败,不返回。


(2)创建线程:pthread_create()

1.函数原型

#include <pthread.h>

int pthread_create(pthread_t* tid, const pthread_attr_t *attr,
                 void *(*start_routine) (void *), void *args);

参数:
①&tid:若线程创建成功,则修改tid的值为创建的线程的tid,pthread_t tid
②attr:线程属性,一般填NULL,表示采用默认属性
start_routine:线程的入口函数 (函数指针),是子线程的执行流程 (main是主线程的执行流程)
④args:线程入口函数的参数 (只能有一个参数,若需要传多个参数,可用结构体包装,并传结构体的指针)

void*:第三个参数可以返回任意值,第四个参数可以返回任意函数


2.返回值:
①成功,返回0
②失败,返回errno

如果线程创建成功,pthread_create()返回0,此时tid会被设置为新线程的线程ID。如果线程创建失败,pthread_create()返回一个非零的错误码,而tid的值不会被修改。


3.基本用法
(1)不传参,第四个参数为NULL

pthread_t tid;
int err = pthread_create(&tid, NULL, start_routine, NULL);
if(err){
	error(1, err, "pthread_create");
}
void* start_routine(void* args) {
    print_ids("new_thread");
    
    return NULL;
}

(2)传参

pthread_t tid;
int err = pthread_create(&tid, NULL, start_routine, void*(1027));
if(err){
	error(1, err, "pthread_create");
}
void* start_routine(void* args) {
    int i = (int)args; 	 //C语言是弱类型语言,可强转
    printf("new_thread: i = %d\n", i);
    
    return NULL;
}

4.代码示例
//①创建子线程,不传参数
//②创建子线程,传一个整数 [容得下,直接传]
//③在子线程中,访问主线程的栈的数据,传结构体指针 [容不下,传指向数据的指针,即传地址]

github网址:https://github.com/WangEdward1027/pthread/tree/main/pthread_create


(3)终止线程
终止进程终止线程
正常终止从main返回从start_routine返回 ×
正常终止exit()pthread_exit() √
异常终止收到信号pthread_cancel() √
注册退出函数atexit()注册进程退出函数pthread_cleanup_push()
pthread_cleanup_pop()

pthread_exit():当前线程终止,子线程主动退出

1.函数原型

#include <pthread.h>

void pthread_exit(void *retval);

(1)参数
①void* retval:返回 任意值,任意类型的数据(void*) 给主线程。

pthread_exit((void*)sum);
或者
return (void*)sum;

②主线程不需要子线程的返回结果,则子线程中调用

pthread_exit(NULL);return NULL;

③注意,不能返回指向该线程栈上数据的指针。因为当子线程退出时,子线程的栈空间会销毁

④pthread_exit()执行时,会执行线程清理函数,将cleanup栈中还未pop的栈帧全部弹出。


2.代码
//子线程使用pthread_exit()退出后, start_routine的后续代码不执行
github网址:https://github.com/WangEdward1027/pthread/blob/main/pthread_exit/pthread_exit.c


②pthread_cancel():发送取消请求,用一个线程终止另一个线程

1.功能
发送取消请求,用一个线程终止另一个线程。但对方不一定响应。

2.响应时机
会不会相应,以及何时响应,取决于目标线程的两个属性,CANCEL_STATE、CANCEL_TYPE


3.函数原型

#include <pthread.h>

int pthread_cancel(pthread_t tid);

4.返回值:
①成功,返回0
②失败,返回非0的错误码


5.取消状态、取消类型
(1)CANCEL_STATE:是否响应
①PTHREAD_CANCEL_ENABLE:能够响应。[默认值]
②PTHREAD_CANCEL_DISABLE:不响应

(2)CANCEL_TYPE:何时响应
①PTHREAD_CANCEL_DEFERRED:延迟响应,延迟到取消点才响应。[默认值]
②PTHREAD_CANCEL_ASYNCHRONOUS:立刻响应,在任何时刻都可以响应

线程的默认行为是延迟取消,即在取消点检查取消请求

(3)函数原型

#include <pthread.h>

int pthread_setcancelstate(int state, int *oldstate);
int pthread_setcanceltype(int type, int *oldtype);

(4)用法

int oldstate;  //保存旧的状态,以便后续恢复
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRNOUS, &oldstate);

6.取消点
(1)取消点有很多
①pthread_testcancel():显式地设置取消点,检查是否有响应请求,如果有就立刻响应,非阻塞状态

#include <pthread.h>

void pthread_testcancel(void);
使用场景:长时间运行的循环:在循环中插入 pthread_testcancel,使线程在每次循环迭代时检查取消请求

②pthread_join()
③sleep()
④read()
⑤write()

(2)取消点可能会陷入长时间的阻塞



(4)线程等待:pthread_join():(无限期)等待子线程结束,并接收子线程的返回值

1.概念
①父进程通过wait/waitpid获取子进程的终止信息
②主线程用pthread_join()接收子线程的返回值


2.pthread_join:
①接收子线程pthread_exit()的返回值
②接收子线程return的返回值

3.函数原型

#include <pthread.h>

int pthread_join(pthread_t tid, void **retval);

参数:
①thread:等待哪个子线程结束
②void** retval:传出参数,接收void*类型的值,所以是二级指针

4.用法:

int result;	//子线程若返回一个int类型
err = pthread_join(tid, &result);
//err = pthread_join(tid, (void**)&result);
if(err){
	error(1, err, "pthread_join %lu\n",tid);
}

5.代码
//①主线程用pthread_join()接收子线程的返回值
//②子线程不能返回自己栈上数据的指针,只能返回子线程堆上的数据。因为当子线程退出的时候,子线程的栈会销毁!

github网址:https://github.com/WangEdward1027/pthread/tree/main/pthread_join


(5)线程游离:pthread_detach():主线程将子线程设置为分离状态

1.概念
主线程不需要接收该子线程的返回值时,主线程可使用pthread_detach,将主线程和子线程分离,将子线程变为游离线程 (detached thread)。

游离线程终止后,操作系统会自动处理并释放与该线程相关的所有资源。主线程不需要也无法使用 pthread_join 来等待它结束和释放资源。

分离线程的设计就是为了避免显式的资源管理,适合于那些不需要等待其结束的后台任务。例如,后台日志写入线程、定时器线程等

在这里插入图片描述


2.函数原型

#include <pthread.h>

int pthread_detach(pthread_t thread);
pthread_detach(tid);

3.代码
//主线程主动使用pthred_detach,则子线程退出后系统会自动回收其资源,主线程不需要也无法显式地调用pthread_join来回收子进程的返回值

github网址:https://github.com/WangEdward1027/pthread/tree/main/pthread_detach

(6)线程资源的清理:线程清理函数

注册线程清理函数
用栈保存,执行顺序与注册顺序相反。

在这里插入图片描述

①pthread_cleanup_push()

1.函数原型

#include <pthread.h>

void pthread_cleanup_push(void (*routine)(void *), void *arg);

②pthread_cleanup_pop()

1.函数原型

#include <pthread.h>

void pthread_cleanup_pop(int execute);

execute == 0:出栈,不执行
execute != 0:出栈,并执行


③线程清理函数

1.代码
github网址:https://github.com/WangEdward1027/pthread/blob/main/pthread_cleanup/pthread_cleanup.c

2.执行线程清理函数的时机
①pthread_exit():✔ 会执行线程清理函数,将cleanup栈中还未pop的栈帧全部弹出。
②pthread_cancel():✔ 响应取消请求
③调用pthread_cleanup_pop(非0值):✔ 从栈上出栈一个函数,并执行,并但不会导致线程的终止
④从start_routine返回:❌ return 不会执行线程清理函数

注意:pthread_cleanup_push() 和 pthread_cleanup_pop() 必须成对出现
有多少个pthread_cleanup_push,就要对应写多少个pthread_cleanup_pop。否则编译不通过。
原因:它们是改进版宏函数,do{ 宏函数体} while(0)。所以必须成对出现,否则语法不通过。



3.线程的同步 (sync)

(1)术语

1.原子性:CPU指令是原子的
举例:i++的汇编代码:

LOAD  R1, i    ; 将变量 i 的值加载到寄存器 R1
ADD   R1, 1    ; 将寄存器 R1 的值加 1
STORE i,  R1   ; 将增加后的值存回变量 i

在STORE之前切换线程,导致其他线程读脏数据。


2.竞态条件 (race condition)
①多个执行流程 (并发执行)
共享资源
程序的结果(状态)取决于执行流程调度的情况   (调度是随机的,所以程序的结果看起来每次都不同)

若多个执行流程存在共享资源,且程序的结果取决于实际调度,则称为竟态条件。需要加上同步手段,比如互斥锁。


3.同步和异步
(1)异步
任何调度情况都可能出现。两个执行流程不做任何交流。会得到随机的结果。
②速度快

(2)同步 sync
①概念
1)两个执行流程发生了交流,让一些坏的调度情况不出现,只出现好的调度。会得到预期的结果。(让某些调度不可能出现)

同步用于协调线程之间的操作,以确保共享资源的正确访问。同步的主要机制包括互斥锁、读写锁、信号量和屏障等。

2)同步会有一些开销。
3)发生了竟态条件,一定要配合同步

②要求:
1)互斥地访问资源:互斥锁
2)等待某个条件成立

同步类似现实生活中的规则
多个执行流程,默认是并发、异步执行


4.并发和并行
(1)并发
并发是一种现象,在一个时间段中,执行流程可以交替运行。使得多个任务在宏观上看起来是同时执行的。只需要一个CPU核。

(2)并行
并行是一种技术,使得可以同时执行多个执行流程。需要多个CPU核才能实现。(并行是并发的一种)

在这里插入图片描述


(2)互斥

互斥锁、读写锁、CAS


互斥锁 (Mutex)

0.互斥锁 mutex (mutual exclusive,相互排斥的)
原子性:CPU指令

1.作用
加入了互斥锁,将原本不是原子操作的命令,变成了逻辑上的原子操作。(进入临界区之前,获取锁;执行临界区代码;退出临界区,释放锁)

互斥锁是一种用于确保在任何时刻只有一个线程可以访问共享资源的机制。它通常用于保护临界区,防止多个线程同时访问共享资源导致数据不一致

在这里插入图片描述


2.什么时候上锁?
在临界区要上锁
临界区 (critical area):对共享资源的操作的代码(指令)

注意事项:①锁的粒度 ②避免死锁


3.函数原型
(1)初始化锁:
①静态初始化

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

②动态初始化

int pthread_mutex_init(pthread_mutex_t* restrict mutex, 
			 const pthread_mutexattr_t* restrict attr);
pthread_mutex_init(&mutex, NULL);  //attr一般填NULL,表示默认属性

传出参数,要修改,所以要加取地址,传的是指针


(2)尝试获取锁

int pthread_mutex_trylock(pthread_mutex_t* mutex); //不会阻塞,若锁不可用则立刻返回

(3)上锁

int pthread_mutex_lock(pthread_mutex_t* mutex);  //无限期阻塞
pthread_mutex_lock(&mutex);

(4)释放锁

int pthread_mutex_unlock(pthread_mutex_t* mutex);  //释放锁

(5)销毁锁

int pthread_mutex_destroy(pthread_mutex_t* mutex); 

4.代码
//sync.c
//bank.c

github网址:https://github.com/WangEdward1027/pthread/tree/main/pthread_sync

注意事项:
①选择合适粒度的锁
②避免死锁的发生


②死锁 (Deadlock)

1.死锁出现的原因 (四个必要条件,同时成立,才可能出现死锁)
①互斥
②不能抢占 (不剥夺)
③持有并等待 (保持并请求)
④循环等待

在这里插入图片描述

int transfer(Account* acctA, Account* acctB, int money) {
    pthread_mutex_lock(&acctA->mutex);
    sleep(1);  //增加坏的调度的概率
    pthread_mutex_lock(&acctB->mutex);

    if (acctA->balance < money) {
        pthread_mutex_unlock(&acctA->mutex);	//各自先申请自己的锁
        pthread_mutex_unlock(&acctB->mutex);	//导致死锁

        return 0;
    }

    acctA->balance -= money;
    acctB->balance += money;

    pthread_mutex_unlock(&acctA->mutex);
    pthread_mutex_unlock(&acctB->mutex);

    return money;
}

2.避免死锁:破坏死锁四个必要条件中的一个或多个
(1)破坏循环等待条件:
按固定的顺序,依次获取锁 (如按id的顺序依次获取锁)

在这里插入图片描述


(2)破坏持有并等待条件:
两把锁,要么一口气都获取,要么都不能获取。则可以给获取两把锁的操作再加一个锁。

在这里插入图片描述


(3)破坏不能抢占条件:
拿到锁1后,尝试拿锁2,若拿不到则主动放弃锁1.
注意要随机睡眠来模拟随机调度,不然同频睡眠可能导致同时拿起各自的锁,询问对方,同时释放各自的锁

(经测试,该方法效率最差)
在这里插入图片描述
在这里插入图片描述


(4)互斥
很多情况下无法破坏互斥条件
CAS操作: (compare and swap)
CAS是复杂的CPU指令,导致软件层面可以实现无锁编程
Lock_free 算法、wait_free 算法


3.//deadlock.c 转账的例子

#include <func.h>

typedef struct {
    int id;
    char name[25];
    int balance;
    // 细粒度锁
    pthread_mutex_t mutex;
} Account;

Account acct1 = {1, "xixi", 1000, PTHREAD_MUTEX_INITIALIZER};
Account acct2 = {2, "peanut", 100, PTHREAD_MUTEX_INITIALIZER};

pthread_mutex_t protection = PTHREAD_MUTEX_INITIALIZER;

int transfer(Account* acctA, Account* acctB, int money) {
    // 4. 循环等待
    // 按id的顺序依次获取锁

    /* if (acctA->id < acctB->id) { */
    /*     pthread_mutex_lock(&acctA->mutex); */
    /*     sleep(1);   // 增加坏的调度的概率 */
    /*     pthread_mutex_lock(&acctB->mutex); */
    /* } else { */
    /*     pthread_mutex_lock(&acctB->mutex); */
    /*     sleep(1);   // 增加坏的调度的概率 */
    /*     pthread_mutex_lock(&acctA->mutex); */
    /* } */

    /* // 3. 不能抢占 */
/* start: */
    /* pthread_mutex_lock(&acctA->mutex); */
    /* sleep(1); */
    /* int err = pthread_mutex_trylock(&acctB->mutex); */
    /* if (err) { */
    /*     // 主动释放获取的锁 */
    /*     pthread_mutex_unlock(&acctA->mutex); */
    /*     int seconds = rand() % 10; */
    /*     sleep(seconds); */
    /*     goto start; */
    /* } */

    // 2. 持有并等待
    pthread_mutex_lock(&protection);
    pthread_mutex_lock(&acctA->mutex);
    sleep(1);
    pthread_mutex_lock(&acctB->mutex);
    pthread_mutex_unlock(&protection);

    if (acctA->balance < money) {
        pthread_mutex_unlock(&acctA->mutex);
        pthread_mutex_unlock(&acctB->mutex);

        return 0;
    }

    acctA->balance -= money;
    acctB->balance += money;

    pthread_mutex_unlock(&acctA->mutex);
    pthread_mutex_unlock(&acctB->mutex);

    return money;
}

void* start_routine1(void* args) {
    int money = (int) args;
    int ret = transfer(&acct1, &acct2, money);
    printf("%s -> %s: %d\n", acct1.name, acct2.name, ret);
    return NULL;
}

void* start_routine2(void* args) {
    int money = (int) args;
    int ret = transfer(&acct2, &acct1, money);
    printf("%s -> %s: %d\n", acct2.name, acct1.name, ret);
    return NULL;
}

int main(int argc, char* argv[])
{
    srand(time(NULL));

    pthread_t tid1, tid2;

    pthread_create(&tid1, NULL, start_routine1, (void*)900);    
    pthread_create(&tid2, NULL, start_routine2, (void*)100);    

    // 主线程等待子线程结束
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    printf("%s: balance = %d\n", acct1.name, acct1.balance);
    printf("%s: balance = %d\n", acct2.name, acct2.balance);
    return 0;
}



(3)条件变量 (Condition Variable):pthread_cond_t

等待某个条件成立:
条件变量只是提供了一个等待、唤醒机制,至于条件何时成立、何时不成立,取决于业务


①初始化条件变量:pthread_cond_init()

1.动态初始化

int pthread_cond_init(pthread_cond_t* restrict cond, 
			 const pthread_condattr_t* restrict attr);
pthread_cond_init(&cond, NULL);  //attr一般填NULL,表示默认属性

2.静态初始化

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

等待条件变量:pthread_cond_wait()

当条件不成立,等待 (在线程中等待某个条件成立):
在这里插入图片描述

问题:pthread_cond_wait()为什么需要传递互斥锁?
互斥锁:保护cond变量,是多线程共享的资源


pthread_cond_wait()的内部实现的三个步骤:
释放互斥锁 (①②是原子操作)
阻塞等待
重新获取互斥锁
当返回时,该线程一定再一次获取了mutex
返回时,cond条件曾经成立过,现在是否成立,不确定。存在虚假唤醒现象。


当条件变量被唤醒时,线程会从等待队列中移出,并重新尝试获取传入的互斥锁。只有在成功获取到互斥锁后,pthread_cond_wait才会返回,线程继续执行后续代码。
在这里插入图片描述

//惯用法
pthread_mutex_lock(&mutex);
...
while (!condition) {
    pthread_cond_wait(&cond, &mutex);
}
...  // 条件满足后执行的代码
pthread_mutex_unlock(&mutex);

pthread_cond_wait的伪代码实现:

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) {
    // 原子地释放互斥锁并进入条件变量的等待队列
    enter_atomic_section();
    
    // 释放互斥锁
    pthread_mutex_unlock(mutex);
    
    // 将当前线程放入条件变量的等待队列,并进入等待状态
    add_thread_to_cond_wait_queue(cond, current_thread);
    
    // 离开原子区域
    leave_atomic_section();
    
    // 线程进入等待状态,直到条件成立, 被唤醒
    thread_wait(current_thread);
    
    // 重新获取互斥锁
    pthread_mutex_lock(mutex);
    
    return 0;
}

③通知条件变量

当条件成立时,唤醒等待该条件的线程

在这里插入图片描述

i.pthread_cond_signal()

唤醒至少一个等待该条件变量的线程

注意:在实际实现时,为了性能考虑,可能会一次性唤醒多个线程。

cond维护一个队列
在这里插入图片描述


当某个线程改变条件并使之满足时,通知等待的线程:

pthread_mutex_lock(&mutex);
// 改变条件
condition = 1;
pthread_cond_signal(&cond);  // 或者使用pthread_cond_broadcast
pthread_mutex_unlock(&mutex);

ii.pthread_cond_broadcast()

唤醒所有等待该条件变量的线程

int pthread_cond_broadcast(pthread_cond_t *cond);

在这里插入图片描述


④销毁条件变量:pthread_cond_destroy()

销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

⑤生产者-消费者模型

使用条件变量、阻塞队列来实现生产者-消费者模型:

①阻塞队列:
当队列满时,如果线程往阻塞队列中添加东西,线程会陷入阻塞
当队列空时,如果线程往阻塞队列中取东西,线程会陷入阻塞

②生产者:生产商品
如果队列满了,生产者陷入阻塞,等待队列不满(Not_full)
如果队列不满,生产商品,将商品添加到阻塞队列,队列处于非空状态(Not_empty),唤醒消费者

③消费者:消费商品
如果队列空了,消费者陷入阻塞,等待队列非空(Not_empty)
如果队列非空,从阻塞队列中获取商品,消费商品,队列处于非满状态(Not_full),唤醒生产者

在这里插入图片描述


i.阻塞队列

有界队列:有增长的上限,否则服务器会Out of Memory(OOM现象)

在这里插入图片描述

代码见github网址:https://github.com/WangEdward1027/pthread/tree/main/BlockQueue


ii.线程池:生产者消费者模型

线程池避免了频繁的创建和销毁线程,避免了冷启动

在这里插入图片描述

问题:应用程序,应该包含多少个线程(包括main线程)?
①取决于CPU的核数
②取决于任务的负载类型:
计算密集型:一比一
I/O密集型:一比N (N≥2)

在这里插入图片描述

github代码:[https://github.com/WangEdward1027/pthread/tree/main/threadpool]
(https://github.com/WangEdward1027/pthread/tree/main/threadpool)

这段代码实际上实现了一个生产者-消费者模型。线程池中的线程充当消费者,而主线程充当生产者
①生产者:主线程负责创建和添加任务到阻塞队列中。
②消费者:线程池中的线程负责从阻塞队列中取出并执行任务。
这种设计使得任务可以并发处理,从而提高程序的执行效率。在实际应用中,生产者-消费者模型是多线程编程中的一个常见模式,用于解决任务调度和负载均衡问题。



4.线程安全

1.定义:
线程安全是指在多线程环境下访问共享资源时,程序能够正确地运行,不会出现数据竞争和其他同步

2.实现
①互斥锁 (Mutex)
②读写锁(Read-Write Lock):
③条件变量
信号量(Semaphore):
④自旋锁
原子操作(Atomic Operation):



5.可重入性

1.可重入函数 (Reentrant Function)
(1)定义
可重入函数是指可以被多个线程同时调用而不引起任何问题的函数。这类函数不会依赖或修改共享数据,或在访问共享数据时使用适当的同步机制。

在这里插入图片描述



6.线程池 (Thread Pool)

线程池是一种线程管理技术,允许程序在运行时创建和管理多个线程,以便于处理大量并发任务。线程池通过复用已创建的线程来减少线程创建和销毁的开销,提高系统性能和资源利用率。


GPT实现的线程池:

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

#define THREAD_POOL_SIZE 5

// 任务结构体,用于存储任务信息
typedef struct {
    void (*function)(void *); // 函数指针,指向任务函数
    void *arg;                // 函数参数
} Task;

// 线程池结构体
typedef struct {
    pthread_mutex_t lock;     // 互斥锁,保护任务队列
    pthread_cond_t  notify;   // 条件变量,用于通知空闲线程
    pthread_t       threads[THREAD_POOL_SIZE]; // 线程数组
    Task            queue[THREAD_POOL_SIZE * 2]; // 任务队列
    int             queue_size; // 任务队列大小
    int             head;       // 队列头部指针
    int             tail;       // 队列尾部指针
    int             shutdown;   // 线程池关闭标志
} ThreadPool;

// 初始化线程池
ThreadPool* create_threadpool() {
    ThreadPool *pool = (ThreadPool*)malloc(sizeof(ThreadPool));
    if (pool == NULL) {
        fprintf(stderr, "Failed to allocate memory for thread pool\n");
        return NULL;
    }

    // 初始化互斥锁和条件变量
    pthread_mutex_init(&(pool->lock), NULL);
    pthread_cond_init(&(pool->notify), NULL);

    // 初始化任务队列
    pool->queue_size = 0;
    pool->head = pool->tail = 0;
    pool->shutdown = 0;

    // 创建工作线程
    for (int i = 0; i < THREAD_POOL_SIZE; ++i) {
        pthread_create(&(pool->threads[i]), NULL, thread_function, (void*)pool);
    }

    return pool;
}

// 销毁线程池
void destroy_threadpool(ThreadPool *pool) {
    if (pool == NULL) return;

    // 关闭线程池
    pool->shutdown = 1;

    // 唤醒所有等待的线程
    pthread_cond_broadcast(&(pool->notify));

    // 等待所有线程结束
    for (int i = 0; i < THREAD_POOL_SIZE; ++i) {
        pthread_join(pool->threads[i], NULL);
    }

    // 销毁互斥锁和条件变量
    pthread_mutex_destroy(&(pool->lock));
    pthread_cond_destroy(&(pool->notify));

    // 释放线程池内存
    free(pool);
}

// 线程函数,执行任务队列中的任务
void* thread_function(void *args) {
    ThreadPool* pool = (ThreadPool*)args;
    while (1) {
        pthread_mutex_lock(&(pool->lock));
        while (pool->queue_size == 0 && !pool->shutdown) {
            // 等待任务
            pthread_cond_wait(&(pool->notify), &(pool->lock));
        }
        if (pool->shutdown) {
            // 线程池关闭,退出线程
            pthread_mutex_unlock(&(pool->lock));
            pthread_exit(NULL);
        }
        // 取出任务
        Task task = pool->queue[pool->head];
        pool->head = (pool->head + 1) % (THREAD_POOL_SIZE * 2);
        pool->queue_size--;

        pthread_mutex_unlock(&(pool->lock));

        // 执行任务函数
        (*(task.function))(task.arg);
    }
    return NULL;
}

// 向线程池中添加任务
void add_task(ThreadPool *pool, void (*function)(void *), void *arg) {
    pthread_mutex_lock(&(pool->lock));

    // 队列已满,等待空闲线程
    while (pool->queue_size == THREAD_POOL_SIZE * 2) {
        pthread_cond_wait(&(pool->notify), &(pool->lock));
    }

    // 添加任务到队列尾部
    pool->queue[pool->tail].function = function;
    pool->queue[pool->tail].arg = arg;
    pool->tail = (pool->tail + 1) % (THREAD_POOL_SIZE * 2);
    pool->queue_size++;

    // 唤醒等待的线程
    pthread_cond_signal(&(pool->notify));
    pthread_mutex_unlock(&(pool->lock));
}

// 任务函数示例
void example_task(void *arg) {
    int num = *((int*)arg);
    printf("Task executed with argument: %d\n", num);
    usleep(1000000); // 模拟任务执行
}

int main() {
    ThreadPool *pool = create_threadpool();

    // 向线程池中添加任务
    for (int i = 0; i < 10; ++i) {
        int *arg = (int*)malloc(sizeof(int));
        *arg = i;
        add_task(pool, example_task, (void*)arg);
    }

    // 等待所有任务完成
    sleep(5);

    // 销毁线程池
    destroy_threadpool(pool);

    return 0;
}

在这个示例中,线程池被初始化为包含3个线程和10个任务的队列。任务被添加到队列中,当有空闲线程时,它们会从队列中取出任务并执行。



7.其他

(1)第一性原理

1.程序 = 数据 + 指令 (数据结构 + 算法)
①数据:类型、值、类、对象
②指令:运算符、语句、函数、方法、闭包 …

2.程序的运行方式:
①同步/异步
②并发/并行


(2)知识图谱

1.语言 (多门编程语言):
①数据:类型、值、指针、引用、对象
②指令:运算符、语句、函数、闭包、方法、lamda表达式
③程序执行方式:并发(多条执行流程)、并行、异步、同步

2.工程:
惯用法(idioms)
②设计模式
③架构

3.理论:
①数据结构与算法
②操作系统
③组成原理
④计算机网络
⑤数据库
⑥分布式
⑦编译原理

4.工具:
MySQL、Redis、shell命令、GDB等

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/658976.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

安卓ADB通过WIFI无线连接手机[通过无线安装APK]

安卓ADB通过无线连接手机 本文摘录于&#xff1a;https://www.cnblogs.com/zhuxibo/p/14261117.html只是做学习备份之用&#xff0c;绝无抄袭之意&#xff0c;有疑惑请联系本人&#xff01; 别人给的操作确实可行,我这里实操记录如下: AdministratorpiaoranPC MINGW64 /e/Wor…

大模型部署框架 FastLLM 简要解析

0x0. 前言 本文主要是对FastLLM做了一个简要介绍&#xff0c;展示了一下FastLLM的部署效果。然后以chatglm-6b为例&#xff0c;对FastLLM模型导出的流程进行了解析&#xff0c;接着解析了chatglm-6b模型部分的核心实现。最后还对FastLLM涉及到的优化技巧进行了简单的介绍。 0…

Java 阻塞队列与生产者消费者模型

一、阻塞队列 阻塞队列是⼀种特殊的队列&#xff0c;其也遵守队列 "先进先出" 的原则&#xff1b; 阻塞队列是⼀种线程安全的数据结构&#xff0c;并且具有以下特性&#xff1a; 当队列满的时候&#xff0c;继续入队列就会阻塞&#xff0c;直到有其他线程从队列中…

成都市酷客焕学新媒体科技有限公司:助力品牌打破困境!

在数字化浪潮的推动下&#xff0c;营销策略对品牌的发展愈发关键。成都市酷客焕学新媒体科技有限公司&#xff0c;作为短视频营销领域的佼佼者&#xff0c;凭借其卓越的策略和实力&#xff0c;助力众多品牌在信息海洋中脱颖而出&#xff0c;实现品牌的显著增长。 酷客焕学专注于…

抖音和快手哪个好?来全面了解一下他们的区别!

快手和抖音虽然是短视频领域的两大主流平台&#xff0c;但是两者也存在本质的区别&#xff0c;从产品定位、用户群体到视频风格、变现模式&#xff0c;它们的特征都不一样。 &#xff08;一&#xff09;两个平台核心区别&#xff1a; 1. 核心用户不一样&#xff1a;抖音以1、…

【最优化方法】实验四 约束最优化方法的MATLAB实现

实验的目的和要求&#xff1a;通过本次实验使学生较为熟练使用MATLAB软件&#xff0c;并能利用该软件进行约束最优化方法的计算。 实验内容&#xff1a; &#xff11;、罚函数法的MATLAB实现 &#xff12;、可行方向法的MATLAB实现 学习建议&#xff1a; 本次实验就是要通…

942. 增减字符串匹配 - 力扣

1. 题目 由范围 [0,n] 内所有整数组成的 n 1 个整数的排列序列可以表示为长度为 n 的字符串 s &#xff0c;其中: 如果 perm[i] < perm[i 1] &#xff0c;那么 s[i] I 如果 perm[i] > perm[i 1] &#xff0c;那么 s[i] D 给定一个字符串 s &#xff0c;重构排列 pe…

新能源汽车推行精益生产:绿色动力下的效率革命

在新能源汽车行业迅猛发展的当下&#xff0c;推行精益生产已成为提升竞争力的关键所在。精益生产&#xff0c;作为一种以客户需求为导向、追求流程最优化和浪费最小化的管理理念&#xff0c;正逐步在新能源汽车领域展现出其独特的魅力。 新能源汽车的兴起&#xff0c;不仅代表了…

【云原生】kubernetes中Configmap原理解析与应用实战

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

Python中tkinter入门编程9

在《Python中tkinter编程入门8-CSDN博客》中提到&#xff0c;tkinter中的Canvas表示画布&#xff0c;可以在画布中显示文字和图片。除了以上功能外&#xff0c;还可以在Canvas中添加对鼠标或键盘的响应。 1 为Canvas添加事件响应 可以通过Canvas的bind()方法添加对鼠标或键盘…

深圳比创达电子|EMC与EMI滤波器:电子设备的“电磁防护罩”

在电子科技日新月异的今天&#xff0c;电磁兼容性&#xff08;EMC&#xff09;问题越来越受到工程师和技术人员的关注。其中&#xff0c;电磁干扰&#xff08;EMI&#xff09;和电磁干扰抑制&#xff08;即EMI滤波器&#xff09;是实现良好EMC性能的关键技术之一。 一、EMC与E…

1218. 最长定差子序列

1218. 最长定差子序列 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;参考代码&#xff1a;_1218最长定差子序列 错误经验吸取 原题链接&#xff1a; 1218. 最长定差子序列 https://leetcode.cn/problems/longest-arithmetic-subsequence-of-given-differen…

FlashRAG

文章目录 一、关于 FlashRAG特点 ✨&#x1f527; 安装 二、快速入门&#x1f3c3;1、Toy Example2、使用现成的管道3、建立自己的管道4、只需使用组件 三、组件⚙️1、RAG 组件2、管道 四、支持方法&#x1f916;五、支持数据集&#x1f4d3;六、其他常见问题解答 &#x1f64…

MT3049 区间按位与

思路&#xff1a; 使用ST表。ST表模板可参考MT3024 maxmin 注意点&#xff1a;此题范围较大&#xff0c;所以要避免超时。 ①使用 ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); 加快输入输出速度。 ②换行使用\n而不是endl 代码&#xff1a; 1.暴力6/8 #…

图片怎么批量重命名从1到50?这3个方法一键改名

图片怎么批量重命名从1到50&#xff1f;图片批量重命名从1到50的过程不仅提高了我们处理大量图片文件的效率&#xff0c;还大大简化了命名过程&#xff0c;让我们能更加有条理地管理和存储图片。通过使用各种专业的工具和方法&#xff0c;我们可以轻松实现图片文件的自动化命名…

微信小程序代码加固教程后台接口防止别人乱调用

最近开发了一个小程序前端开发前端和后台花了1个多月时间开发&#xff0c;结果被人轻松的把微信小程序前端代码破解出来。而且完整一个字不差截图给我看了。 小程序是前后端都分离的&#xff0c;如果后端不作验证&#xff0c;别人把你的小程序前端扒了接口也暴露了&#xff0c;…

玩转STM32-通用同步/异步收发器USART(详细-慢工出细活)

CPU与外围设备之间的信息交换或计算机与计算机之间的信息交换称为通信。基 本的通信方式有两种&#xff0c;即并行通信和串行通信。文章目录 一、串行通信基础1.1 串行通信的方式1.2 串行通信的数据传输形式1.3 波特率 二、STM32的USART的结构特征&#xff08;了解&#xff09;…

现在怎么做抖店才能赚钱?这四个重要建议,你千万不能忽略!

大家好&#xff0c;我是电商花花。 现在目前看抖音小店前景和红利依然有很大的市场空间&#xff0c;抖音小店平台流量大&#xff0c;商家入驻门槛低&#xff0c;抖店的运营技术也不像其它传统电商平台那么高。 所以&#xff0c;当下抖音小店仍然是流量大&#xff0c;机遇多。…

使用手机短信恢复软件,完成从新手到专家的进阶之路

由于各种原因&#xff0c;如误删、手机设备损坏等&#xff0c;我们可能会面临重要短信丢失的风险。现在市面上有许多手机短信恢复软件可以帮助我们解决这个问题&#xff0c;但从新手到专家的进阶之路并非一蹴而就的过程&#xff0c;它需要耐心、实践和不断地学习。以下是一篇关…

开源集运wms系统

集运WMS系统是一种专为集运业务设计的仓库管理系统&#xff0c;它能够高效地处理来自多个来源的货物&#xff0c;优化存储和发货流程。 经过长时间的开发和测试&#xff0c;推出了我的集运WMS系统。它不仅具备传统WMS系统的所有功能&#xff0c;还针对集运业务的特点进行了特别…