目录
pthread_rwlock_tryrdlock()(非阻塞)
pthread_rwlock_trywrlock()(非阻塞)

一段需要互斥执行的代码称为临界区。线程在进入临界区之前,首先尝试加锁lock(),如果成功,则进入临界区,此时称这个线程持有锁;否则,就等待,直到持有锁的线程解锁;持有锁的线程执行完临界区的代码后,执行解锁unlock()。
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
#include
//头文件
互斥锁的初始化
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
pthread_mutex_t: 定义互斥体机制完成多线程的互斥操作,该机制的作用是对某个需要互斥的部分,在进入时先得到互斥体,如果没有得到互斥体,表明互斥部分被其它线程拥有,此时欲获取互斥体的线程阻塞,直到拥有该互斥体的线程完成互斥部分的操作为止。
mutex:是指向要初始化的互斥锁的指针。
attr: 是指向属性对象的指针,该属性对象定义要初始化的互斥锁的属性。如果该指针为 NULL,则使用默认的属性。,默认属性为快速互斥锁
成功则返回0, 出错则返回错误编号。
pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_init(&mutex, NULL)
mutex 指向要销毁的互斥锁的指针
int pthread_mutex_destroy(pthread_mutex_t *mutex);
互斥锁销毁函数在执行成功后返回 0,否则返回错误码。
对共享资源的访问, 要对互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁. 在完成了对共享资源的访问后, 要对互斥量进行解锁。
int pthread_mutex_lock(pthread_mutex_t *mutex);
成功则返回0, 出错则返回错误编号.
int pthread_mutex_trylock(pthread_mutex_t *mutex);
说明: 具体说一下trylock函数, 这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态。
函数释放有参数mutex指定的mutex对象的锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功则返回0, 出错则返回错误编号.
由于共享、竞争而没有加任何同步机制,导致产生于时间有关的错误,造成数据混乱:
- #include
- #include
- #include
- #include
- #include
-
- void *tfn(void *arg)
- {
- for(int i =0;i < 5;i++)
- {
- printf("hi, ");
- sleep(1);
- printf(" boy \n");
- sleep(1);
- }
- return NULL;
- }
-
- int main(void)
- {
-
- pthread_t tid;
- pthread_create(&tid, NULL, tfn, NULL);
- for(int i =0;i < 5;i++)
- {
- printf("hello, ");
- sleep(1);
- printf(" girl\n");
- sleep(1);
- }
- pthread_join(tid , NULL);
- return 0;
- }
-
输出结果是无序的
- #include
- #include
- #include
- #include
- #include
-
- pthread_mutex_t mutex;
-
- void *tfn(void *arg)
- {
- for(int i =0;i < 5;i++)
- {
- pthread_mutex_lock(&mutex);
- printf("hi, ");
- sleep(1);
- printf(" boy \n");
- pthread_mutex_unlock(&mutex);
- sleep(1);
- }
- return NULL;
- }
-
- int main(void)
- {
-
- pthread_t tid;
- pthread_mutex_init(&mutex, NULL);
- pthread_create(&tid, NULL, tfn, NULL);
- for(int i =0;i < 5;i++)
- {
- printf("hello, ");
- sleep(1);
- printf(" girl\n");
- sleep(1);
- }
- pthread_mutex_destroy(&mutex);
- pthread_join(tid , NULL);
- return 0;
- }
-
多线程实验
- #include
- #include
- #include
- #include
-
- pthread_mutex_t mutex;
- int val = 0;
- void *ThreadFunc(void *arg)
- {
- while(1)
- {
- pthread_mutex_lock(&mutex); /*获取互斥锁*/
- int i = 0;
- for(i = 0 ; i < 10000 ; i++)
- {
- val++;
- }
- printf("val = %d\n" , val);
- pthread_mutex_unlock(&mutex); /*释放互斥锁*/
- usleep(20);
- }
-
- return 0;
- }
-
- int main()
- {
- void *ResVal;
- pthread_mutex_init (&mutex, NULL); /*定义*/
- pthread_t thread1 , thread2;
- pthread_create(&thread1 , NULL , ThreadFunc , NULL);
- pthread_create(&thread2 , NULL , ThreadFunc , NULL);
- pthread_join(thread1 , &ResVal);
- pthread_join(thread2 , &ResVal);
- }
正是由于自旋锁使用者一般保持锁时间非常短,自旋锁的效率远高于互斥锁。
自旋锁保持期间是抢占失效的,而信号量和读写信号量保持期间是可以被抢占的。
#include
初始化spin lock, 当线程使用该函数初始化一个未初始化或者被destroy过的spin lock有效。该函数会为spin lock申请资源并且初始化spin lock为unlocked状态
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
若成功,返回0;否则,返回错误编号
用来获取(锁定)指定的自旋锁. 如果该自旋锁当前没有被其它线程所持有,则调用该函数的线程获得该自旋锁.否则该函数在获得自旋锁之前不会返回。
int pthread_spin_lock(pthread_spinlock_t *lock);
若成功,返回0;否则,返回错误编号。
EBUSY A thread currently holds the lock.These functions shall not return an error code of [EINTR].如果调用该函数的线程在调用该函数时已经持有了该自旋锁,则结果是不确定的。
int pthread_spin_unlock(pthread_spinlock_t *lock);
若成功,返回0;否则,返回错误编号
用来销毁指定的自旋锁并释放所有相关联的资源(所谓的所有指的是由pthread_spin_init自动申请的资源)
int pthread_spin_destroy(pthread_spinlock_t *lock);
若成功,返回0;否则,返回错误编号
Mutex(互斥锁):
Spin lock(自旋锁)
- #include
- #include
-
- #include
- #include
- #include
-
- int num = 0;
- pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-
- int64_t get_current_timestamp()
- {
- struct timeval now = {0, 0}; //定义时间结构体,前面参数是秒,后面是微妙
- gettimeofday(&now, NULL);//获取当前时间
- return now.tv_sec * 1000 * 1000 + now.tv_usec;
- }
-
- void thread_proc()
- {
- for(int i=0; i<1000000; ++i){
- pthread_mutex_lock(&mutex);
- ++num;
- pthread_mutex_unlock(&mutex);
- }
- }
-
- int main()
- {
- int64_t start = get_current_timestamp();
- std::thread t1(thread_proc), t2(thread_proc);//C++11定义线程
- t1.join();
- t2.join();
- std::cout<<"num:"<
- int64_t end = get_current_timestamp();
- std::cout<<"cost:"<
" us" << std::endl; -
- pthread_mutex_destroy(&mutex); //maybe you always foget this
-
- return 0;
- }
读写锁
- #include
- #include
-
- #include
- #include
- #include
-
- int num = 0;
- pthread_spinlock_t spin_lock;
-
- int64_t get_current_timestamp()
- {
- struct timeval now = {0, 0};//定义时间结构体,前面参数是秒,后面是微妙
- gettimeofday(&now, NULL);//获取当前时间
- return now.tv_sec * 1000 * 1000 + now.tv_usec;
- }
-
- void thread_proc()
- {
- for(int i=0; i<100000000; ++i){
- pthread_spin_lock(&spin_lock);
- ++num;
- pthread_spin_unlock(&spin_lock);
- }
- }
-
- int main()
- {
- pthread_spin_init(&spin_lock, PTHREAD_PROCESS_PRIVATE);//maybe PHREAD_PROCESS_PRIVATE or PTHREAD_PROCESS_SHARED
-
- int64_t start = get_current_timestamp();
-
- std::thread t1(thread_proc), t2(thread_proc);
- t1.join();
- t2.join();
-
- std::cout<<"num:"<
- int64_t end = get_current_timestamp();
- std::cout<<"cost:"<
" us" < -
- pthread_spin_destroy(&spin_lock);
-
- return 0;
- }
结果分析
自旋锁运行效率快一些
改一下循环次数
- for(int i=0; i<100000000; ++i){
- pthread_spin_lock(&spin_lock);
- ++num;
- for(int i=0; i<100; ++i){
- //do nothing
- }
- pthread_spin_unlock(&spin_lock);
- }
自旋锁运行效率慢一些,spin lock虽然mutex的性能更好(花费很少的CPU指令),但是它只适应于临界区运行时间很短的场景。
读写锁
读者写者问题
有一群写者和一群读者,写者在写同一本书,读者也在读这本书,多个读者可以同时读这本书,但是,只能有一个写者在写书。特征:
- 任意多的读进程可以同时读这个文件;
- 一次只允许一个写进程往文件中写;
- 如果一个写进程正在往文件中写,禁止任何读进程或写进程访问文件;
- 写进程执行写操作前,应让已有的写者或读者全部退出。这说明当有读者在读文件时不允许写者写文件。
有时候,在多线程中,有一些公共数据修改的机会比较少,而读的机会却是非常多的,此公共数据的操作基本都是读,如果每次操作都给此段代码加锁,太浪费时间了而且也很浪费资源,降低程序的效率,因为读操作不会修改数据,只是做一些查询,所以在读的时候不用给此段代码加锁,可以共享的访问,只有涉及到写的时候,互斥的访问就好了
定义
读写锁是一种特殊的自旋锁,它把对共享资源对访问者划分成了读者和写者,读者只对共享资源进行访问,写者则是对共享资源进行写操作。读写锁一个读写锁同时只能存在一个写锁但是可以存在多个读锁,但不能同时存在写锁和读锁。
三种状态
如果读写锁当前没有读者,也没有写者,那么写者可以立刻获的读写锁,否则必须自旋,直到没有任何的写锁或者读锁存在。如果读写锁没有写锁,那么读锁可以立马获取,否则必须等待写锁释放。
所以读写锁具备三种状态:
- 读模式下加锁状态 (读锁)
- 写模式下加锁状态 (写锁)
- 不加锁状态
特性
- 读写锁是"写模式加锁"时, 解锁前,所有对该锁加锁的线程都会被阻塞;
- 读写锁是"读模式加锁"时, 如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞;
- 读写锁是"读模式加锁"时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高。
写独占,读共享;写锁优先级高
使用流程(相关API)
pthread_rwlock_init()
作用
初始化
函数原型
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
参数
- pthread_rwlock_t:定义读写锁的结构体
- PTHREAD_RWLOCK_INITIALIZER静态初始化读写锁
- pthread_rwlock_init 函数动态初始化锁
- attr:通常使用默认属性,NULL
pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);
返回值
成功返回0, 失败直接返回错误号
pthread_rwlock_rdlock() (阻塞)
作用
申请读锁
函数原型
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
返回值
成功,返回 0。否则,将返回用于指明错误的错误号
注意事项
- 如果写入器未持有读锁,并且没有任何写入器基于该锁阻塞,则调用线程会获取读锁。
- 如果写入器未持有读锁,但有多个写入器正在等待该锁时,调用线程是否能获取该锁是不确定的。
- 如果一个线程写锁定了 读写锁后,又调用了pthread_rwlock_rdlock来读锁定同一个读写锁,结果无法预测。
- 如果某个写入器持有读锁,则调用线程无法获取该锁。
- 调用线程必须获取该锁之后,才能从 pthread_rwlock_rdlock() 返回。
- 为避免写入器资源匮乏,允许在多个实现中使写入器的优先级高于读取器。
- pthread_rwlock_rdlock() n 次。该线程必须调用pthread_rwlock_unlock() n 次才能执行匹配的解除锁定操作。
- 线程信号处理程序可以处理传送给等待读写锁的线程的信号。从信号处理程序返回后,线程将继续等待读写锁以执行读取,就好像线程未中断一样。
pthread_rwlock_tryrdlock()(非阻塞)
作用
申请读锁
函数原型
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);//尝试加读锁,没锁上就立即返回
返回值
无论加锁是否成功,上面的函数都会立即返回,成功返回 0,失败返回 EBUSY。
注意事项
如果任何线程持有 rwlock 中的写锁或者写入器基于 rwlock 阻塞,则 pthread_rwlock_tryrdlock()
函数会失败。
pthread_rwlock_wrlock() (非阻塞)
作用
申请写锁
函数原型
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
返回值
成功返回0, 失败直接返回错误号
注意事项
- 如果没有其他读取器线程或写入器线程持有读写锁 rwlock,则调用线程将获取写锁,否则,调用线程将阻塞。
- 调用线程必须获取该锁之后,才能从 pthread_rwlock_wrlock() 调用返回。
- 如果在进行调用时,调用线程持有读写锁(读锁或写锁),则结果是不确定的。
- 为避免写入器资源匮乏,写入器的优先级高于读取器。 如果针对未初始化的读写锁调用 pthread_rwlock_wrlock(),则结果是不确定的。
- 线程信号处理程序可以处理传送给等待读写锁以执行写入的线程的信号。从信号处理程序返回后,线程将继续等待读写锁以执行写入,就好像线程未中断一样。
pthread_rwlock_trywrlock()(非阻塞)
作用
申请写锁
函数原型
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);//尝试(没锁上就立即返回)加锁
返回值
成功返回0, 失败直接返回错误号
注意事项
- 如果任何线程当前持有用于读取和写入的 rwlock,则pthread_rwlock_trywrlock() 函数会失败。
- 如果针对未初始化的读写锁调用 pthread_rwlock_trywrlock(),则结果是不确定的
pthread_rwlock_unlock()
作用
释放在 rwlock 引用的读写锁对象中持有的锁。
函数原型
int pthread_rwlock_unlock (pthread_rwlock_t *rwlock);
返回值
成功返回 0,否则返回用于指明错误的错误号
注意事项
- 如果调用线程未持有读写锁 rwlock,则结果是不确定的。
- 如果通过调用 pthread_rwlock_unlock()来释放读写锁对象中的读锁,并且其他读锁当前由该锁对象持有,则该对象会保持读取锁定状态。
- 如果 pthread_rwlock_unlock()释放了调用线程在该读写锁对象中的最后一个读锁,则调用线程不再是该对象的属主。
- 如果 pthread_rwlock_unlock()释放了该读写锁对象的最后一个读锁,则该读写锁对象将处于无属主、解除锁定状态。
- 如果通过调用 pthread_rwlock_unlock() 释放了该读写锁对象的最后一个写锁,则该读写锁对象将处于无属主、解除锁定状态。
- 如果 pthread_rwlock_unlock()解除锁定该读写锁对象,并且多个线程正在等待获取该对象以执行写入,则通过调度策略可确定获取该对象以执行写入的线程。
- 如果多个线程正在等待获取读写锁对象以执行读取,则通过调度策略可确定等待线程获取该对象以执行写入的顺序。
- 如果多个线程基于rwlock 中的读锁和写锁阻塞,则无法确定读取器和写入器谁先获得该锁。
- 如果针对未初始化的读写锁调用 pthread_rwlock_unlock(),则结果是不确定的。
pthread_rwlock_destroy()
作用
销毁 rwlock 引用的读写锁对象并释放该锁使用的任何资源
函数原型
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
返回值
成功,返回 0。否则,返回用于指明错误的错误号
注意事项
- 尝试销毁未初始化的读写锁会产生不确定的行为。
- 已销毁的读写锁对象可以使用 pthread_rwlock_init() 来重新初始化。
- 销毁读写锁对象之后,如果以其他方式引用该对象,则结果是不确定的。
测试代码
- #include
- #include
- #include
- #include
-
- pthread_rwlock_t rwlock;//创建读写锁
- long int love;
-
- void *pth_wr(void *arg)//写操作
- {
- int i = (int)arg;//参数类型转化
- while (love <= 520)
- {
- pthread_rwlock_wrlock(&rwlock);//请求写锁
- printf("write================love = %ld, threadID = %d\n", love += 40, i + 1);//写操作,love每次加40
- pthread_rwlock_unlock(&rwlock);//写锁释放
- sleep(1);
- }
- return NULL;
- }
- void *pth_rd(void *arg)//读操作
- {
- int i = (int)arg;
- while (love <= 520)
- {
- pthread_rwlock_rdlock(&rwlock);//请求读锁
- printf("love = %ld, threadID = %d-------------------- read\n", love, i + 1);
- pthread_rwlock_unlock(&rwlock);//读锁释放
- sleep(1);
- }
- return NULL;
- }
- int main(void)
- {
- pthread_t pth[10];
- int i;
- pthread_rwlock_init(&rwlock, NULL);
- for (i = 0; i != 5; i++)
- {
- pthread_create(&pth[i], NULL, pth_wr, (void *)i);
- }
- for (i = 0; i != 5; i++)
-
- {
- pthread_create(&pth[5 + i], NULL, pth_rd, (void *)i);
- }
- while (1)
- {
- if (love >= 520)
- {
- for (int j = 0; j != 10; j++)
- {
- pthread_join(pth[j], NULL);
- }
- break;
- }
- }
- pthread_rwlock_destroy(&rwlock);
- return 0;
- }
互斥锁、读写锁、自旋锁区别
互斥锁:
- 用于保证在任何时刻,都只能有一个线程访问该对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒
读写锁:
- 分为读锁和写锁。处于读操作时,可以允许多个线程同时获得读操作。但是同一时刻只能有一个线程可以获得写锁。其它获取写锁失败的线程都会进入睡眠状态,直到写锁释放时被唤醒。 注意:写锁会阻塞其它读写锁。当有一个线程获得写锁在写时,读锁也不能被其它线程获取;写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)。适用于读取数据的频率远远大于写数据的频率的场合。
自旋锁:
- 在任何时刻同样只能有一个线程访问对象。但是当获取锁操作失败时,不会进入睡眠,而是会在原地自旋,直到锁被释放。这样节省了线程从睡眠状态到被唤醒期间的消耗,在加锁时间短暂的环境下会极大的提高效率。但如果加锁时间过长,则会非常浪费CPU资源。
读写锁的三种实现
由于C++14和C++17 均对读写锁进行了函数封装,但是这里的是C++11实现读写锁
使用互斥锁和条件变量实现读写锁
- class readwrite_lock
- {
- public:
- readwrite_lock()
- : stat(0)
- {
- }
-
- void readLock()
- {
- mtx.lock();
- while (stat < 0)
- cond.wait(mtx);
- ++stat;
- mtx.unlock();
- }
-
- void readUnlock()
- {
- mtx.lock();
- if (--stat == 0)
- cond.notify_one(); // 叫醒一个等待的写操作
- mtx.unlock();
- }
-
- void writeLock()
- {
- mtx.lock();
- while (stat != 0)
- cond.wait(mtx);
- stat = -1;
- mtx.unlock();
- }
-
- void writeUnlock()
- {
- mtx.lock();
- stat = 0;
- cond.notify_all(); // 叫醒所有等待的读和写操作
- mtx.unlock();
- }
-
- private:
- mutex mtx;
- condition_variable cond;
- int stat; // == 0 无锁;> 0 已加读锁个数;< 0 已加写锁
- };
使用2个互斥锁实现读写锁
- class readwrite_lock
- {
- public:
- readwrite_lock()
- : read_cnt(0)
- {
- }
-
- void readLock()
- {
- read_mtx.lock();
- if (++read_cnt == 1)
- write_mtx.lock();
-
- read_mtx.unlock();
- }
-
- void readUnlock()
- {
- read_mtx.lock();
- if (--read_cnt == 0)
- write_mtx.unlock();
-
- read_mtx.unlock();
- }
-
- void writeLock()
- {
- write_mtx.lock();
- }
-
- void writeUnlock()
- {
- write_mtx.unlock();
- }
-
- private:
- mutex read_mtx;
- mutex write_mtx;
- int read_cnt; // 已加读锁个数
- };
用mutex和conditon实现写优先的读写锁
-
- class RWLock {
- private:
- pthread_mutex_t mxt;
- pthread_cond_t cond;
- int rd_cnt;//等待读的数量
- int wr_cnt;//等待写的数量
-
- public:
- RWLock() :rd_cnt(0), wr_cnt(0) {
- pthread_mutex_init(&mxt,NULL);
- pthread_cond_init(&cond,NULL);
- }
-
- void readLock() {
- pthread_mutex_lock(&mxt);
-
- ++rd_cnt;
- while(wr_cnt > 0)
- pthread_mutex_wait(&cond, &mxt);
-
- pthread_mutex_unlock(&mxt);
- }
-
- void readUnlock() {
- pthread_mutex_lock(&mxt);
-
- --rd_cnt;
- if (rd_cnt == 0 )
- pthread_cond_signal(&cond);
-
- pthread_mutex_unlock(&mxt);
- }
-
- void writeLock() {
- pthread_mutex_lock(&mxt);
-
- ++wr_cnt;
- while (wr_cnt + rd_cnt >=2)
- pthread_cond_wait(&cond, &mxt);
-
- pthread_mutex_unlock(&mxt);
- }
-
- void writerUnlock() {
- pthread_mutex_lock(&mxt);
-
- --wr_cnt;
- if(wr_cnt==0)
- pthread_cond_signal(&cond);
-
- pthread_mutex_unlock(&mxt);
- }
- };
-
相关阅读:
风行智能电视N39S、N40 强制刷机升级方法,附刷机升级数据MstarUpgrade.bin
算法训练day43|动态规划 part05:0-1背包 (LeetCode 1049. 最后一块石头的重量 II、494. 目标和、474.一和零)
08架构管理之架构检查方法
Redis持久化机制详解
SpringCloud 组件Gateway服务网关【全局过滤器】
TensorFlow学习(3)初始化&非饱和激活函数&批量归一化&梯度剪裁&迁移学习&优化器
VR全景技术在城市园区发展中有哪些应用与帮助
机器学习-朴素贝叶斯之多项式模型
计算机毕业设计(附源码)python野生动物保护资讯管理系统
MP逻辑删除
-
原文地址:https://blog.csdn.net/m0_62807361/article/details/132787375