【Linux 物联网网关主控系统-Linux主控部分(四)】
Linux 物联网网关主控系统-Linux主控部分四一、引入问题二、线程基础1.线程的核心定义2.进程与线程的调度 / 资源分配重构3.主线程与多线程进程4.线程的基本特性5、多线程与多进程的形象类比6.线程的资源特性核心共享 私有7.多线程的核心优点三、多线程编程-线程创建1.核心函数2.示例代码3.线程查看四、多线程 —— 互斥锁1.为什么需要互斥锁2.基本概念3.核心函数4.示例代码五、多线程-条件变量1.为什么需要条件变量2.基本概念3.条件变量核心函数4.示例代码一、引入问题问题 1多个进程有相同全局数组要同步怎么高效问题 2进程有动态链表一变化就要同步给其他进程问题 3一个进程要同时处理多个阻塞任务串口、摄像头、socket、消息队列二、线程基础1.线程的核心定义1.线程又称轻量级进程Lightweight ProcessLWP是程序执行流的最小单元是进程中的一个实体被系统独立调度和分派。2.线程自身不拥有独立的系统资源仅持有运行必需的少量资源如寄存器、堆栈可与同属一个进程的其他线程共享进程的全部资源。3.线程的核心作用为程序提供并行执行的能力允许一个程序同时执行多个任务。2.进程与线程的调度 / 资源分配重构1.引入线程前进程既是资源分配的基本单位也是调度的基本单位各进程拥有独立的 PCB、内存空间和系统资源上下文切换开销大。2.引入线程后进程仍为资源分配的基本单位线程成为调度的基本单位一个进程可包含多个线程所有线程共享进程的地址空间和资源仅做独立调度。3.Linux 内核实现Linux 中同样用task_struct描述线程线程和进程参与统一的调度无单独的线程调度机制。3.主线程与多线程进程1.主线程操作系统创建进程时会自动创建一个主线程程序的main()函数是主线程的入口是进程的默认执行流。2.单线程进程程序仅有主线程进程本身就是单一线程的执行体包含进程的代码、数据、打开文件、单个寄存器和堆栈。3.多线程进程一个进程内创建多个线程所有线程共享进程的代码、数据、打开文件等资源每个线程拥有独立的寄存器、堆栈可独立执行进程的不同代码部分。4.线程的基本特性1.任何程序至少包含一个线程单线程程序中线程即为程序本身。2.线程拥有三种基本状态就绪态、阻塞态、运行态状态切换规则与进程一致时间片用完→就绪遇阻塞事件→阻塞阻塞事件结束→就绪。3.Linux 下多线程基于NPTL新 POSIX 线程库 实现为第三方线程库提供标准操作接口。4.线程上下文切换开销远低于进程同进程线程共享地址空间切换时仅需保存 / 恢复线程私有少量资源无需重新分配内存空间等系统资源。5、多线程与多进程的形象类比6.线程的资源特性核心共享 私有一同进程线程共享的资源无需额外通信即可访问可执行指令、静态数据、进程打开的文件描述符、信号处理函数、当前工作目录、用户 IDUID、用户组 IDGID。二每个线程私有的资源独立拥有互不干扰线程 IDTID、PC程序计数器及相关寄存器、堆栈、局部变量、返回地址、错误号errno、信号掩码和优先级、执行状态和属性。7.多线程的核心优点经济实惠分配资源少线程切换、维护的系统开销远低于进程资源共享同进程线程天然共享存储器和进程资源通信效率高提升响应速度程序某部分阻塞 / 执行冗长操作时其他线程可继续运行提升多 CPU 利用率在多处理机体系结构中多线程实现真正的并行执行提高并发性。三、多线程编程-线程创建1.核心函数函数名所需头文件函数原型参数说明返回值pthread_create线程创建#include pthread.hint pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*routine)(void *), void *arg)1. thread指向线程 ID 的指针接收创建后的线程标识 2. attr线程属性NULL 为默认属性 3. routine线程执行的函数指针新线程入口 4. arg传递给 routine 函数的参数成功0 出错-1pthread_join线程等待#include pthread.hint pthread_join(pthread_t thread, void **value_ptr)1. thread需要等待的目标线程 ID 2. value_ptr指向指针的指针接收线程退出的返回值NULL 则不获取成功0 出错-1pthread_exit线程主动退出#include pthread.hint pthread_exit(void *value_ptr)value_ptr线程退出时的返回值可被 pthread_join 获取成功0 出错-1pthread_cancel线程取消#include pthread.hint pthread_cancel(pthread_t thread)thread需要被取消的目标线程 ID成功0 出错-12.示例代码两个线程每隔 1 秒交替打印 A、B#includestdio.h#includepthread.h#includeunistd.h// 线程1打印 Avoid*print_A(void*arg){while(1){printf(A\n);sleep(1);// 每隔1秒}}// 线程2打印 Bvoid*print_B(void*arg){while(1){printf(B\n);sleep(1);// 每隔1秒}}intmain(){pthread_ttid1,tid2;// 创建线程1pthread_create(tid1,NULL,print_A,NULL);// 创建线程2pthread_create(tid2,NULL,print_B,NULL);// 等待线程pthread_join(tid1,NULL);pthread_join(tid2,NULL);return0;}这份代码能实现打印 A、B 的效果但无法保证严格的 “交替”3.线程查看字段全称 / 含义核心说明UIDUser ID运行该进程 / 线程的用户 ID标识进程的属主PIDProcess ID进程 ID同一进程下的所有线程PID 完全相同线程属于该进程PPIDParent Process ID父进程 ID标识该进程的父进程LWPLight Weight Process轻量级进程 ID即线程 ID/TID同一进程下的每个线程拥有唯一 LWP主线程的 LWP 进程 PIDCCPU utilizationCPU 占用率标识进程 / 线程的 CPU 使用情况NLWPNumber of Light Weight Process该进程包含的轻量级进程线程总数量即进程的线程数STIMEStart Time进程 / 线程的启动时间TTYTerminal该进程 / 线程运行的终端?表示后台运行无终端TIMECPU Time进程 / 线程累计占用的 CPU 总时间CMDCommand进程 / 线程的启动命令同一进程下的所有线程 CMD 完全一致四、多线程 —— 互斥锁1.为什么需要互斥锁多个线程共享全局变量 / 资源时如果同时进行读写修改会出现数据混乱、结果错误。这种多个线程同时操作共享资源导致的问题叫线程竞争 / 竞态条件。必须保证同一时间只有一个线程访问共享资源。互斥锁就是用来保护临界区实现线程同步与互斥。2.基本概念1.临界区访问共享资源的代码片段必须独占执行。2.互斥锁mutex一把 “锁”保证同一时刻只有一个线程进入临界区。3.工作原理访问前加锁访问中独占资源访问结束解锁其他线程必须等待锁释放才能进入3.核心函数函数名所需头文件函数原型功能说明返回值pthread_mutex_init#include pthread.hint pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);初始化互斥锁成功0失败错误码pthread_mutex_lock#include pthread.hint pthread_mutex_lock(pthread_mutex_t *mutex);加锁阻塞等待成功0失败错误码pthread_mutex_unlock#include pthread.hint pthread_mutex_unlock(pthread_mutex_t *mutex);解锁成功0失败错误码pthread_mutex_destroy#include pthread.hint pthread_mutex_destroy(pthread_mutex_t *mutex);销毁互斥锁成功0失败错误码使用步骤1.定义互斥锁变量pthread_mutex_t mutex;2.初始化锁pthread_mutex_init(mutex, NULL);3.进入临界区前加锁pthread_mutex_lock(mutex);4.访问共享资源临界区代码5.退出临界区后解锁pthread_mutex_unlock(mutex);6.程序结束销毁锁pthread_mutex_destroy(mutex);4.示例代码#includestdio.h#includepthread.h#includeunistd.h// 定义互斥锁 顺序控制标志pthread_mutex_tmutex;intflag0;// 0打印A 1打印B// 打印 A 的线程void*print_A(void*arg){while(1){// 加锁pthread_mutex_lock(mutex);if(flag0){printf(A\n);flag1;// 切换标志让B打印sleep(1);// 每隔1秒}// 解锁pthread_mutex_unlock(mutex);}}// 打印 B 的线程void*print_B(void*arg){while(1){// 加锁pthread_mutex_lock(mutex);if(flag1){printf(B\n);flag0;// 切换标志让A打印sleep(1);// 每隔1秒}// 解锁pthread_mutex_unlock(mutex);}}intmain(){pthread_ttid1,tid2;// 初始化互斥锁pthread_mutex_init(mutex,NULL);// 创建线程pthread_create(tid1,NULL,print_A,NULL);pthread_create(tid2,NULL,print_B,NULL);// 等待线程pthread_join(tid1,NULL);pthread_join(tid2,NULL);// 销毁锁pthread_mutex_destroy(mutex);return0;}这样就是严格交替打印 A、B了PS:互斥锁保证同一时间只有一个线程打印避免混乱。flag控制谁能打印强制轮流保证严格交替。但这个其实也不是更好的互斥锁 条件变量这才是线程同步的标准答案。五、多线程-条件变量1.为什么需要条件变量互斥锁只能解决互斥访问问题不能解决线程间的执行顺序、等待 / 唤醒问题。互斥锁 flag 会造成忙等待大量占用 CPU效率极低。需要一种机制条件不满足时线程主动休眠等待不占 CPU条件满足时其他线程唤醒等待线程条件变量就是用来实现线程间同步、等待与唤醒的机制。2.基本概念1.条件变量允许线程在满足某个条件前阻塞等待直到其他线程发送信号唤醒。2.必须与互斥锁配合使用锁保护条件本身防止并发修改条件条件变量负责等待与唤醒3.典型场景生产者消费者、交替打印、队列有数据再消费等。3.条件变量核心函数函数名所需头文件函数原型功能说明返回值pthread_cond_init#include pthread.hint pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);初始化条件变量成功0失败错误码pthread_cond_wait#include pthread.hint pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);阻塞等待条件满足会自动释放锁成功0失败错误码pthread_cond_signal#include pthread.hint pthread_cond_signal(pthread_cond_t *cond);唤醒一个等待在该条件上的线程成功0失败错误码pthread_cond_broadcast#include pthread.hint pthread_cond_broadcast(pthread_cond_t *cond);唤醒所有等待在该条件上的线程成功0失败错误码pthread_cond_destroy#include pthread.hint pthread_cond_destroy(pthread_cond_t *cond);销毁条件变量成功0失败错误码pthread_cond_wait 关键特性调用时会自动释放互斥锁让其他线程能进入临界区修改条件。被唤醒时会自动重新加锁保证条件判断安全。必须在已加锁的情况下调用。必须配合while判断条件防止虚假唤醒。4.示例代码条件变量 让 “暂时不能打印” 的线程去睡觉不浪费 CPU#includestdio.h#includepthread.h#includeunistd.hpthread_mutex_tmutex;pthread_cond_tcond;intflag0;// 0:A打印 1:B打印void*print_A(void*arg){while(1){pthread_mutex_lock(mutex);// 条件不满足就阻塞等待不占CPUwhile(flag!0)pthread_cond_wait(cond,mutex);printf(A\n);flag1;sleep(1);// 唤醒Bpthread_cond_signal(cond);pthread_mutex_unlock(mutex);}}void*print_B(void*arg){while(1){pthread_mutex_lock(mutex);while(flag!1)pthread_cond_wait(cond,mutex);printf(B\n);flag0;sleep(1);// 唤醒Apthread_cond_signal(cond);pthread_mutex_unlock(mutex);}}intmain(){pthread_ttid1,tid2;pthread_mutex_init(mutex,NULL);pthread_cond_init(cond,NULL);pthread_create(tid1,NULL,print_A,NULL);pthread_create(tid2,NULL,print_B,NULL);pthread_join(tid1,NULL);pthread_join(tid2,NULL);pthread_cond_destroy(cond);pthread_mutex_destroy(mutex);return0;}