Linux多线程开发
2024-06-18 14:47:05 4 举报
AI智能生成
Linux后端
作者其他创作
大纲/内容
线程(thread)是允许应用程序并发执行多个任务的一种机制
一个进程可以包含多个线程。同一个程序中的所有线程均会独立执行相同程序,且共享同一份全局内存区域,其中包括初始化数据段、未初始化数据段,以及堆内存段
传统意义上的 UNIX 进程只是多线程程序的一个特例,该进程只包含一个线程
线程是轻量级的进程(LWP:Light Weight Process),在 Linux 环境下线程的本质仍是进程
查看指定进程的 LWP 号:ps –Lf pid
简介
进程是 CPU 分配资源的最小单位,线程是操作系统调度执行的最小单位。
进程间的信息难以共享。由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换
调用 fork() 来创建进程的代价相对较高,即便利用写时复制技术,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着 fork() 调用在时间上的开销依然不菲
线程之间能够方便、快速地共享信息。只需将数据复制到共享(全局或堆)变量中即可
创建线程比创建进程通常要快 10 倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表
线程和进程区别
进程的栈空间、text段被分为一小块一小块交给各个线程
共享库、堆空间等各个线程共享
线程和进程的虚拟地址空间
进程 ID 和父进程 ID
进程组 ID 和会话 ID
用户 ID 和 用户组 ID
文件描述符表
信号处置
文件权限掩码
当前工作目录
文件系统的相关信息
虚拟地址空间(除了栈和text段)
共享资源
线程 ID
信号掩码
线程特有数据
error 变量
实时调度策略和优先级
栈,本地变量和函数的调用链接信息
非共享资源
线程之间共享和非共享资源
函数原型
创建一个线程
功能
一个指向pthread_t类型的指针,用于存储新创建线程的 ID
thread
一个指向pthread_attr_t类型的指针,用于设置线程属性
如果设置为NULL,则使用默认属性
attr
线程开始执行的函数指针
start_routine
传递给start_routine的参数
arg
参数
成功时返回0
失败时返回错误号
返回值
pthread_create()
pthread_t pthread_self(void)
返回调用线程的线程ID
调用线程的pthread_t类型的线程 ID
pthread_self()
t1和t2,要比较的两个线程ID
相等返回非0值
否则返回0
void pthread_exit(void *retval)
retval,一个指针,指向线程的返回值
该函数不会返回
注意
pthread_exit()
thread:要等待的线程的线程 ID
retval:一个指向指针的指针,用于存储线程的返回值
pthread_join()
int pthread_detach(pthread_t thread);
分离线程,使其在终止时自动释放所有资源
thread:要分离的线程的线程 ID
pthread_detach()
int pthread_cancel(pthread_t thread);
发送取消请求给指定的线程
thread:要取消的线程的线程 ID
pthread_cancel()
创建管理线程
初始化线程属性对象,为其设置默认值
pthread_attr_init(pthread_attr_t *attr)
销毁线程属性对象,释放任何与之相关的资源
pthread_attr_destroy(pthread_attr_t *attr)
初始化和销毁
设置线程堆栈大小
获取线程堆栈大小
堆栈大小
获取线程的调度策略
设置线程的调度参数,通常用于设置线程的优先级
获取线程的调度参数
调度策略和优先级
设置线程的分离状态
PTHREAD_CREATE_DETACHED(创建后立即分离)
PTHREAD_CREATE_JOINABLE(可以被其他线程 join)
例如
获取线程的分离状态
分离状态
设置线程是否应从创建它的线程继承调度属性
获取线程的继承调度属性设置
继承调度属性
设置线程属性
函数
用于表示线程标识符的数据类型
程序员通常不需要(也不应该)知道其内部结构或如何工作
不透明性
每个线程都有一个与之关联的唯一的 pthread_t 值
唯一性
不能用==来比较两个线程,要用pthread_equal()函数进行比较
比较
当使用 pthread_create() 函数创建一个新线程时,新线程的 pthread_t 值会被存储在传递给该函数的 pthread_t 变量中
初始化
特性
主要用于线程管理和操作
等待线程完成(pthread_join())
取消线程(pthread_cancel())
查询线程属性
用途
pthread_t
不透明,但是提供了许多操作
pthread_attr_t
类型
线程操作
线程
线程的主要优势在于,能够通过全局变量来共享信息
必须确保多个线程不会同时修改同一变量,或者某一线程不会读取正在由其他线程修改的变量
线程的优势
问某一共享资源的代码片段,并且这段代码的执行应为原子操作
同时访问同一共享资源的其他线程不应终端该片段的执行
临界区
即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作
该线程完成操作,其他线程才能对该内存地址进行操作,而其他线程则处于等待状态
线程同步
为避免线程更新共享变量时出现问题,可以使用互斥量(mutex 是 mutual exclusion的缩写)来确保同时仅有一个线程可以访问某项共享资源.
互斥量有两种状态:已锁定(locked)和未锁定(unlocked)
任何时候,至多只有一个线程可以锁定该互斥量
试图对已经锁定的某一互斥量再次加锁,将可能阻塞线程或者报错失败,具体取决于加锁时使用的方法
状态
一旦线程锁定互斥量,随即成为该互斥量的所有者,只有所有者才能给互斥量解锁
一般情况下,对每一共享资源(可能由多个相关变量组成)会使用不同的互斥量
针对共享资源锁定互斥量
访问共享资源
对互斥量解锁
每一线程在访问同一资源时将采用如下协议
所有者
如果多个线程试图执行这一块代码(一个临界区),事实上只有一个线程能够持有该互斥量(其他线程将遭到阻塞),即同时只有一个线程能够进入这段代码区域
img src=\
阻塞
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
静态初始化
初始化一个互斥量
mutex:指向要初始化的互斥量的指针
如果使用默认属性,可以设置为NULL
attr:指向pthread_mutexattr_t的指针,用于设置互斥量的属性
pthread_mutex_init()
函数动态初始化
定义和初始化
int pthread_mutex_lock(pthread_mutex_t *mutex)
锁定互斥量
mutex:要解锁的互斥量的指针
pthread_mutex_lock()
int pthread_mutex_trylock(pthread_mutex_t *mutex);
尝试锁定互斥量,但不阻塞
mutex:要尝试锁定的互斥量的指针
如果互斥量已被锁定则返回EBUSY
其他失败情况返回错误号
pthread_mutex_trylock()
int pthread_mutex_unlock(pthread_mutex_t *mutex);
解锁互斥量
pthread_mutex_unlock():
锁定和解锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
销毁一个已初始化的互斥量
mutex:要销毁的互斥量的指针
在销毁互斥量之前,必须确保它是未锁定的,并且没有其他线程正在等待它。
pthread_mutex_destroy()
销毁
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
attr:指向要初始化的互斥量属性对象的指针
返回值:成功时返回0,失败时返回错误号
pthread_mutexattr_init()
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
attr:指向要销毁的互斥量属性对象的指针
pthread_mutexattr_destroy():
互斥量属性 (pthread_mutexattr_t)
相关函数
pthread_mutex_t,不透明
互斥量
有时,一个线程需要同时访问两个或更多不同的共享资源,而每个资源又都由不同的互斥量管理
当超过一个线程加锁同一组互斥量时,就有可能发生死锁
场景
两个或两个以上的进程在执行过程中,因争夺共享资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁
定义
忘记释放锁
重复加锁
多线程多锁,抢占锁资源
常见原因
死锁
当有一个线程已经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住。
但是考虑一种情形,当前持有互斥锁的线程只是要读访问共享资源,而同时有其它几个线程也想读取这个共享资源
但是由于互斥锁的排它性,所有其它线程都无法获取锁,也就无法读访问共享资源了,但是实际上多个线程同时读访问共享资源并不会导致问题
在对数据的读写操作中,更多的是读操作,写操作较少,例如对数据库数据的读写应用
为了满足当前能够允许多个读出,但只允许一个写入的需求,线程提供了读写锁来实现
如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作
如果有其它线程写数据,则其它线程都不允许读、写操作
写是独占的,写的优先级高
特点
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
初始化一个读写锁。
rwlock:指向要初始化的读写锁的指针
指向pthread_rwlockattr_t的指针,用于设置读写锁的属性
pthread_rwlock_init()
动态初始化
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
为读操作锁定读写锁
rwlock:要锁定的读写锁的指针。
pthread_rwlock_rdlock()
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
尝试为读操作锁定读写锁,但不阻塞
rwlock:要尝试锁定的读写锁的指针
如果读写锁已被写锁定则返回EBUSY
pthread_rwlock_tryrdlock()
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
解锁读写锁
rwlock:要解锁的读写锁的指针
pthread_rwlock_unlock():
读锁定和解锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
为写操作锁定读写锁
rwlock:要锁定的读写锁的指针
pthread_rwlock_wrlock()
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
尝试为写操作锁定读写锁,但不阻塞
如果读写锁已被读锁定或写锁定则返回EBUSY
他失败情况返回错误号
pthread_rwlock_trywrlock()
写锁定和解锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
销毁一个已初始化的读写锁
rwlock:要销毁的读写锁的指针
pthread_rwlock_destroy()
不透明
pthread_rwlock_t
读写锁
生产者消费者模型
允许线程等待某个条件成为真,而在等待时释放互斥锁
当其他线程改变了这个条件并发出通知时,等待的线程会被唤醒并重新尝试获得互斥锁
pthread_cond_t
初始化一个条件变量
cond:指向要初始化的条件变量的指针。
指向pthread_condattr_t的指针,用于设置条件变量的属性
pthread_cond_init()
等待条件变量
cond:要等待的条件变量的指针
与条件变量关联的互斥锁的指针
mutex
调用此函数前,线程必须持有该互斥锁。
pthread_cond_wait()
当线程需要等待某个条件成为真时,它会使用 pthread_cond_wait 函数
这个函数会自动释放与条件变量关联的互斥锁,并使线程进入阻塞状态
当条件变量被通知时,线程会被唤醒并重新尝试获得互斥锁
介绍
等待
在指定的时间内等待条件变量
cond:要等待的条件变量的指针。
mutex:与条件变量关联的互斥锁的指针
指定等待的绝对时间
如果在这个时间之前条件变量没有被通知,函数会返回
abstime
如果超时则返回ETIMEDOUT
pthread_cond_timedwait()
pthread_cond_timedwait 函数允许线程等待一个指定的时间段
如果在这段时间内条件没有变为真,函数会返回
有时限的等待
使用 pthread_cond_signal 函数唤醒一个等待条件变量的线程
唤醒一个等待条件变量的线程。
int pthread_cond_signal(pthread_cond_t *cond);
cond:要通知的条件变量的指针
pthread_cond_signal()
单个通知
使用 pthread_cond_broadcast 函数唤醒所有等待条件变量的线程
唤醒所有等待条件变量的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
pthread_cond_broadcast()
广播通知
通知
销毁一个已初始化的条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
cond:要销毁的条件变量的指针
pthread_cond_destroy():
在调用 pthread_cond_wait 或 pthread_cond_timedwait 之前,线程必须持有与条件变量关联的互斥锁
当条件变量被通知并唤醒线程时,线程会自动重新获得互斥锁
为了避免虚假唤醒,通常在一个循环中检查条件,并在条件不满足时调用 pthread_cond_wait
条件变量
同步原语,用于控制多个线程对共享资源的访问
是一个整数值,通常用于表示可用资源的数量
提供了两个主要的操作:wait(或P操作)和signal(或V操作)
信号量可以被初始化为任何非负整数值,表示资源的初始数量
当线程想要获取一个资源时,它会执行wait操作
这会导致信号量的值减少1
如果信号量的值在操作前已经是0,那么执行wait操作的线程会被阻塞,直到其他线程释放资源
wait (P操作)
当线程释放一个资源时,它会执行signal操作
这会导致信号量的值增加1
如果有线程因为wait操作而被阻塞,那么一个线程会被唤醒
signal (V操作)
基本概念和操作
初始化一个信号量
sem指向要初始化的信号量的指针
如果设置为非零值,信号量将在进程之间共享
否则,它只能在同一进程的线程之间共享
pshared
信号量的初始值
value
sem_init()
int sem_wait(sem_t *sem);
减少信号量的值
如果信号量的值为0,则调用线程会被阻塞
sem:要操作的信号量的指针
sem_wait()
int sem_post(sem_t *sem);
增加信号量的值,并唤醒任何等待的线程
sem_post()
int sem_destroy(sem_t *sem);
销毁一个信号量,释放其相关的资源
sem_destroy()
信号量
Linux多线程开发
0 条评论
回复 删除
下一页