文章目录
- 三、线程
- 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等