1.C++语言实现多线程同步的四种方式(代码演示)
2.linux是如何解决优先级反转问题的?
3.Pthread编程barrier,spin,mutex,rwlock,semaphore,condition详解
4.c++笔记(读写锁)
5.Linux线程共享变量的安全性保障linux线程共享变量
C++语言实现多线程同步的四种方式(代码演示)
本文将探讨C++语言实现多线程同步的四种方式:互斥锁、条件变量、读写锁与信号量。
互斥锁是C++线程同步的基础,实现一个特殊全局变量,具有lock和unlock状态。古剑飞仙手游源码大全锁定互斥锁后,其他线程需在锁被释放后方能获取。静态初始化时,使用`pthread_mutex_t mutex_x = PTHREAD_MUTEX_INITIALIZER;`;动态初始化时,调用`pthread_mutex_init`函数。
互斥锁属性分为`PTHREAD_PROCESS_PRIVATE`与`PTHREAD_PROCESS_SHARED`,前者限于单进程内,后者支持跨进程。互斥锁类型包括三种,通过`type`参数定义。
测试加锁函数`pthread_mutex_lock`在锁被占用时返回`EBUSY`,允许线程继续运行,而非挂起。此函数可清晰展示线程争用资源的情况。
条件变量是互斥锁的补充,用于线程等待特定条件满足时,进入睡眠状态,当条件满足时,唤醒线程。条件变量通过阻塞和等待信号实现,常与互斥锁结合使用。创建条件变量使用静态方式`pthread_cond_t cond PTHREAD_COND_INITIALIZER;`或动态方式`int pthread_cond_init(&cond,微网课源码NULL);`。
条件变量有等待、信号与广播函数。等待函数`pthread_cond_wait(&cond,&mutex)`与`pthread_cond_timewait`允许线程等待条件满足或超时。信号函数`pthread_cond_signal(&cond)`与广播函数`pthread_cond_broadcast(&cond)`唤醒等待线程。
读写锁允许多个线程同时读取数据,但只允许一个线程写入。读写锁分为强读同步与强写同步策略。初始化读写锁使用静态方式`pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;`或动态方式`int pthread_rwlock_init(rwlock,NULL);`,资源释放前需调用`pthread_rwlock_destory`。
读写锁的获取锁方式分为阻塞与非阻塞两种。非阻塞方式允许快速尝试获取锁,避免阻塞,提高效率。
信号量允许线程共享资源,与互斥锁不同的是,信号量允许多个线程进入临界区。初始化信号量后,通过加减操作管理资源。示例通过信号量模拟窗口服务,展示资源分配与释放过程。
linux是如何解决优先级反转问题的?
从一个线上问题说起,最近在线上遇到了一些[HMDConfigManager remoteConfigWithAppID:]卡死的情况。通过观察发现,出问题的线程都有QOS:BACKGROUND标记。整体看起来持有锁的子线程仍然在执行,只是留给主线程的时间不够了。这种现象可能是移码转化为源码优先级反转导致的,即持有读写锁且优先级低的线程迟迟得不到调度,而具有高优先级的线程由于拿不到读写锁,一直被阻塞,从而导致互相死锁。
解决这个问题的关键在于调整队列的优先级。在iOS8之后引入了QualityOfService的概念,类似于线程的优先级,通过设置不同的QualityOfService的值,可以分配不同的CPU时间、网络资源和硬盘资源等。因此,我们可以尝试通过调整队列的优先级来解决优先级反转问题。
具体解决方法有两种:一种是去除对NSOperationQueue的优先级设置,遵循苹果的建议,不要随意修改线程的优先级,特别是存在高低优先级线程之间存在临界资源竞争的情况。另一种方法是在线程通过pthread_rwlock_wrlock拿到_rwlock的时候,临时提升其优先级,在释放_rwlock之后,恢复其原先的优先级。这里需要注意的是,这种方法只能使用pthread的api,不能使用NSThread提供的API。
为了验证上述的手动调整线程优先级是否有效,可以通过本地实验进行测试。定义了个operation(目的是为了CPU繁忙),优先级设置为NSQualityOfServiceUserInitiated,有了源码然后且对其中可以被整除的operation的优先级调整为NSQualityOfServiceBackground,在每个operation执行相同的耗时任务,然后对这被选中的个operation进行耗时统计。
统计结果显示,通过手动调整其优先级,低优先级任务的整体耗时得到大幅度的降低,这样在持有锁的情况下,可以减少对主线程的阻塞时间。最终上线验证,通过调整优先级解决了线上遇到的问题,卡死情况得到了缓解。
通过深入理解优先级反转,可以发现它是指某同步资源被较低优先级的进程/线程所拥有,较高优先级的进程/线程竞争该同步资源未获得该资源,导致较高优先级进程/线程反而推迟被调度执行的现象。根据阻塞类型的不同,优先级反转可以分为Bounded priority inversion和Unbounded priority inversion。
解决Unbounded priority inversion有两种方法:优先权极限(priority ceiling protocol)和优先级继承(priority inheritance)。这两种方法都会在释放锁的时候,恢复低优先级任务的优先级,但无法阻止Bounded priority inversion。要避免优先级反转,可以通过QoS传递在不同线程(或queue)间传递QoS。
在iOS系统中,优先级反转可以通过系统API来触发优先级反转避免机制。在发生优先级反转时,如果系统知道block所在的驱动鼠标按键源码目标线程,系统会通过提高相关线程的优先级来解决优先级反转的问题。如果系统不知道block所在的目标线程,则无法知道应该提高谁的优先级,也就无法解决反转问题。
通过深入调研各种基础系统API,如pthread mutex、os_unfair_lock、pthread_rwlock_t、dispatch_sync、dispatch_wait、dispatch_semaphore等,可以验证这些API在解决优先级反转问题时的表现。通过验证可以发现,不同API在处理优先级反转时有不同表现,例如pthread mutex、os_unfair_lock和dispatch_sync等API在处理优先级反转时能够提升线程优先级,而dispatch_semaphore则无法避免优先级反转。
在字节跳动APM团队,我们致力于提升整个集团内全系产品的性能和稳定性表现,工作内容包括但不限于性能稳定性监控、问题排查、深度优化、防劣化等。我们期望为业界输出更多更有建设性的问题发现和深度优化手段。如果你对字节APM团队职位感兴趣,欢迎投递简历至邮箱 xushuangqing@bytedance.com。
Pthread编程barrier,spin,mutex,rwlock,semaphore,condition详解
在多线程编程中,理解同步机制对于确保程序的正确性和效率至关重要。以下是对各种同步机制的详细解析,包括barrier、spin、mutex、rwlock、semaphore和condition,以及它们在并行算法中的应用。barrier
barrier是一种同步机制,用于确保一组线程在执行特定操作前达到一致状态。它维护一个计数器和等待线程队列。当线程请求检查barrier状态时,如果等待线程数量小于指定计数减一,该线程将被挂起并加入队列,返回状态为0。当等待线程数量达到计数减一时,barrier会循环唤醒所有挂起的线程,清空队列,并返回PTHREAD_BARRIER_SERIAL_THREAD状态。lock
锁是解决线程间并发数据访问冲突的一种机制,确保同一时刻只有一个线程能访问共享资源。程序在访问数据前应先执行加锁操作,获取访问权限。加锁意味着向操作系统申请访问权,当前线程在获取权限前不能执行后续操作。开锁则是将访问权限归还操作系统,可能分配给等待的线程之一。spin
自旋锁是一种轻量级的锁机制,用于处理简单的线程同步问题。在元素查找实例中,自旋锁允许线程在等待资源时进行循环操作,而不是阻塞等待,从而减少线程切换的开销。mutex
互斥锁是一种同步机制,用于保护共享资源免受并发访问的影响。当一个线程执行开锁操作后,其他线程只能在队列非空时恢复执行,或者当前线程将锁的状态设为开锁,继续执行。rwlock
读写锁是一种并发控制机制,允许多个线程同时读取共享资源,但同一时刻仅允许一个线程进行写操作。原子操作函数,如`__sync_fetch_and_add`,用于安全地对计数器进行加法操作,防止中间状态导致的数据不一致。semaphore
信号量是用于控制多个线程访问共享资源的一种机制,常用于生产者-消费者模型。信号量维护一个计数器,表示当前可以被消费的资源数量。生产者向信号量申请释放资源,消费者从信号量请求资源。condition
条件量(condition)与互斥锁结合使用,用于协调线程间的同步。当一个线程需要等待特定条件满足时,它可以通过条件量进行等待,而当条件满足时,可以使用`pthread_cond_broadcast`或`pthread_cond_signal`唤醒等待的线程。 通过这些同步机制,多线程程序可以有效地控制并行操作的执行顺序,避免数据竞争和死锁,从而提高程序的性能和可靠性。c++笔记(读写锁)
C++中,当一个线程持有互斥锁时,其他试图进入的线程会被阻塞,但若读操作更为频繁,互斥锁的排它性可能会导致读请求被阻塞。为了解决这个问题,引入了读写锁。读写锁设计用于允许多个线程同时读取,但限制写操作的独占性。 其主要特点如下:在读操作活跃时,其他线程可以申请并获取读锁,执行读操作,但禁止写操作。
若写操作正在进行,所有线程都被阻塞,包括读操作,直到写操作结束。
读写锁分为读锁和写锁,遵循以下规则:一个线程持有读锁时,其他线程可以获取读锁,但不能申请写锁。
一旦有线程持有写锁,无论是读锁还是写锁,其他线程都不能获取。
在POSIX系统中,读写锁的实现形式为pthread_rwlock_t类型。Linux线程共享变量的安全性保障linux线程共享变量
Linux线程共享变量的安全性保障
在Linux操作系统开发中,由于多核CPU的出现,线程开发技术也得到了普及。随着当前Linux应用系统的发展,一个应用进程通常会被分解成多个多线程进行任务的分解,这样可以有效的提高程序的运行效率。这个时候,多个线程之间实现变量共享就变得十分重要,使整个程序的逻辑性能更加紧凑、运行效率更高。那么,Linux线程共享变量的安全性保障如何实现呢?
首先,需要采用“互斥锁”来保护共享变量,避免出现两线程更改同一变量时,产生数据混乱的情况,可以使用Linux提供的pthread_mutex_lock()函数来实现。
另外,为了防止多线程在竞争互斥锁时出现死锁,可以使用pthread_mutex_trylock()函数。这个函数可以在每次申请获取互斥锁时,自动判断互斥锁状态,如果当前互斥不可以获取,则返回EBUSY状态。
常用的 Linux线程共享变量的安全性保障还有“读写锁”。当一个线程企图去更改共享变量时,读写锁将阻止其他任何程序访问该变量,可以使用pthread_rwlock_wrlock()函数实现。而当一个进程只是企图去读取共享变量时,获取读写锁控制权,无需阻塞其它程序,就可以进行读取。可以使用pthread_rwlock_rdlock()函数实现。
此外,关于Linux线程共享变量的安全性问题,在某些情况下也可以使用“信号量”来处理,信号量通常用于在多个线程之间实现线程的控制,可以使用sem_wait()、sem_post()函数实现。
通过以上几种方式,Linux线程共享变量的安全性保障可以得到极好的保障,在多线程应用运行中,可以有效的避免多线程资源竞争带来的不稳定因素,从而提高程序效率。