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等

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入解析BGP:互联网路由协议的全貌与应用

BGP&#xff08;Border Gateway Protocol&#xff09;是互联网上用于在自治系统&#xff08;AS&#xff09;之间交换路由信息的协议。它负责决定数据包的最佳路径以及路由的选择。以下是BGP的一些关键特点和工作原理的详细内容&#xff1a; BGP的特点&#xff1a; 1.路径矢量型…

Android开发 -- JNI开发

1.配置JNI环境 创建JNI文件夹 在项目的主目录中创建一个名为 JNI 的文件夹。这个文件夹将包含所有的本地源代码和配置文件。 编写Android.mk文件 这个文件是一个 Makefile&#xff0c;用来指导 NDK 如何编译和构建本地代码。 #清除之前定义的变量&#xff0c;确保每个模块的…

《python编程从入门到实践》day40

# 昨日知识点回顾 编辑条目及创建用户账户 暂没能解决bug&#xff1a; The view learning_logs.views.edit_entry didnt return an HttpResponse object. It returned None instead.# 今日知识点学习 19.2.5 注销 提供让用户注销的途径 1.在base.html中添加注销链接 …

运维笔记.Docker镜像分层原理

运维专题 Docker镜像原理 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.csdn.net/qq_28550263/artic…

探讨大米自动化生产线包装设备的智能化发展趋势

随着科技的飞速发展&#xff0c;智能化已经成为各行各业转型升级的重要方向。在大米生产领域&#xff0c;自动化生产线包装设备的智能化发展更是引领着粮食产业的未来潮流。星派将从智能化技术、市场需求、发展趋势等方面&#xff0c;探讨大米自动化生产线包装设备的智能化发展…

java图书电子商务网站的设计与实现源码(springboot+vue+mysql)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的图书电子商务网站的设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 图书电子商…

鸿蒙ArkTS声明式开发:跨平台支持列表【按键事件】

按键事件 按键事件指组件与键盘、遥控器等按键设备交互时触发的事件&#xff0c;适用于所有可获焦组件&#xff0c;例如Button。对于Text&#xff0c;Image等默认不可获焦的组件&#xff0c;可以设置focusable属性为true后使用按键事件。 说明&#xff1a; 开发前请熟悉鸿蒙开…

嵌入式进阶——外部中断(EXTI)

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 STC8H中断外部中断外部中断编写配置外部中断调用中断触发函数 外部中断测试测试外部中断0测试外部中断2、3或者4 PCB中断设计 STC8…

echarts取消纵坐标,自定义提示内容,完整 echarts 布局代码

效果图 实现代码 开启点击柱子时的提示内容 //完整写法请看下面tooltip: {trigger: axis,axisPointer: {type: shadow}},自定义提示内容 //完整写法请看下面formatter: function (param) {// param是悬浮窗所在的数据&#xff08;x、y轴数据&#xff09;let relVal "&…

【华为】将eNSP导入CRT,并解决不能敲Tab问题

华为】将eNSP导入CRT&#xff0c;并解决不能敲Tab问题 eNSP导入CRT打开eNSP&#xff0c;新建一个拓扑右键启动查看串口号关联CRT成功界面 SecureCRT连接华为模拟器ensp,Tab键不能补全问题选择Options&#xff08;选项&#xff09;-- Global Options &#xff08;全局选项&#…

LangChain技术解密:构建大模型应用的全景指南

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【工具大全】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

vue3父组件改变 子组件不改变(uniapp)

项目中遇到了这么个问题 场景&#xff1a;封装select组件&#xff0c;通过子组件选中后传递值给父组件&#xff0c;父组件需要回显这个值&#xff08;这里使用 defineProps和defineEmits就可以实现&#xff0c;或者直接使用defineModel也可以实现&#xff0c;但是uniapp目前不…

学习编程对英语要求高吗?

学习编程并不一定需要高深的英语水平。我这里有一套编程入门教程&#xff0c;不仅包含了详细的视频讲解&#xff0c;项目实战。如果你渴望学习编程&#xff0c;不妨点个关注&#xff0c;给个评论222&#xff0c;私信22&#xff0c;我在后台发给你。 虽然一些编程资源和文档可能…

AI大模型在测试中的深度应用与实践案例

文章目录 1. 示例项目背景2. 环境准备3. 代码实现3.1. 自动生成测试用例3.2. 自动化测试脚本3.3. 性能测试3.4. 结果分析 4. 进一步深入4.1. 集成CI/CD管道4.1.1 Jenkins示例 4.2. 详细的负载测试和性能监控4.2.1 Locust示例 4.3. 测试结果分析与报告 5. 进一步集成和优化5.1. …

文件上传漏洞:pikachu靶场中的文件上传漏洞通关

目录 1、文件上传漏洞介绍 2、pikachu-client check 3、pikachu-MIME type 4、pikachu-getimagesize 最近在学习文件上传漏洞&#xff0c;这里使用pikachu靶场来对文件上传漏洞进行一个复习练习 废话不多说&#xff0c;开整 1、文件上传漏洞介绍 pikachu靶场是这样介绍文…

一键批量整理神器:轻松将相同名称文件归类至指定文件夹,告别繁琐文件管理!

信息爆炸的时代&#xff0c;电脑中的文件数量如潮水般涌现&#xff0c;管理起来令人头疼不已。您是否曾因为文件命名不规范而耗费大量时间寻找某个重要资料&#xff1f;是否曾因为文件散落各处而影响了工作效率&#xff1f;现在&#xff0c;我们为您隆重推荐一款文件管理神器—…

APM2.8如何供电

APM2.8飞控供电有两种&#xff0c; 1.电流计供电&#xff0c; 2.带BEC&#xff08;稳压功能&#xff09;的电调供电 飞控有一个JP1&#xff0c;它是一个供电选择接口&#xff0c;当插入跳线帽时&#xff0c;飞控用带BEC电调供电&#xff0c;当不插入时&#xff0c;用电流计供…

英语新概念2-回译法-lesson16

第一次回译 if you ___ your car on a wrong place, the traffic police man will find you quickly. If he do not give you the ticket,you are lucky.However,the ___ not all like this,The police man is __ sometimes.I had a holiday in Sweden, I found a ___ in my c…

《java数据结构》--顺序表详解

一.顺序表的概念&#x1f649; &#x1f431;顺序表是一段物理地址连续的储存单元&#xff0c;一次储存数据元素的线性结构。一般情况下采用数组储存&#xff0c;和数组的增删查改类似。 但是顺序表和数组还是有区别的比如&#xff0c;数组按照是否可以扩容可以分为&#xff…

深入分析 Android Activity (八)

文章目录 深入分析 Android Activity (八)1. Activity 的资源管理1.1 使用资源 ID1.2 动态加载资源1.3 资源的本地化1.4 使用 TypedArray 访问资源 2. Activity 的配置变更处理2.1 在 Manifest 文件中声明配置变更2.2 重写 onConfigurationChanged 方法2.3 保存和恢复实例状态 …