多线程编程的概念二
Pthread线程库Linux的API对象操作LINUX Pthread API线程创建退出等待pthread_createpthread_exitpthread_join互斥锁创建销毁加锁解锁pthread_mutex_initpthread_mutex_destroypthread_mutex_lockpthr...
-
Pthread线程库
Linux的API
对象 操作 LINUX Pthread API 线程 创建
退出
等待pthread_create
pthread_exit
pthread_join互斥锁 创建
销毁
加锁
解锁pthread_mutex_init
pthread_mutex_destroy
pthread_mutex_lock
pthread_mutex_unlock条件变量 创建
销毁
触发
广播
等待pthread_cond_init
pthread_cond_destroy
pthread_cond_signal
pthread_cond_broadcast
pthread_cond_wait读写锁 创建
等待
销毁pthread_rwlock_init
pthread_rwlock_rdlock
pthread_rwlock_destroy- 线程的实现:windows、Linux
- Pthread库:POSIX标准中的thread API
- Glibc与LinuxThread
- Glibc和NPTL
- Sgetconf GUN_LIBPTHREAD_VERSION
使用pthread库
- 安装man手册
- apt install glibc-doc manpages-posix-dev
- 程序的编译
- gcc main.c -lpthread
- /usr/lib/libpthread.a
- pthread常用API
在目录下查找文件
find -name libpthread.a
创建一个线程
API接口说明
函数原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
-函数功能:创建一个线程
-参数说明:
-thread:指向线程ID的指针
-attr:线程属性
-start_routine:线程执行实体入口
-arg:传递给线程的参数
-typedef unsigned long int pthread
当pthread_create成功返回时,新创建线程的线程ID会被设置成thread所指向的内存单元
attr参数用于制定各种不同的线程属性
新创建的线程从start_routine函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递一个以上参数,需要把这些参数放在一个结构体中。
线程的终止
- 线程终止的三种方式
- 从start_routine正常return
- 显示调用pthread_exit
- 函数原型:void pthread_exit(void *retval);
- 返回值通过参数retval传递
- 线程被pthread_cancel取消
单个线程可以通过3种方式退出,因此可以在不终止整个进程的情况下,停止它的控制流。
- 线程可以简单地从启动例程中返回,返回值是线程的退出码。
- 线程可以被同一个进程中的其他线程pthread_cancel取消。
- 线程调用pthread_exit。
线程pthread_exit与exit的区别
- 如果进程中的任意线程调用了exit、_Exit或者_exit,终止整个进程。
- 线程调用pthread_exit,只会结束当前线程,不影响程序的执行。
等待线程的终止
线程分两种
-Joinable
- PTHREAD_CREATE_JOINABLE
- 可通过pthread_join等待线程终止
- 调用pthread_join的线程会阻塞
- 一个Joinbale线程结束时,资源不会自动释放给系统(堆栈、exit状态等)
- 当线程终止时,pthread_join会回收该线程资源,然后返回
- 若无pthread_join参与“擦屁股”工作,该线程将变成僵尸线程
-Unjoinable
- PTHREAD_CREATE_DETACHED
- 可通过pthread_detach分离一个线程
- int pthread_detach(pthread_t thread);
- 当线程终止时,资源会自动释放给系统
API接口
- 函数原型:int pthread_detach(pthread_t thread);
- 函数功能:将指定线程与当前线程分离
- 参数说明:指定要分离的线程ID
线程的属性
默认参数
- 调度参数:
- 线程栈地址:
- 线程栈大小:8M
- 栈未尾警戒缓冲区大小:PAGESIZE
- 线程的分离状态:joinable、detached
- 继承性:PTHREAD_INHERIT_SCHED、PTHREAD_EXPLICIT_SCHED
- 作用域:PTHREAD_SCOPE_PROCESS、PTHREAD_SCOPE_SYSTEM
- 调度策略:SCHED_FIFO、SCHED_RR、SCHED_OTHER
相关API函数
- int pthread_attr_init(pthread_attr_t *attr);
- int pthread_attr_destroy(pthread_attr_t *attr);
- int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
- int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
- int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
- int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr);
- int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
- int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
线程调度与运行
-核心级线程
- 由内核调度,有利于并发使用多处理器资源
线程安全
多线程下的资源访问
其次,多个线程的任务之间还可能存在顺序依赖的关系,一个线程未能完成某些操作之前,其他线程不能或不应该运行。
多个线程之间需要同步。
想象如下场景:你和你的朋友合租一套公寓,这套公寓只有1个卫生间。当你朋友正在使用卫生间的时候,你就无法使用了。多线程也会遇到类似的问题。
多个线程生活在进程地址空间这同一个屋檐下,若存在多个线程操作共享资源,则需要同步,否则可能会出现结果错误、数据结构遭到破坏甚至是程序崩溃等后果。
因此多线程编程中存在临界区的概念,临界区的代码只允许一个线程执行,线程提供了锁机制来保护临界区。
当其他线程来到临界区却无法申请到锁时,就可能陷入阻塞,不再处于可执行状态,线程可能不得不让出CPU资源。如果设计不合理,临界区非常多,线程之间的竞争异常激烈,频繁地上下文切换也会导致性能急剧恶化。
对于多线程编程,还存在四大陷阱,一不小心就可能落入陷阱之中。
多线程的四个陷阱
分别是:
- 死锁(Dead Lock)
- 饿死(Starvation)
- 活锁(Live Lock)
- 竞态条件(Race Condition)
客观地说,多线程编程的难度要更大一些,需要程序员更加小心,更加谨慎。
当你需要使用多线程的时候,一定要花费足够的时间小心地规划每个线程的分工,尽可能地减少线程之间的依赖。
良好的设计,合理的分工是多线程编程至关重要的环节。
若初期随意地设计线程的分工,那么在最后,你很有可能不得不花费大量的时间来优化性能,定位bug,甚至不得不推倒重来。
资源划分
——共享的资源
- 代码段、数据段、地址空间
- 打开的文件、信号处理程序
——独占的资源
- 程序计数器:PC
- 寄存器
- 栈空间
- 不同体系不同分配方式
- 用户线程和管理线程栈是分离的
——进程与线程资源
- 一室一厅一卫
- 三室一厅一卫
内核空间 |
---|
线程1的栈空间 |
线程2的栈空间 |
线程3的栈空间 |
堆 |
数据 |
代码 |
线程安全
-
对共享资源的安全访问
- 临界区与临界资源
- 关中断
- 锁、条件变量、读写锁
-
函数引入
- 可重入函数
- 线程安全函数
多线程下的资源访问
—— 全局变量
—— 缓冲区(运用场合视频编解码)
——三室一厅卫生间
互斥锁
相关API函数
#include <pthread.h>
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
条件变量
基本概念
- 互斥锁缺陷:
- 不断加锁解锁、查询满足条件、开销很大
- 加锁开销:用户态-内核态-用户态,阻塞在内核态
- 解锁开销:用户态-内核态-用户态,唤醒等待线程
- 条件变量
- 互斥锁(mutex)搭配使用,允许线程阻塞,等待条件满足的信号
- 优势:
- 将互斥锁和条件变量绑定
- 省去了不断加锁解锁的开销
- 可以使用广播(broadcast)唤醒所有绑定到该条件变量的线程
#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
int pthread_cond_destroy(pthread_cond_t *cond);
线程同步:广播
唤醒所有线程
线程同步:读写锁
基本概念
- 互斥锁:同一时刻只允许一个线程读写
- 读写锁
- 允许多个读线程同时读
- 只允许一个线程写,写的时候会阻塞其他线程(包括读线程)
- 写优先级高于读
应用场景:读线程多过写线程
相关API:
#include <pthread.h>
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
线程池的概念
线程的开销
- 系统调用的开销:线程的创建、销毁
- 上下文切换、互斥锁等加锁解锁
线程池原理
- 预先在池中创建一些线程
- 无任务时,线程阻塞在池中
- 有任务时,将任务分配到指定的线程执行
- 池中线程的数目甚至可根据任务多少动态删减
实现原理
-管理线程
- 创建并管理线程
- 任务分配运行
-工作线程
- 线程池中实际执行任务的线程
-任务接口
- 每个任务的实现接口
实际上,线程池有异步、缓冲、FIFO
超线程技术
基本概念
- 电脑 ----打印机
- 电脑1----打印机
- 电脑2----打印机
超线程技术
- Hyper-Threading,简称HT
- 使用特殊指令将一个物理处理器视为2个逻辑处理器
- 每个逻辑处理器都可以分配一个线程运行
- 最大限度地提升CPU资源利用率
实现原理
-交替工作
-共享单元
- 执行单元
- 缓存
- 总线
-应用场所
- 服务器
- 工作站
协程的概念
-线程的开销
- 上下文切换开销
- 互斥锁的开销
-协程
- 对共享资源的访问由程序本身维护控制
- 不需要锁,无调度成本,执行效率高
- 适合彼此熟悉的程序组件:合作式多任务、管道
- 进程+线程 -> 进程 + 协程
更多推荐
所有评论(0)