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.mzph.cn/web/17211.shtml

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

相关文章

OrangePi AIpro(8T) 基本介绍和资料汇总

OrangePi AIpro 基本介绍和资料汇总 1 介绍1.1 香橙派(Orange Pi) 公司概述1.2 OrangePi AIpro(8T) 介绍概述硬件规格参数引脚图产品详细图芯片优缺点优点 2 资料汇总2.1 官方资料概述资料下载 参考 1 介绍 1.1 香橙派(Orange Pi) 公司概述 香橙派&#xff08;Orange Pi&…

惊喜:我一晚上赚了10万!深扒挖瑞幸1.8亿起死回生的商业真相

■ ■ ■ ■ 瑞幸咖啡 2020年1月&#xff0c;浑水报告发出后&#xff0c;瑞幸股价一路颠簸&#xff0c;最终退市停牌。 然而&#xff0c;19个月后&#xff0c;在粉单市场股价再次增长781.16% 暴雷后的19个月&#xff0c;瑞幸经历了怎样的起死回生&#xff1f; 2020年4月2日…

【RuoYi】如何启动RuoYi项目

一、前言 最近&#xff0c;在做一个管理系统的项目&#xff0c;接触到了RuoYi这个前后端分离的框架&#xff0c;自己是第一次接触这个框架&#xff0c;所以刚开始有点好奇&#xff0c;在用该框架写了一些代码后。发现RuoYi这个框架做的真的好&#xff0c;它包含了权限管理和一些…

【稳定检索】2024年电子技术、传感器与信号处理国际会议(ETSS 2024)

2024年电子技术、传感器与信号处理国际会议 2024 International Conference on Electronic Technology, Sensors, and Signal Processing 【1】会议简介 2024年电子技术、传感器与信号处理国际会议&#xff0c;作为业内领先的学术交流平台&#xff0c;将汇聚全球顶尖的电子技术…

99%的人都不知道,微信才是真正的学习神器

微信&#xff0c;作为一款全球最受欢迎的社交应用之一&#xff0c;除了聊天、朋友圈、小程序等功能外&#xff0c;还有许多隐藏的学习功能&#xff0c;今天小编就给大家分享10个微信隐藏的学习功能&#xff0c;助您轻松成为学霸。 1、微信笔记 用过代办清单软件的朋友都知道&…

如何调用通义千问大模型API

目录 登录阿里云 大模型服务平台百炼 登录控制台 QWen Long QWen 通义千问开源系列 大语言模型 OpenAI接口兼容 登录阿里云 阿里云-计算&#xff0c;为了无法计算的价值 大模型服务平台百炼 降价信息&#xff1a; 登录控制台 右上角取得API key 创建Key QWen Long qw…

Linux网络编程:传输层协议|UDP

知识引入&#xff1a; 端口号&#xff1a; 当应用层获得一个传输过来的报文时&#xff0c;这时数据包需要知道&#xff0c;自己应该送往哪一个应用层的服务&#xff0c;这时就引入了“端口号”&#xff0c;通过区分同一台主机不同应用程序的端口号&#xff0c;来保证数据传输…

【VTKExamples::Utilities】第九期 FrameRate

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享VTK样例FrameRate,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 1. FrameRate 该样例介绍 如…

【Mongo】索引结构

结论 Mongo3.2版本开始&#xff0c;索引的结构默认是B树。 起因 面试的时候&#xff0c;面试官问为什么Mongo DB底层使用B树而不是B树&#xff1f; 面试完赶紧恶补&#xff0c;结果发现面试官好像给我埋了个坑。。。 MongoDB官方描述&#xff1a; 翻译一下就是&#xff1…

JVM 内存布局深度解析,你所不知道的一面

作为Java开发者&#xff0c;想要写出高质量的代码&#xff0c;理解JVM的内存结构是必修课。本文将为您深度解析 Java 虚拟机(JVM)中的内存布局及其细节分析&#xff0c;让你在内存管理的道路上行稳致远。希望通过本文能让你彻底理解其中的奥秘。 一、内存布局概览 在我们深入具…

Python爬虫实战(实战篇)—17获取【CSDN某一专栏】数据转为Markdown列表放入文章中

文章目录 专栏导读背景结果预览1、页面分析2、通过返回数据发现适合利用lxmlxpath3、进行Markdown语言拼接总结 专栏导读 在这里插入图片描述 &#x1f525;&#x1f525;本文已收录于《Python基础篇爬虫》 &#x1f251;&#x1f251;本专栏专门针对于有爬虫基础准备的一套基…

电脑可以录音吗?这里有你想要的答案!

在数字化时代&#xff0c;电脑已经成为我们日常生活中不可或缺的工具。除了办公、娱乐等基本功能外&#xff0c;电脑还具备许多实用的辅助功能&#xff0c;其中之一就是录音功能。可是电脑可以录音吗&#xff1f;本文将介绍两种在电脑上录音的方法&#xff0c;希望通过本文的介…

客服快捷回复话术分享:618议价话术和催发货话术

随着618活动大促的临近&#xff0c;客服小伙伴们将迎来一年中最繁忙的时刻。面对顾客的议价、催发货等需求&#xff0c;我们应该如何回复才能既满足顾客的需求&#xff0c;又能保持良好的服务形象呢&#xff1f;下面就为大家分享一些议价和催发货的快捷回复话术&#xff0c;希望…

申请免费通配符SSL证书教程

申请免费通配符SSL证书的步骤相对直接&#xff0c;但需要注意的是免费且支持通配符的证书提供商较为有限&#xff0c;JoySSL是一个被多次提及提供此类服务的机构。以下是一个基于汇总信息的简明教程&#xff0c;帮助你申请免费的通配符SSL证书&#xff1a; 1. 准备工作 确认兼…

Android studio 连接 adb传输文件到电脑

前提是已经连接到adb window R&#xff1a; 打开控制台adb devices&#xff1a;可以查看已经连接的设备adb pull /storage/emulated/0/Download/aa.png C:\Users\Administrator\Desktop&#xff1a;拉取连接设备的文件 aa.png 到电脑桌面上 (在电脑控制台进行拉取操作) 如果…

C字符串和内存函数介绍(二)——长度不固定的字符串函数

前面我们一起学习了strlen&#xff0c;strcpy&#xff0c;strcmp&#xff0c;strcat的使用以及它们的模拟实现&#xff0c;它们的特点是你传参的时候&#xff0c;传过去的是数组首元素的地址&#xff0c;然后无论是计算长度&#xff0c;实现拷贝&#xff0c;相互比较还是进行追…

拓展虚拟世界边界,云手机可以做到吗

虚拟世界&#xff0c;AI&#xff0c;VR等词汇是21世纪最为流行的词汇&#xff0c;在科技背后&#xff0c;这些词汇的影响变得越来越大&#xff0c;已经走进了人们的世界&#xff0c;比如之前APPLE发布的vision pro&#xff0c;使人们能够更加身临其境的体验到原生os系统&#x…

如何理解Spring Boot自动配置原理和应用?

我们知道&#xff0c;基于Spring Boot&#xff0c;我们只需要在类路径中引入一组第三方框架的starter组件&#xff0c;就能在Spring容器中使用这些框架所提供的各项功能。这在当下的开发过程中已经习以为常&#xff0c;但在Spring Boot还没有诞生之前却是不可想象的。如果我们使…

这款信创FTP软件,可实现安全稳定的文件传输

信创&#xff0c;即信息技术应用创新&#xff0c;2018年以来&#xff0c;受“华为、中兴事件”影响&#xff0c;国家将信创产业纳入国家战略&#xff0c;并提出了“28n”发展体系。“8”具体指金融、石油、电力、电信、交通、航空航天、医院、教育等主要行业。目前企业使用比较…

0527_C++1

练习1&#xff1a; 定义自己的命名空间my_sapce&#xff0c;在my_sapce中定义string类型的变量s1&#xff0c;再定义一个函数完成对字符串的逆置。 #include <iostream>using namespace std; namespace my_space {string s1"hello world";void my_strreverse…