0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

详细谈谈Linux中的多线程同步和互斥机制

dyquk4xk2p3d 来源:Linux兵工厂 作者:Linux兵工厂 2023-03-20 09:09 次阅读

同步和互斥

互斥:多线程中互斥是指多个线程访问同一资源时同时只允许一个线程对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的;

同步:多线程同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源。

互斥锁

在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源。为了同一时刻只允许一个任务访问资源,需要用互斥锁对资源进行保护。互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。

互斥锁操作基本流程

访问共享资源前,对互斥锁进行加锁

完成加锁后访问共享资源

对共享资源完成访问后,对互斥锁进行解锁

对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放

互斥锁特性

原子性:互斥锁是一个原子操作,操作系统保证如果一个线程锁定了一个互斥锁,那么其他线程在同一时间不会成功锁定这个互斥锁

唯一性:如果一个线程锁定了一个互斥锁,在它解除锁之前,其他线程不可以锁定这个互斥锁

非忙等待:如果一个线程已经锁定了一个互斥锁,第二个线程又试图去锁定这个互斥锁,则第二个线程将被挂起且不占用任何CPU资源,直到第一个线程解除对这个互斥锁的锁定为止,第二个线程则被唤醒并继续执行,同时锁定这个互斥锁

示例

#include
#include
#include
#include
#include

char*pTestBuf=nullptr;//全局变量

/*定义互斥锁*/
pthread_mutex_tmutex;

void*ThrTestMutex(void*p)
{
pthread_mutex_lock(&mutex);//加锁
{
pTestBuf=(char*)p;
sleep(1);
}
pthread_mutex_unlock(&mutex);//解锁
}

intmain()
{
/*初始化互斥量,默认属性*/
pthread_mutex_init(&mutex,NULL);

/*创建两个线程对共享资源访问*/
pthread_ttid1,tid2;
pthread_create(&tid1,NULL,ThrTestMutex,(void*)"Thread1");
pthread_create(&tid2,NULL,ThrTestMutex,(void*)"Thread2");

/*等待线程结束*/
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);

/*销毁互斥锁*/
pthread_mutex_destroy(&mutex);

return0;
}

读写锁

读写锁允许更高的并行性,也叫共享互斥锁。互斥量要么是加锁状态,要么就是解锁状态,而且一次只有一个线程可以对其加锁。读写锁可以有3种状态:读模式下加锁状态、写模式加锁状态、不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁,即允许多个线程读但只允许一个线程写。

当读操作较多,写操作较少时,可用读写锁提高线程读并发性

读写锁特性

如果有线程读数据,则允许其它线程执行读操作,但不允许写操作

如果有线程写数据,则其它线程都不允许读、写操作

如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁

如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁

读写锁适合于对数据的读次数比写次数多得多的情况

读写锁创建和销毁

#include
intphtread_rwlock_init(pthread_rwlock_t*restrictrwlock,constpthread_rwlockattr_t*restrictattr);
intpthread_rwlock_destroy(pthread_rwlock_t*rwlock);

参数:rwlock:读写锁,attr:读写锁属性

返回值:成功返回0,出错返回错误码

读写锁加锁解锁

#include
/**加读锁*/
intpthread_rwlock_rdlock(pthread_rwlock_t*rwlock);
/**加写锁*/
intpthread_rwlock_wrlock(pthread_rwlock_t*rwlock);
/**释放锁*/
intpthread_rwlock_unlock(pthread_rwlock_t*rwlock);

参数:rwlock:读写锁

返回值:成功返回 0;出错,返回错误码

示例

#include
#include
#include
#include
#include

/*定义读写锁*/
pthread_rwlock_trwlock;

/*定义共享资源变量*/
intg_nNum=0;

/*读操作其他线程允许读操作不允许写操作*/
void*fun1(void*arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);
{
printf("readthread1==%d
",g_nNum);
}
pthread_rwlock_unlock(&rwlock);

sleep(1);
}
}

/*读操作,其他线程允许读操作,不允许写操作*/
void*fun2(void*arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);
{
printf("readthread2==%d
",g_nNum);
}
pthread_rwlock_unlock(&rwlock);

sleep(1);
}
}

/*写操作,其它线程都不允许读或写操作*/
void*fun3(void*arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);
{
g_nNum++;
printf("writethread1
");
}
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
}
/*写操作,其它线程都不允许读或写操作*/
void*fun4(void*arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);
{
g_nNum++;
printf("writethread2
");
}
pthread_rwlock_unlock(&rwlock);

sleep(1);
}
}

intmain(intarc,char*argv[])
{
pthread_tThrId1,ThrId2,ThrId3,ThrId4;

pthread_rwlock_init(&rwlock,NULL);//初始化一个读写锁

/*创建测试线程*/
pthread_create(&ThrId1,NULL,fun1,NULL);
pthread_create(&ThrId2,NULL,fun2,NULL);
pthread_create(&ThrId3,NULL,fun3,NULL);
pthread_create(&ThrId4,NULL,fun4,NULL);

/*等待线程结束,回收其资源*/
pthread_join(ThrId1,NULL);
pthread_join(ThrId2,NULL);
pthread_join(ThrId3,NULL);
pthread_join(ThrId4,NULL);

pthread_rwlock_destroy(&rwlock);//销毁读写锁

return0;
}

结果

be5272ee-c6aa-11ed-bfe3-dac502259ad0.png

自旋锁

自旋锁与互斥锁功能相同,唯一不同的就是互斥锁阻塞后休眠不占用CPU,而自旋锁阻塞后不会让出CPU,会一直忙等待,直到得到锁

自旋锁在用户态较少用,而在内核态使用的比较多

自旋锁的使用场景:锁的持有时间比较短,或者说小于2次上下文切换的时间

自旋锁在用户态的函数接口和互斥量一样,把pthread_mutex_lock()/pthread_mutex_unlock()中mutex换成spin,如:pthread_spin_init()

自旋锁函数

linux中的自旋锁用结构体spinlock_t 表示,定义在include/linux/spinlock_type.h。自旋锁的接口函数全部定义在include/linux/spinlock.h头文件中,实际使用时只需include即可

示例

include
spinlock_tlock;//定义自旋锁
spin_lock_init(&lock);//初始化自旋锁
spin_lock(&lock);//获得锁,如果没获得成功则一直等待
{
.......//处理临界资源
}
spin_unlock(&lock);//释放自旋锁

条件变量

条件变量用来阻塞一个线程,直到条件发生。通常条件变量和互斥锁同时使用。条件变量使线程可以睡眠等待某种条件满足。条件变量是利用线程间共享的全局变量进行同步的一种机制。

条件变量的逻辑:一个线程挂起去等待条件变量的条件成立,而另一个线程使条件成立。

基本原理

线程在改变条件状态之前先锁住互斥量。如果条件为假,线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步

示例

#include
#include
#include
#include

pthread_cond_ttaxicond=PTHREAD_COND_INITIALIZER;
pthread_mutex_ttaximutex=PTHREAD_MUTEX_INITIALIZER;

void*ThrFun1(void*name)
{
char*p=(char*)name;

//加锁,把信号量加入队列,释放信号量
pthread_mutex_lock(&taximutex);
{
pthread_cond_wait(&taxicond,&taximutex);
}
pthread_mutex_unlock(&taximutex);

printf("ThrFun1:%snowgotasignal!
",p);
pthread_exit(NULL);
}

void*ThrFun2(void*name)
{
char*p=(char*)name;
printf("ThrFun2:%scondsignal.
",p);//发信号
pthread_cond_signal(&taxicond);
pthread_exit(NULL);
}

intmain(intargc,char**argv)
{
pthread_tThread1,Thread2;
pthread_attr_tthreadattr;
pthread_attr_init(&threadattr);//线程属性初始化

//创建三个线程
pthread_create(&Thread1,&threadattr,ThrFun1,(void*)"Thread1");
sleep(1);

pthread_create(&Thread2,&threadattr,ThrFun2,(void*)"Thread2");
sleep(1);

pthread_join(Thread1,NULL);
pthread_join(Thread2,NULL);

return0;
}

结果

be6a4fd6-c6aa-11ed-bfe3-dac502259ad0.png

虚假唤醒

当线程从等待已发出信号的条件变量中醒来,却发现它等待的条件不满足时,就会发生虚假唤醒。之所以称为虚假,是因为该线程似乎无缘无故地被唤醒了。但是虚假唤醒不会无缘无故发生:它们通常是因为在发出条件变量信号和等待线程最终运行之间,另一个线程运行并更改了条件

避免虚假唤醒

在wait端,我们必须把判断条件和wait()放到while循环中

pthread_mutex_lock(&taximutex);
{
while(value!=wantValue)
{
pthread_cond_wait(&taxicond,&taximutex);
}
}
pthread_mutex_unlock(&taximutex);

信号量

信号量用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于0时,则可以访问,否则将阻塞

#include

//初始化信号量
intsem_init(sem_t*sem,intpshared,unsignedintvalue);

//信号量P操作(减1)
intsem_wait(sem_t*sem);

//以非阻塞的方式来对信号量进行减1操作
intsem_trywait(sem_t*sem);

//信号量V操作(加1)
intsem_post(sem_t*sem);

//获取信号量的值
intsem_getvalue(sem_t*sem,int*sval);

//销毁信号量
intsem_destroy(sem_t*sem);

示例

//信号量用于同步实例
#include
#include
#include
#include

sem_tsem_g,sem_p;//定义两个信号量
chars8Test='a';

void*pthread_g(void*arg)//此线程改变字符的值
{
while(1)
{
sem_wait(&sem_g);
s8Test++;
sleep(2);
sem_post(&sem_p);
}
}
void*pthread_p(void*arg)//此线程打印字符的值
{
while(1)
{
sem_wait(&sem_p);
printf("%c",s8Test);
fflush(stdout);
sem_post(&sem_g);
}
}
intmain(intargc,char*argv[])
{
pthread_ttid1,tid2;
sem_init(&sem_g,0,0);//初始化信号量为0
sem_init(&sem_p,0,1);//初始化信号量为1

pthread_create(&tid1,NULL,pthread_g,NULL);
pthread_create(&tid2,NULL,pthread_p,NULL);

pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
return0;
}

结果

be7776e8-c6aa-11ed-bfe3-dac502259ad0.png



审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • cpu
    cpu
    +关注

    关注

    68

    文章

    10449

    浏览量

    206579
  • Linux
    +关注

    关注

    87

    文章

    10991

    浏览量

    206742
  • 计数器
    +关注

    关注

    32

    文章

    2126

    浏览量

    92996
  • 信号量
    +关注

    关注

    0

    文章

    53

    浏览量

    8259
  • Linux编程
    +关注

    关注

    0

    文章

    5

    浏览量

    602

原文标题:详解Linux多线程中互斥锁、读写锁、自旋锁、条件变量、信号量

文章出处:【微信号:良许Linux,微信公众号:良许Linux】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux多线程机制

    另一个带类型的指针变量线程同步互斥同步(按照预想的顺序执行)M->Y->M->Y->M->YM->YYY->M->YYY......互斥
    发表于 11-11 09:53

    Linux多线程机制

    定义变量,但不可以使用(*p = 100, p++, p--)  ///无类型指针只能赋值给另一个带类型的指针变量  线程同步互斥  同步(按照预想的顺序执行)  M->Y->M-
    发表于 01-10 14:59

    Linux多线程线程同步

    。同一进程内的线程共享进程的地址空间。通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步互斥手段的辅助,以保证数据的一致性。调度和切换:
    发表于 12-08 14:14

    多线程同步互斥有几种实现方法

    线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程
    发表于 08-05 06:06

    利用线程互斥实现串口多线程收发数据

    利用线程互斥实现串口多线程收发数据从而达到流水灯的效果。多线程串口编程主要分为三步,第一部分,连接串口及开发板,确定设备号;第二部分为串口参数的设置;第三部分为
    发表于 01-07 08:08

    Win32多线程同步技术浅析

    简要介绍了在Win32环境下多线程访问共享资源时的同步机制,讨论了主要的4种同步对象(临界区、互斥元、事件、信号量),并描述了它们的优缺点,给出了使用Win32 API函数操控这4种对
    发表于 11-14 10:55 31次下载
    Win32<b class='flag-5'>多线程</b><b class='flag-5'>同步</b>技术浅析

    多线程与聊天室程序的创建

    多线程程序的编写,多线程应用中容易出现的问题。互斥对象的讲解,如何采用互斥对象来实现多线程同步
    发表于 05-16 15:22 0次下载

    linux多线程机制-线程同步

    ,线程调度、同步互斥都需要用户程序自己完成。内核级线程需要内核参与,由内核完成线 程调度并提供相应的系统调用,用户程序可以通过这些接口函数对线程
    发表于 04-02 14:42 343次阅读

    Linux多线程同步互斥量Mutex详解

    嵌入式linux中文站向各位爱好者介绍linux常见同步方式互斥量Mutex的使用方法1. 初始化:在Linux下,
    发表于 04-02 14:45 232次阅读

    Linux 多线程互斥互斥

    同步问题, 线程同步的思路: 让多个线程依次访问共享资源,而不是并行互斥VS同步
    发表于 04-02 14:47 201次阅读

    Linux多线程同步

    操作中。 多线程同步对于多线程程序来说,同步(synchronization)是指在一定的时间内只允许某一个线程访问某个资源 。而在此时间内
    发表于 04-02 14:47 329次阅读

    三种Linux中的常用多线程同步方式浅析

    嵌入式linux中文站给大家介绍三种Linux中的常用多线程同步方式:互斥量,条件变量,信号量。
    发表于 05-02 14:49 2897次阅读
    三种<b class='flag-5'>Linux</b>中的常用<b class='flag-5'>多线程</b><b class='flag-5'>同步</b>方式浅析

    Linux多线程编程的知识点

    Hello、Hello大家好,我是木荣,今天我们继续来聊一聊Linux多线程编程中的重要知识点,详细谈谈多线程同步
    发表于 04-26 17:27 482次阅读
    <b class='flag-5'>Linux</b>中<b class='flag-5'>多线程</b>编程的知识点

    多线程同步的几种方法

    多线程同步是指在多个线程并发执行的情况下,为了保证线程执行的正确性和一致性,需要采用特定的方法来协调线程之间的执行顺序和共享资源的访问。下面
    的头像 发表于 11-17 14:16 527次阅读

    多线程如何保证数据的同步

    。本文将详细介绍多线程数据同步的概念、问题、以及常见的解决方案。 一、多线程数据同步概念 在多线程
    的头像 发表于 11-17 14:22 354次阅读