热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux线程的同步和互斥

目录1、线程的互斥2、可重入VS线程安全3、线程的同步1、线程的互斥

目录

1、线程的互斥

2、可重入VS线程安全

3、线程的同步


1、线程的互斥

1)线程互斥的相关概念

  • 临界资源:被多个执行流共享的资源就称为临界资源,例如全局变量。
  • 临界区:访问临界资源的代码称为临界区。
  • 互斥:互斥保证了任何时刻只有一个线程进入临界区访问临界资源。
  • 原子性:不会被任何机制打断的操作,该操作只有两态,要么已经完成要么还没开始(不能存在已经开始了,但是还没完成的情况,简单理解就是一句汇编代码就可以实现的)。

2)通过订票示例引入互斥量

#include
#include
#include

int tickets = 1000;//票数,每订购一张票数减1
void* ticket(void* arg)
{
//模拟订票过程,多个线程(执行流)访问该程序
while(1)
{
if(tickets > 0)
{
usleep(500);
printf("%s,抢票成功,剩余票数:%d\n",(char*)arg,--tickets);
}
else
break;
} }

int main()
{
//创建多个线程
pthread_t tid[5];
pthread_create(&tid[0],NULL,ticket,"new thread1");
pthread_create(&tid[1],NULL,ticket,"new thread2");
pthread_create(&tid[2],NULL,ticket,"new thread3");
pthread_create(&tid[3],NULL,ticket,"new thread4");
pthread_create(&tid[4],NULL,ticket,"new thread5");

//线程等待
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
pthread_join(tid[2],NULL);
pthread_join(tid[3],NULL);
pthread_join(tid[4],NULL);
return 0;
}

运行结果分析:

第一个原因:当一个执行流执行到if判断为真以后,首先会usleep(500),在这期间ticket还没有进行--操作,因此ticket还是大于0的,如果这时再有其他执行流执行if还是判断为真,然后等待一段时间多个执行流对ticket(此时值为0)进行--就变成了负值。

第二个原因:ticket--本身就不是原子性的,汇编代码如下:

如何解决这个问题?

  • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区执行
  • 如果多个线程同时要进入临界区执行,且当前临界区没有线程在执行时,只允许一个线程进入该临界区进行执行。
  • 如果线程不在临界区执行,或者已经从临界区执行完了,则不能阻止其他线程进入临界区执行。

满足这些条件的实际上就是一把锁,Linux中提供的锁叫做互斥量。注意:要保证加锁后其他线程不能进入临界区,则其他线程必须能够看到锁,也就是说锁也是临界资源(多个线程都可以访问),因此锁在保证临界区前必须先保证自己的安全,也就是说加锁的过程必须是原子性的(保证了多个线程进入该临界区时只允许一个进入)。

3)互斥量的相关接口

初始化互斥量

方法1:静态分配(全局变量)

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER ;

方法2:动态分配

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

         参数:mutex需要初始化的互斥量    attr暂不关注,置NULL即可

         返回值:成功返回0,失败返回错误码

注意:如果使用方法1初始化的互斥量不需要销毁

销毁互斥量

int  pthread_mutex_destroy(pthread_mutex_t *restrict mutex);

注意:不要销毁一个已经加锁的互斥量;已经销毁的互斥量,要确保后面不会在尝试进行加锁。

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

使用互斥量改进售票系统代码

#include
#include
#include

pthread_mutex_t mutex;//初始化互斥量
int tickets = 10000;//票数,每订购一张票数减1

void* ticket(void* arg)
{
//模拟订票过程,多个线程(执行流)访问该程序
while(1)
{
pthread_mutex_lock(&mutex);//加锁
if(tickets > 0)
{
printf("%s,抢票成功,剩余票数:%d\n",(char*)arg,--tickets);
pthread_mutex_unlock(&mutex);//解锁
usleep(500);
}
else
{
pthread_mutex_unlock(&mutex);
break;
}
}
}

int main()
{
//创建多个线程
pthread_t tid[5];
pthread_create(&tid[0],NULL,ticket,"new thread1");
pthread_create(&tid[1],NULL,ticket,"new thread2");
pthread_create(&tid[2],NULL,ticket,"new thread3");
pthread_create(&tid[3],NULL,ticket,"new thread4");
pthread_create(&tid[4],NULL,ticket,"new thread5");

//线程等待
pthread_join(tid[0],NULL);
pthread_join(tid[1],NULL);
pthread_join(tid[2],NULL);
pthread_join(tid[3],NULL);
pthread_join(tid[4],NULL);

//销毁互斥量
pthread_mutex_destroy(&mutex);
return 0;
}

4)互斥量原理

语言层面单纯的i++/++i等并不是具有原子性,可能会有数据一致性问题。为了实现互斥锁操作,大多数体系结构都实现了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相互交换,由于只有一跳指令,保证了原子性。

2、可重入VS线程安全

1)相关概念

  • 线程安全:多个线程并发执行同一段代码不会出现不同结果。常见对全局变量或静态变量进行操作,并且没有锁保护的情况下,会出现该问题。
  • 可重入:同一个函数被不同的执行流执行,当一个执行流还没有结束,其他执行流就再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或任何问题的情况下,我们称之为可重入。反之,称之为不可重入。

2)常见线程不安全的情况

  • 不保护共享变量的函数

  • 函数状态随着被调用,状态发生变化的函数

  • 返回指向静态变量指针的函数

  • 调用线程不安全函数的函数

3)常见的线程安全的情况

  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的

  • 类或者接口对于线程来说都是原子操作

  • 多个线程之间的切换不会导致该接口的执行结果存在二义性

4)常见不可重入的情况

  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的

  • 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构

  • 可重入函数体内使用了静态的数据结构

5)常见可重入的情况

  • 不使用全局变量或静态变量

  • 不使用用malloc或者new开辟出的空间

  • 不调用不可重入函数

  • 不返回静态或全局数据,所有数据都有函数的调用者提供

  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

6)可重入与线程安全的区别与联系

  • 函数是可重入的,那就是线程安全的

  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题

  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

  • 可重入函数是线程安全函数的一种

  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。

  • 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的

3、线程的同步

1)常见锁的概念

死锁是指在在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程占用的不会释放的资源而处于一种永久等待的状态。

死锁的四个必要条件

  • 互斥条件:一个资源一次只能被一个执行流使用。
  • 请求与保持条件:一个执行流因请求资源而等待时,对已申请到的资源保持不放。
  • 不剥夺条件:一个执行流已获得的资源,在未使用完之前不能强行剥夺。
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系。

死锁的避免

  • 破坏死锁的四个条件
  • 加锁顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配

死锁的避免算法

  • 死锁检测算法
  • 银行家算法

3)Linux线程同步相关概念

  • 条件变量:当一个线程互斥地访问某个变量时,在其它线程改变状态之前,它什么也做不了。例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量
  • 同步概念:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。
  • 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。

4)什么是线程同步?为什么要存在同步?

在保证线程安全的条件下,让多个执行流按照特定的顺序访问临界资源,我们称之为同步。

线程同步保证了多个线程协同完成任务的安全性和高效性。

5)线程同步相关接口

初始化条件变量

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

        参数:cond要初始化的条件变量   attr:暂不关注,置空即可

        返回值:成功返回0,失败返回错误码

销毁条件变量

int pthread_cond_desroy(pthread_cond_t *restrict cond);//销毁条件变量

等待条件满足

int  pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);

       参数:cond等待的条件变量      mutex:互斥量

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);

int pthread_cond_signal(pthread_cond_t *cond);

#include
#include
#include

pthread_mutex_t mutex;
pthread_cond_t cond;

void* fun1(void* arg)
{
while(1)
{
sleep(1);//每隔一秒发送一个信号
pthread_cond_signal(&cond);
}
}

void* fun2(void* arg)
{
while(1)
{
//等待,当接收到信号就会执行下面输出语句。
pthread_cond_wait(&cond,&mutex);
printf("开始行动\n");
}
}

int main()
{
//初始化互斥量和条件变量
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
//创建两个线程,一个发送信号,另一个接收到信号打印“活动”
pthread_t t1,t2;
pthread_create(&t1,NULL,fun1,NULL);
pthread_create(&t2,NULL,fun2,NULL);

//线程等待
pthread_join(t1,NULL);
pthread_join(t2,NULL);

//销毁互斥量和条件变量
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}

6)为什么pthread_cond_wait需要互斥量

  • 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足时一直等下去条件都不会满足。所以必须要有一个线程通过某些操作,改变共享变量,使得原先不满足的条件变得满足并去通知等待的线程。
  • 条件变量的满足必然会牵扯到共享数据的变化,所以必须有互斥锁的保护,没有互斥锁就无法保证线程安全的获取修改条件变量。
  • 当一个线程发现条件不满足时,就要调用wait将自己挂起等待,挂起等待时是带锁等待的!!!如果不解锁,其他线程无法访问条件变量,条件永远也不会成立,该线程将一直等待下去。因此,这里的互斥量起到解锁的作用。


推荐阅读
  • 在操作系统中,阻塞状态与挂起状态有着显著的区别。阻塞状态通常是指进程因等待某一事件(如I/O操作完成)而暂时停止执行,而挂起状态则是指进程被系统暂时移出内存,以释放资源或降低系统负载。此外,本文还深入分析了`sleep()`函数的实现机制,探讨了其在不同操作系统中的具体实现方式及其对进程调度的影响。通过这些分析,读者可以更好地理解操作系统如何管理进程的不同状态以及`sleep()`函数在其中的作用。 ... [详细]
  • 本文详细探讨了Zebra路由软件中的线程机制及其实际应用。通过对Zebra线程模型的深入分析,揭示了其在高效处理网络路由任务中的关键作用。文章还介绍了线程同步与通信机制,以及如何通过优化线程管理提升系统性能。此外,结合具体应用场景,展示了Zebra线程机制在复杂网络环境下的优势和灵活性。 ... [详细]
  • 在 Linux 环境下,多线程编程是实现高效并发处理的重要技术。本文通过具体的实战案例,详细分析了多线程编程的关键技术和常见问题。文章首先介绍了多线程的基本概念和创建方法,然后通过实例代码展示了如何使用 pthreads 库进行线程同步和通信。此外,还探讨了多线程程序中的性能优化技巧和调试方法,为开发者提供了宝贵的实践经验。 ... [详细]
  • 本文详细介绍了如何在PHP中使用Memcached进行数据缓存,包括服务器连接、数据操作、高级功能等。 ... [详细]
  • 欢迎来到Netgen新时代:探索网络生成技术的无限可能
    欢迎进入Netgen的新时代:探索网络生成技术的无限潜力。本文将详细介绍如何编译下载的Netgen源代码,生成Netgen程序,并提供开发所需的库nglib。此外,还将探讨Netgen在现代网络设计与仿真中的应用前景,以及其在提高网络性能和可靠性方面的关键作用。 ... [详细]
  • Python Selenium WebDriver 浏览器驱动详解与实践
    本文详细介绍了如何使用Python结合Selenium和unittest构建自动化测试框架,重点解析了WebDriver浏览器驱动的配置与使用方法,涵盖Chrome、Firefox、IE/Edge等主流浏览器。 ... [详细]
  • 来自FallDream的博客,未经允许,请勿转载,谢谢。一天一套noi简直了.昨天勉强做完了noi2011今天教练又丢出来一套noi ... [详细]
  • 本文介绍了如何使用Java编程语言实现凯撒密码的加密与解密功能。凯撒密码是一种替换式密码,通过将字母表中的每个字母向前或向后移动固定数量的位置来实现加密。 ... [详细]
  • 本文详细介绍了如何使用Linux下的mysqlshow命令来查询MySQL数据库的相关信息,包括数据库、表以及字段的详情。通过本文的学习,读者可以掌握mysqlshow命令的基本语法及其常用选项。 ... [详细]
  • 基于Linux开源VOIP系统LinPhone[四]
    ****************************************************************************************** ... [详细]
  • 在MFC框架中,存在多个全局函数,用于在不同对象间获取信息或创建新对象。其中,`afxGetApp`函数尤为关键,它能够帮助开发者轻松获取当前应用程序的实例指针。本文将详细解析`afxGetApp`函数的内部机制及其在MFC应用程序中的具体应用场景,探讨其在提升代码可维护性和灵活性方面的优势。此外,还将介绍其他常用全局函数如`AfxWinInit()`和`AfxBeginThread()`的功能和使用方法,为开发者提供全面的参考。 ... [详细]
  • 在Linux系统中,为了提高安全性,可以通过设置命令执行超时和用户超时注销来防止因用户长时间未操作而带来的安全隐患。具体而言,可以通过编辑 `/etc/profile` 文件,添加或修改相关参数,确保用户在指定时间内无操作后自动注销。此外,还可以利用 `timeout` 命令来限制特定命令的执行时间,进一步增强系统的稳定性和安全性。 ... [详细]
  • 本文深入探讨了IO复用技术的原理与实现,重点分析了其在解决C10K问题中的关键作用。IO复用技术允许单个进程同时管理多个IO对象,如文件、套接字和管道等,通过系统调用如`select`、`poll`和`epoll`,高效地处理大量并发连接。文章详细介绍了这些技术的工作机制,并结合实际案例,展示了它们在高并发场景下的应用效果。 ... [详细]
  • ANR一般有三种类型: ... [详细]
  • ubuntu下基于c++的opencv学习
    一、环境配置1、安装opencv2、makefile编写makefile模板,与c文件在同一个目录下,用make指令生成可执行文件,然后运 ... [详细]
author-avatar
小荷蛋蛋图_945
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有