【Linux】线程同步与互斥

目录

线程相关问题

线程安全

常见的线程安全的情况

常见的线程不安全的情况

可重入函数与不可重入函数

常见不可重入的情况

常见可重入的情况

可重入与线程安全的关系

联系

区别

线程同步与互斥

互斥锁

使用

死锁

死锁的四个必要条件

如何避免死锁

条件变量

同步概念与竞态条件

使用

Posix 信号量

使用

生产者消费者模型

线程池

读者写者问题


线程相关问题

线程安全

线程安全是指多线程环境下,某个函数、代码块或数据结构在被多个线程并发访问时,能够正确地执行并且保持数据的一致性和完整性,不会出现数据污染、数据竞争或死锁等问题。

常见的线程安全的情况

  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的。
  • 类或者接口对于线程来说都是原子操作。
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性。

常见的线程不安全的情况

  • 不保护共享变量的函数。
  • 函数状态随着被调用,状态发生变化的函数。
  • 返回指向静态变量指针的函数。
  • 调用线程不安全函数的函数。

可重入函数与不可重入函数

若一个函数在调用期间被不同的控制流程调用,在第一次调用未结束前再次调用该函数,这种行为是被允许的,运行结果正确且不会出现任何问题,则称该函数为可重入函数,否则就是不可重入函数。

上图表示链表的多次 insert 操作,在 insert node1后,若在更新 head 前进入信号处理函数并 insert node2,最后的结果就是丢失 node2 的信息,所以 insert 是不可重入函数。

常见不可重入的情况

  • 调用了 malloc/free 函数,因为 malloc 函数是用全局链表来管理堆的。
  • 调用了标准 I/O 库函数,标准 I/O 库的很多实现都以不可重入的方式使用全局数据结构。
  • 可重入函数体内使用了静态的数据结构。

常见可重入的情况

  • 没有使用全局变量或静态变量
  • 没有使用 malloc 或者 new 开辟出空间
  • 没有调用其他不可重入函数
  • 返回值不是静态或全局数据,所有数据都由函数的调用者提供
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

可重入与线程安全的关系

联系

  • 函数如果可重入,那么就是线程安全的。
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题。
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

区别

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

线程同步与互斥

编写代码,创建 4 个线程进行抢票,Thread.hpp 文件为【Linux】线程与线程控制-CSDN博客里模拟实现的线程库。

#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <pthread.h>
#include "Thread.hpp"using namespace zyh;const int num = 4;
int ticket = 10000;void print(std::string name)
{while (ticket > 0){usleep(1001);td::cout << "I am " << name <<  ", ticket: " << --ticket << std::endl;// sleep(1);}
}int main()
{std::vector<Thread<int>> threads;int cnt = 10;// 1. 创建线程for (int i = 0; i < num; i++){std::string name = "thread-" + std::to_string(i + 1);threads.emplace_back(print, name, name);}// 2. 启动线程for (auto& thread : threads){thread.start();}sleep(3);// 3. 等待线程for (auto& thread : threads){thread.join();std::cout << "wait thread done, thread is: " << thread.getThreadName() << std::endl;}return 0;
}

运行结果:

可以看到,我们希望原本票数为 0 时,线程就应该无法进行抢票,但实际上,票数为负数时仍有线程在抢票。

原因:当 ticket 为 0,线程 1 执行完 while (ticket > 0),但还未打印剩余票数时,发生线程调度切换到线程 2,此时 ticket 仍为 0,线程 2 会打印剩余票数并使剩余票数减 1,执行完毕后 ticket < 0,切换回线程 1 后继续执行剩余的打印代码,这时打印出来的结果就是负数。

解决方案:出现上述问题,归根结底的原因是 ticket 是临界资源,多线程在对临界资源的修改与访问并不是原子化的,使用互斥锁就可以解决问题。

#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <pthread.h>
#include "Thread.hpp"using namespace zyh;const int num = 4;
int ticket = 10000;
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;void print(std::string name)
{while (ticket > 0){pthread_mutex_lock(&mtx);//usleep(1001);if (ticket > 0){std::cout << "I am " << name <<  ", ticket: " << --ticket << std::endl;// sleep(1);}pthread_mutex_unlock(&mtx);}
}int main()
{std::vector<Thread<std::string>> threads;int cnt = 10;// 1. 创建线程for (int i = 0; i < num; i++){std::string name = "thread-" + std::to_string(i + 1);threads.emplace_back(print, name, name);}// 2. 启动线程for (auto& thread : threads){thread.start();}sleep(3);// 3. 等待线程for (auto& thread : threads){thread.join();std::cout << "wait thread done, thread is: " << thread.getThreadName() << std::endl;}return 0;
}

互斥锁

如文章【Linux】进程间通信 —— 管道与 System V 版本通信方式-CSDN博客 信号量部分所述,信号量本质上是一个描述临界资源数量的计数器,对公共局部临界资源的预定机制,用来保护临界资源。如果信号量初始值是 1 呢?代表将临界资源看作整体,来实现互斥,二元信号量就是一把锁。

伪代码如下:

使用

 ubuntu 下可能会出现 man 手册查不到 pthread 相关库函数的问题

原因:因为man手册中默认没有安装关于 posix 标准的文档。

解决办法:bash 输入以下内容

sudo apt-get install manpages-posix-dev

创建与销毁

原型

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

        int pthread_mutex_destroy(pthread_mutex_t *mutex);

若 mutex 为静态或全局变量,则可以用宏来初始化,后续不用 destroy
        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

加锁与解锁

原型

        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 <iostream>
#include <pthread.h>pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;int main()
{pthread_mutex_lock(&mtx);std::cout << "Lock once..." << std::endl;pthread_mutex_lock(&mtx);std::cout << "Deadlock generated." << std::endl;pthread_mutex_unlock(&mtx);return 0;
}

另一份死锁代码:

#include <iostream>
#include <pthread.h>pthread_mutex_t mtx1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mtx2 = PTHREAD_MUTEX_INITIALIZER;void* handler1(void* arg)
{pthread_mutex_lock(&mtx1);std::cout << "Lock mtx1." << std::endl;pthread_mutex_lock(&mtx2);std::cout << "Lock mtx1 and mtx2." << std::endl;pthread_mutex_unlock(&mtx2);std::cout << "Unlock mtx2." << std::endl;pthread_mutex_unlock(&mtx1);std::cout << "Unlock mtx1." << std::endl;return nullptr;
}void* handler2(void* arg)
{pthread_mutex_lock(&mtx2);std::cout << "Lock mtx2." << std::endl;pthread_mutex_lock(&mtx1);std::cout << "Lock mtx1 and mtx2." << std::endl;pthread_mutex_unlock(&mtx1);std::cout << "Unlock mtx1." << std::endl;pthread_mutex_unlock(&mtx2);std::cout << "Unlock mtx2." << std::endl;return nullptr;
}int main()
{pthread_t tid1, tid2;pthread_create(&tid1, nullptr, handler1, nullptr);pthread_create(&tid2, nullptr, handler2, nullptr);pthread_join(tid1, nullptr);pthread_join(tid2, nullptr);return 0;
}

死锁的四个必要条件

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

如何避免死锁

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

条件变量

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。

例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

同步概念与竞态条件

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。

竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。

使用

创建与销毁

原型        

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

        int pthread_cond_destroy(pthread_cond_t *cond);

若 cond 为静态或全局变量,则可以用宏来初始化,后续不用 destroy

        pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

唤醒与等待

原型

        int pthread_cond_signal(pthread_cond_t *cond);

        int pthread_cond_broadcast(pthread_cond_t *cond);

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

Posix 信号量

Posix 信号量和 System V 信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但 Posix 可以用于线程间同步。

相关概念可查看【Linux】进程间通信 —— 管道与 System V 版本通信方式-CSDN博客 信号量部分。

使用

创建与销毁

原型  

        int sem_init(sem_t *sem, int pshared, unsigned int value);

        int sem_destroy(sem_t *sem);

参数:
        pshared: 0 表示线程间共享,非 0 表示进程间共享
        value:信号量初始值

P 操作

原型

        int sem_wait(sem_t *sem);

        int sem_trywait(sem_t *sem);

V 操作

原型

        int sem_post(sem_t *sem);

生产者消费者模型

这里会放一个超链接,待更新。

线程池

这里会放一个超链接,待更新。

读者写者问题

这里会放一个超链接,待更新。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/398043.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity读取Android外部文件

最近近到个小需求,需要读Android件夹中的图片.在这里做一个记录. 首先读写部分,这里以图片为例子: 一读写部分 写入部分: 需要注意的是因为只有这个地址支持外部读写,所以这里用到的地址都以 :Application.persistentDataPath为地址起始. private Texture2D __CaptureCamera…

【JavaEE】初步认识多线程

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【Java】登神长阶 史诗般的Java成神之路 &#x1f3b7; 一.线程 1.概念 线程&#xff08;Thread&#xff09;是在计算机科学中&#xff0c;特别是操作系统领域里的一个关键概念。它是操作系统能够进行…

Android中的Binder

binder是Android平台的一种跨进程通信&#xff08;IPC&#xff09;机制&#xff0c;从应用层角度来说&#xff0c;binder是客户端和服务端进行通信的媒介。 ipc原理 ipc通信指的是两个进程之间交换数据&#xff0c;如图中的client进程和server进程。 Android为每个进程提供了…

【聚类算法】

聚类算法是一种无监督学习方法&#xff0c;用于将数据集中的数据点自动分组到不同的类别中&#xff0c;这些类别也称为“簇”或“群”。聚类的目标是让同一簇内的数据点尽可能相似&#xff0c;而不同簇之间的数据点尽可能不相似。聚类算法广泛应用于多种领域&#xff0c;如数据…

xtrabackup搭建MySQL 8.0 主从复制

xtrabackup搭建MySQL 8.0 主从复制 安装MySQL 8.0.37安装xtrabackupGTIDs初始化从库参考&#xff1a;GTID概述GTID相较与传统复制的优势GTID自身存在哪些限制GTID工作原理简单介绍如何开启GTID复制GTID与传统模式建立复制时候语句的不同点传统复制GTID复制 GTID同步状态简单解析…

Linux系统编程 day09 线程同步

Linux系统编程 day09 线程同步 1.互斥锁2.死锁3.读写锁4.条件变量&#xff08;生产者消费者模型&#xff09;5.信号量 1.互斥锁 互斥锁是一种同步机制&#xff0c;用于控制多个线程对共享资源的访问&#xff0c;确保在同一时间只有一个线程可以访问特定的资源或执行特定的操作…

机器学习第一课

1.背景 有监督学习&#xff1a;有标签&#xff08;连续变量&#xff08;回归问题&#xff1a;时间序列等&#xff09;、分类变量&#xff08;分类&#xff09;&#xff09; 无监督学习&#xff1a;没有标签&#xff08;聚类、关联&#xff08;相关性分析&#xff1a;哪些相关…

代码随想录算法训练营Day35 | 01背包问题 | 416. 分割等和子集

今日任务 01背包问题 题目链接&#xff1a; https://kamacoder.com/problempage.php?pid1046题目描述&#xff1a; Code #include <iostream> #include <vector> #include <functional> #include <algorithm>using namespace std;int main(void)…

工作随记:我在OL8.8部署oracle rac遇到的问题

文章目录 一、安装篇问题1&#xff1a;[INS-08101] Unexpected error while executing the action at state:supportedosCheck问题1解决办法&#xff1a;问题2&#xff1a;[INS-06003] Failed to setup passwordless SSH connectivity with thefollowing nodeis): [xxxx1, xxxx…

go语言后端开发学习(四) —— 在go项目中使用Zap日志库

一.前言 在之前的文章中我们已经介绍过如何使用logrus包来作为我们在gin框架中使用的日志中间件&#xff0c;而今天我们要介绍的就是我们如何在go项目中如何集成Zap来作为日志中间件 二.Zap的安装与快速使用 和安装其他第三方包没什么区别&#xff0c;我们下载Zap包只需要执…

pod详解 list-watch机制 预选优选策略 如何指定节点调度pod

K8S是通过 list-watch 机制实现每个组件的协同工作 controller-manager、scheduler、kubelet 通过 list-watch 机制监听 apiserver 发出的事件&#xff0c;apiserver 也会监听 etcd 发出的事件 scheduler的调度策略&#xff1a; 预选策略&#xff08;Predicates&#xff09;…

Pytorch_cuda版本的在线安装命令

pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 运行效果如下&#xff1a; 这个方法是直接从pytorch官网进行在线下载和安装。 cu121&#xff0c;表示当前您安装的cuda版本是12.1

【Redis】持久化—RDB和AOF机制

目录 什么是持久化&#xff1f; RDB&#xff08;定期备份&#xff09; 触发机制 bgsave命令的运作流程 RDB文件的处理 RDB文件 RDB的优缺点 AOF&#xff08;实时备份&#xff09; AOF工作流程 AOF 缓冲区同步⽂件策略 AOF重写机制 AOF重写流程 Redis根据持久化文件…

2015款到18款奔驰GLC升级为2021款的HU6主机后,实现了触摸屏人机交互和Carplay功能

奔驰GLC是北京奔驰生产的一款中型SUV。有车主将2015款奔驰GLC升级为2021款的HU6主机后&#xff0c;实现了触摸屏人机交互和Carplay功能。该车主分享了使用体验&#xff1a; • Carplay功能&#xff1a;可以直接在车机大屏幕上显示导航、音乐和电话信息&#xff0c;让用户在开车…

联想SR650更换风扇后提示传感器异常“传感器Phy Presence Set已从正常状态转换至非紧急状态”

服务器型号&#xff1a;联想ThinkSystem SR650 故障现象&#xff1a;一台联想 ThinkSystem SR650服务器告警&#xff0c;面板和平台同时报风扇故障 告警信息如下图&#xff1a;&#xff08;面板和平台同时告警&#xff09; 接入bmc后查看发现是6号风扇告警 硬件这里已经无法识…

会C++了,想开始接触C#怎么办?|.Net 架构|从C++到C#的入门教学

前言 高质量博客汇总https://blog.csdn.net/yu_cblog/category_12379430.html成熟常用的开发工具和框架https://blog.csdn.net/yu_cblog/category_12737979.htmlDocker从认识到实践再到底层原理https://blog.csdn.net/yu_cblog/category_12424689.html操作系统和计算机网络从入…

[C++] 深入理解面向对象编程特性 : 继承

文章目录 继承的概念与定义继承的定义定义格式不同继承方式与继承的基类中访问限定符间的影响C中的继承和访问控制总结父类的private成员在子类中的访问限制protected成员的使用场景成员访问方式总结继承方式的默认值实际应用中的继承方式 示例代码 OOP中类之间的关系“is a” …

PLSQL导入导出ORACLE数据提示失败问题修改PLSQL配置

oracle中plsql导入提示无法导入问题 1.首先看下是否环境变量已经配置(具体配置看下面环境变量配置) 2.plsql数据导入中tools-->Preferences中配置如下框中的内容 3.设置 tnsnames.ora文件中看下是否设置有问题 4.PLSQL乱码问题 NLS_LANG SIMPLIFIED CHINESE_CHINA.ZHS16…

性能测试工具之JMeter

JMeter Apache JMeter应用程序是开源软件,是一个100%纯Java应用程序,旨在负载测试功能行为和衡量性能。它最初是为测试Web应用程序而设计的,但后来扩展到其他测试功能。 JMeter是一个免费、开源、跨平台的性能测试工具,于20世纪90年代后期面世。这是一个成熟、健全且具有…

湖南(市场调查)源点咨询 构建多元样本是如何改善调研结果的?

湖南&#xff08;市场调研&#xff09;源点咨询认为&#xff0c;大多数市场研究从业者更倾向于从单一数据源获取调研样本&#xff0c;然而在很多情况下&#xff0c;仅从单个数据源很难获得真正具有代表性的样本。 使用多元样本源有两大好处&#xff1a; ①能够捕获不愿加入样…