婚介网站模板,设计师网名大全,企业形象设计学什么,小程序对接wordpress目录
资源竞争#xff08;背景#xff09; 锁#xff08;解决方式#xff0c;实现同步#xff09;
互斥锁
读写锁
自旋锁 资源竞争#xff08;背景#xff09;
竞态条件
当多个线程并发访问和修改同一个共享资源#xff08;如全局变量#xff09;时#xff0c;…目录
资源竞争背景 锁解决方式实现同步
互斥锁
读写锁
自旋锁 资源竞争背景
竞态条件
当多个线程并发访问和修改同一个共享资源如全局变量时如果没有适当的同步措施就会遇到线程同步问题。这种情况下程序最终的结果依赖于线程执行的具体时序导致了竞态条件。
竞态条件race condition是一种特定的线程同步问题指的是两个或者以上进程或者线程并发执行时其最终的结果依赖于进程或者线程执行的精确时序。它会导致程序的行为和输出超出预期因为共享资源的最终状态取决于线程执行的顺序和时机。为了确保程序执行结果的正确性和预期一致需要通过适当的线程同步机制来避免竞态条件。
#includestdlib.h
#includestdio.h
#includeunistd.h
#includepthread.h#define COUNT 20000long long int num 0;void* fun(void *arg) {for (int i 0;i 1000000;i) { // 加100wnum;}
}int main(int argc, char const* argv[])
{pthread_t tid[2];pthread_create(tid[0], NULL, fun, NULL);pthread_create(tid[1], NULL, fun, NULL);// 等待全部线程执行完成for (int i 0;i 2;i) {pthread_join(tid[i], NULL);}printf(num的值是%lld\n, num);return 0;
} 两个线程使用同一个资源出现资源竞争问题。 锁解决方式实现同步
如何避免竞态条件
上述程序如果想避免竞态条件有下面两种解决方案
避免多线程写入一个地址。给资源加锁使同一时间操作特定资源的线程只有一个。
方法1可以通过逻辑上组织业务逻辑实现这里我们讲方法2。
想解决竞争问题我们需要互斥锁——mutex。
常见的锁机制
锁主要用于互斥即在同一时间只允许一个执行单元进程或线程访问共享资源。包括上面的互斥锁在内常见的锁机制共有三种
互斥锁Mutex保证同一时刻只有一个线程可以执行临界区的代码。读写锁Reader/Writer Locks允许多个读者同时读共享数据但写者的访问是互斥的。自旋锁Spinlocks在获取锁之前线程在循环中忙等待适用于锁持有时间非常短的场景一般是Linux内核使用。
互斥锁
pthread_mutex_t 是一个定义在头文件pthreadtypes.h中的联合体类型的别名其声明如下。
typedef union
{struct __pthread_mutex_s __data;char __size[__SIZEOF_PTHREAD_MUTEX_T];long int __align;
} pthread_mutex_t;pthread_mutex_t用作线程之间的互斥锁。互斥锁是一种同步机制用来控制对共享资源的访问。在任何时刻最多只能有一个线程持有特定的互斥锁。如果一个线程试图获取一个已经被其他线程持有的锁那么请求锁的线程将被阻塞直到锁被释放。 用途 保护共享数据避免同时被多个线程访问导致的数据不一致问题。实现线程间的同步确保线程之间对共享资源的访问按照预定的顺序进行。 操作 初始化pthread_mutex_init创建互斥锁并初始化。锁定pthread_mutex_lock获取互斥锁。如果锁已经被其他线程持有调用线程将阻塞。尝试锁定pthread_mutex_trylock尝试获取互斥锁。如果锁已被持有立即返回而不是阻塞。解锁pthread_mutex_unlock释放互斥锁使其可被其他线程获取。销毁pthread_mutex_destroy清理互斥锁资源。 #include pthread.h
/*** brief 获取锁如果此时锁被占则阻塞* * param mutex 锁* return int 获取锁结果*/
int pthread_mutex_lock(pthread_mutex_t *mutex);/*** brief 非阻塞式获取锁如果锁此时被占则返回EBUSY* * param mutex 锁* return int 获取锁结果*/
int pthread_mutex_trylock(pthread_mutex_t *mutex);/*** brief 释放锁* * param mutex 锁* return int 释放锁结果*/
int pthread_mutex_unlock(pthread_mutex_t *mutex);pthread_mutex_lock 该函数用于锁定指定的互斥锁。如果互斥锁已经被其他线程锁定调用此函数的线程将会被阻塞直到互斥锁变为可用状态。这意味着如果另一个线程持有锁当前线程将等待直到锁被释放。 成功时返回0失败时返回错误码。 pthread_mutex_trylock 该函数尝试锁定指定的互斥锁。与pthread_mutex_lock不同如果互斥锁已经被其他线程锁定pthread_mutex_trylock不会阻塞调用线程而是立即返回一个错误码EBUSY。 如果成功锁定互斥锁则返回0如果互斥锁已被其他线程锁定返回EBUSY其他错误情况返回不同的错误码。 pthread_mutex_unlock 该函数用于解锁指定的互斥锁。调用线程必须是当前持有互斥锁的线程否则解锁操作可能会失败。 成功时返回0失败时返回错误码。 初始化互斥锁
PTHREAD_MUTEX_INITIALIZER是POSIX线程Pthreads库中定义的一个宏用于静态初始化互斥锁mutex。这个宏为互斥锁提供了一个初始状态使其准备好被锁定和解锁而不需要在程序运行时显式调用初始化函数。
当我们使用PTHREAD_MUTEX_INITIALIZER初始化互斥锁时实际上是将互斥锁设置为默认属性和未锁定状态。这种初始化方式适用于简单的同步问题我们可以通过以下代码初始化互斥锁。
static pthread_mutex_t counter_mutex PTHREAD_MUTEX_INITIALIZER;
案例对上面那个资源竞争的代码稍作修改
static pthread_mutex_t mutex PTHREAD_MUTEX_INITIALIZER;void* fun(void* arg) {for (int i 0;i 1000000;i) { // 加100w// 获取锁pthread_mutex_lock(mutex);num;// 释放锁pthread_mutex_unlock(mutex);}
}
读写锁
读操作在读写锁的控制下多个线程可以同时获得读锁。这些线程可以并发地读取共享资源但它们的存在阻止了写锁的授予。
写操作如果至少有一个读操作持有读锁写操作就无法获得写锁。写操作将会阻塞直到所有的读锁都被释放。 pthread_rwlock_t typedef union
{struct __pthread_rwlock_arch_t __data;char __size[__SIZEOF_PTHREAD_RWLOCK_T];long int __align;
} pthread_rwlock_t;pthread_rwlock_init() /*** brief 为rwlock指向的读写锁分配所有需要的资源并将锁初始化为未锁定状态。读写锁的属性由attr参数指定如果attr为NULL则使用默认属性。当锁的属性为默认时可以通过宏PTHREAD_RWLOCK_INITIALIZER初始化即* pthread_rwlock_t rwlock PTHREAD_RWLOCK_INITIALIZER; 效果和调用当前方法并为attr传入NULL是一样的* * param rwlock 读写锁* param attr 读写锁的属性* return int 成功则返回0否则返回错误码*/
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);pthread_rwlock_destroy() #include pthread.h/*** brief 销毁rwlock指向的读写锁对象并释放它使用的所有资源。当任何线程持有锁的时候销毁锁或尝试销毁一个未初始化的锁结果是未定义的。* * param rwlock * return int */
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);pthread_rwlock_rdlock() /*** brief 应用一个读锁到rwlock指向的读写锁上并使调用线程获得读锁。如果写线程持有锁调用线程无法获得读锁它会阻塞直至获得锁。* * param rwlock 读写锁* return int 成功返回0失败返回错误码*/
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);pthread_rwlock_wrlock() /*** brief 应用一个写锁到rwlock指向的读写锁上并使调用线程获得写锁。只要任意线程持有读写锁则调用线程无法获得写锁它将阻塞直至获得写锁。* * param rwlock 读写锁* return int 成功返回0失败返回错误码*/
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);pthread_rwlock_unlock() /*** brief 释放调用线程锁持有的rwlock指向的读写锁。* * param rwlock 读写锁* return int 成功返回0.失败返回错误码*/
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);案例
#include stdio.h
#include pthread.h
#include unistd.h
#include stdlib.hpthread_rwlock_t rwlock;
int shared_data 0;void* lock_reader(void* args) {// 读写锁中的读是可以多个线程同时读取的// 获取读锁pthread_rwlock_rdlock(rwlock);printf(%s读取到%d\n, (char*)args, shared_data);pthread_rwlock_unlock(rwlock);free(args);
}void* lock_writer(void* args) {// 获取写锁pthread_rwlock_wrlock(rwlock);sleep(1);shared_data;printf(当前%s写入完毕,结果是%d\n, (char*)args, shared_data);// 释放写锁pthread_rwlock_unlock(rwlock);
}int main(int argc, char const* argv[])
{// 动态初始化创建锁pthread_rwlock_init(rwlock,NULL);pthread_t writer1, writer2;pthread_t read[10];// 写线程pthread_create(writer1, NULL, lock_writer, 写线程1);pthread_create(writer2, NULL, lock_writer, 写线程2);sleep(3);// 读线程for (int i 0;i 10;i) {char* s (char*)malloc(20 * sizeof(char));sprintf(s, 读线程%d, i);pthread_create(read[i], NULL, lock_reader, s);}// 主线程等待pthread_join(writer1, NULL);pthread_join(writer2, NULL);for (int i 0;i 10;i) {pthread_join(read[i], NULL);}pthread_rwlock_destroy(rwlock);return 0;
}
要注意的是线程的执行顺序是由操作系统内核调度的其运行规律并不简单地为“先创建先执行”。 写饥饿 多次运行后我们发现此时读操作总是连续执行的且读操作休眠未结束时写操作会被阻塞。与工作原理相符① 读操作可以并发执行相互之间不必争抢锁多个读操作可以同时获得读锁② 只要有一个线程持有读写锁写操作就会被阻塞。我们在读操作中加了1s休眠只要有一个读线程获得锁在1s内写操作是无法执行的其它读操作就可以有充足的时间执行因此读操作就会连续发生写操作必须等待所有读操作执行完毕方可获得读写锁执行写操作。这就是使用读写锁时存在的潜在问题写饥饿。 解决方法 ① 问题描述 读写锁的写饥饿问题Writer Starvation是指在使用读写锁时写线程可能无限期地等待获取写锁因为读线程持续地获取读锁而不断地推迟写线程的执行。这种情况通常在读操作远多于写操作时出现。 ② 解决方案 Linux提供了可以修改的属性pthread_rwlockattr_t默认情况下属性中指定的策略为“读优先”当写操作阻塞时读线程依然可以获得读锁从而在读操作并发较高时导致写饥饿问题。我们可以尝试将策略更改为“写优先”当写操作阻塞时读线程无法获取锁避免了写线程持有锁的时间持续延长使得写线程获取锁的等待时间显著降低从而避免写饥饿问题。 pthread_rwlockattr_t typedef union
{char __size[__SIZEOF_PTHREAD_RWLOCKATTR_T];long int __align;
} pthread_rwlockattr_t;pthread_rwlockattr_init #include pthread.h/*** brief 用所有属性的默认值初始化attr指向的属性对象* * param attr 读写锁属性对象指针* return int 成功返回0失败返回错误码*/
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);pthread_rwlockattr_destroy #include pthread.h/*** brief 销毁读写锁属性对象* * param attr 读写锁属性对象指针* return int 成功返回0失败返回错误码*/
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);pthread_rwlockattr_setkind_np #include pthread.h/*** brief 将attr指向的属性对象中的锁类型属性设置为pref规定的值* * param attr 读写锁属性对象指针* param pref 希望设置的锁类型可以被设置为以下三种取值的其中一种* PTHREAD_RWLOCK_PREFER_READER_NP: 默认值读线程拥有更高优先级。当存在阻塞的写线程时读线程仍然可以获得读写锁。只要不断有新的读线程写线程将一直保持饥饿。* PTHREAD_RWLOCK_PREFER_WRITER_NP: 写线程拥有更高优先级。这一选项被glibc忽略。* PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP: 写线程拥有更高优先级在当前系统环境下它是有效的将锁类型设置为该值以避免写饥饿。* return int 成功返回0失败返回非零的错误码*/
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);案例
#include stdio.h
#include pthread.h
#include unistd.h
#include stdlib.hpthread_rwlock_t rwlock;
int shared_data 0;void* lock_reader(void* args) {// 读写锁中的读是可以多个线程同时读取的// 获取读锁printf(%s开始获取读锁\n,(char*)args);pthread_rwlock_rdlock(rwlock);printf(%s读取到%d\n, (char*)args, shared_data);sleep(1);pthread_rwlock_unlock(rwlock);free(args);
}void* lock_writer(void* args) {// 获取写锁printf(%s开始获取写锁\n,(char*)args);pthread_rwlock_wrlock(rwlock);shared_data;printf(当前%s写入完毕,结果是%d\n, (char*)args, shared_data);// 释放写锁pthread_rwlock_unlock(rwlock);
}int main(int argc, char const* argv[])
{// 创建写锁属性对象pthread_rwlockattr_t attr;pthread_rwlockattr_init(attr);// 设置写线程优先级pthread_rwlockattr_setkind_np(attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);// 动态初始化创建锁pthread_rwlock_init(rwlock,attr);pthread_t writer1, writer2;pthread_t read[10];// 写线程pthread_create(writer1, NULL, lock_writer, 写线程1);// 读线程for (int i 0;i 10;i) {char* s (char*)malloc(20 * sizeof(char));sprintf(s, 读线程%d, i);pthread_create(read[i], NULL, lock_reader, s);if (i 4) {pthread_create(writer2, NULL, lock_writer, 写线程2);}}// 主线程等待pthread_join(writer1, NULL);pthread_join(writer2, NULL);for (int i 0;i 10;i) {pthread_join(read[i], NULL);}pthread_rwlock_destroy(rwlock);pthread_rwlockattr_destroy(attr);return 0;
}
可以看到写线程是先于读线程的。 不会像前面那样出现写饥饿问题。 自旋锁
我们写的用户锁一般是要尽量避免空转的而对于自旋锁是属于操作系统内核的锁不需要像用户锁那样去避免空转
在Linux内核中自旋锁是一种用于多处理器系统中的低级同步机制主要用于保护非常短的代码段或数据结构以避免多个处理器同时访问共享资源。自旋锁相对于其他锁的优点是它们在锁被占用时会持续检查锁的状态即“自旋”而不是让线程进入休眠。这使得自旋锁在等待时间非常短的情况下非常有效因为它避免了线程上下文切换的开销。
自旋锁主要用于内核模块或驱动程序中避免上下文切换的开销。不能在用户空间使用。