IO:线程的同步互斥

news2024/11/24 14:02:53

一、引入

例:

要求定义一个全局变量 char buf[] = "1234567",创建两个线程,不考虑退出条件。

A线程循环打印buf字符串,

B线程循环倒置buf字符串,即buf中本来存储1234567,倒置后buf中存储7654321. 不打印!!

倒置不允许使用辅助数组。

要求A线程打印出来的结果只能为 1234567 或者 7654321 不允许出现7634521 7234567

不允许使用sleep函数

#include <head.h>

char buf[] = "1234567";

void *callback1(void *arg)
{
    while (1)
    {
        printf("%s\n", buf);
    }
    pthread_exit(NULL);
}
void *callback2(void *arg)
{
    char temp = 0;
    while (1)
    {
        for (int i = 0; i < strlen(buf) / 2; i++)
        {
            temp = buf[i];
            buf[i] = buf[strlen(buf) - i - 1];
            buf[strlen(buf) - i - 1] = temp;
        }
    }
}

int main(int argc, char const *argv[])
{
    pthread_t tid1, tid2;
    if (pthread_create(&tid1, NULL, callback1, NULL) != 0)
    {
        printf("创建失败\n");
        return -1;
    }
    // pthread_detach(tid1);

    if (pthread_create(&tid2, NULL, callback2, NULL) != 0)
    {
        printf("创建失败\n");
        return -1;
    }
    // pthread_detach(tid2);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

很明显输出的字符不符合规范,所以我们要加入一个标志位flag

#include <head.h>

char buf[] = "1234567";
int flag = 0;
void *callback1(void *arg)
{
    while (1)
    {
        if (flag == 0)
        {
            printf("%s\n", buf);
            flag = 1;
        }
    }
    pthread_exit(NULL);
}
void *callback2(void *arg)
{
    char temp = 0;
    while (1)
    {
        if (flag == 1)
        {
            for (int i = 0; i < strlen(buf) / 2; i++)
            {
                temp = buf[i];
                buf[i] = buf[strlen(buf) - i - 1];
                buf[strlen(buf) - i - 1] = temp;
            }
            flag = 0;
        }
    }
}

int main(int argc, char const *argv[])
{
    pthread_t tid1, tid2;
    if (pthread_create(&tid1, NULL, callback1, NULL) != 0)
    {
        printf("创建失败\n");
        return -1;
    }
    // pthread_detach(tid1);

    if (pthread_create(&tid2, NULL, callback2, NULL) != 0)
    {
        printf("创建失败\n");
        return -1;
    }
    // pthread_detach(tid2);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

二、线程同步互斥的概念

互斥:指某个资源同一时间只允许一个访问者对其进行访问,具有唯一性。互斥机制无法限制访问者的访问顺序,即访问者的访问顺序是无序的,如互斥锁

同步:在互斥的基础上,使用别的手段实现访问者的有序访问,如条件变量

三、线程同步互斥的实现

线程之间如果要进行通信,需要引入同步互斥机制,避免产生竞态,保证在同一时刻,只有一个线程在处理临界资源。

由于flag的效率太低,当flag的条件不满足时,程序会在while循环内白白浪费时间,所以我们引入以下同步互斥机制。

2.1互斥锁

原理:

1.访问临界资源之前要先申请互斥锁

2.申请上锁,进入临界区访问临界资源,退出临界区,解开锁

3.如果申请上锁失败,说明互斥锁被别的线程占用,该线程进入休眠,等待互斥锁解开

4.互斥锁只能保证临界区完整,只有一个线程访问,但无法制定访问者的顺序

#include <head.h>

char buf[] = "1234567";

// 互斥锁
pthread_mutex_t mutex; // 放在全局可以让所有函数访问到

void *callback1(void *arg)
{
    while (1)
    {
        // 上锁
        pthread_mutex_lock(&mutex);

        printf("%s\n", buf);

        // 解锁
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(NULL);
}
void *callback2(void *arg)
{
    char temp = 0;
    while (1)
    {
        // 上锁
        pthread_mutex_lock(&mutex);

        for (int i = 0; i < strlen(buf) / 2; i++)
        {
            temp = buf[i];
            buf[i] = buf[strlen(buf) - i - 1];
            buf[strlen(buf) - i - 1] = temp;
        }
        // 解锁
        pthread_mutex_unlock(&mutex);
    }
}

int main(int argc, char const *argv[])
{
    // 申请一个互斥锁
    pthread_mutex_init(&mutex, NULL);//null代表互斥锁属性为默认

    pthread_t tid1, tid2;
    if (pthread_create(&tid1, NULL, callback1, NULL) != 0)
    {
        printf("创建失败\n");
        return -1;
    }

    if (pthread_create(&tid2, NULL, callback2, NULL) != 0)
    {
        printf("创建失败\n");
        return -1;
    }
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    // 销毁锁
    pthread_mutex_destroy(&mutex);

    return 0;
}

死锁:

拥有锁资源的任务没有释放锁

1.持有互斥锁的线程异常退出,没有释放锁资源

2.同一线程对一把互斥锁重复上锁

3.互斥锁交叉嵌套

2.2信号量(信号灯)

原理:

1.线程要访问临界资源都要去申请信号量

        当信号量的值>0时,申请成功,信号量-1

        当信号量的值=0时,申请失败,该申请操作会阻塞,线程休眠,等待信号量>0

2.互斥锁又被称之为二值信号量,只允许一个线程进入临界区,即信号量的初始值为1

3.信号量根据初始值不同,可以让一个或多个线程同时进入临界区。

4.PV操作:

        p操作:申请信号量,信号量-1

        v操作:释放信号量,信号量+1

#include <head.h>
// 信号量
sem_t sem;
void *callback(void *arg)
{
    sleep(3);

    // V操作
    if (sem_post(&sem) < 0)
    {
        perror("sem_post\n");
        return NULL;
    }
    printf("v操作\n");
    pthread_exit(NULL);
}

int main(int argc, char const *argv[])
{
    if (sem_init(&sem, 0, 2) < 0)
    // 第二个参数:共享标识,0表示用于线程,非0表示用于进程
    // 第三个参数:指定信号量的初始值
    {
        perror("sem_init");
        return -1;
    }
    // 创建一个线程,3s后V一次
    pthread_t tid;
    if (pthread_create(&tid, NULL, callback, NULL) != 0)
    {
        printf("创建失败\n");
        return -1;
    }

    // p操作
    if (sem_wait(&sem) < 0)
    {
        perror("sem_wait");
        return -1;
    }
    printf("p操作\n");
    // p操作
    if (sem_wait(&sem) < 0)
    {
        perror("sem_wait");
        return -1;
    }
    printf("p操作\n");
    // p操作
    if (sem_wait(&sem) < 0)
    {
        perror("sem_wait");
        return -1;
    }
    printf("p操作\n");
    pthread_join(tid, NULL);
    sem_destroy(&sem);
    return 0;
}

2.3条件变量(条件变量需要与互斥锁共用)

原理:

将不访问共享资源的线程直接休眠,并设置一个唤醒条件,当线程需要访问的时候,其他线程通过制定的条件变量唤醒该线程。

#include <head.h>

// 互斥锁
pthread_mutex_t mutex;
// 条件变量
pthread_cond_t cond;

int flag = 0;

void *callback1(void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&mutex);
        if (flag != 0)
        {
            pthread_cond_wait(&cond, &mutex);
            // 设置唤醒条件同时解开互斥锁
            // 休眠,等待被唤醒
        }
        printf("A\n");
        flag = 1;
        pthread_cond_signal(&cond); // 通过条件变量唤醒线程
        // 只需记住调用signal肯定会随机唤醒一个睡在cond上的线程
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(NULL);
}
void *callback2(void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&mutex);
        if (flag != 1)
        {
            pthread_cond_wait(&cond, &mutex);
        }
        printf("B\n");
        flag = 0;
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
    // 创建互斥锁
    pthread_mutex_init(&mutex, NULL);

    // 创建条件变量
    if (pthread_cond_init(&cond, NULL) != 0)
    {
        printf("创建失败");
        return -1;
    }
    // 创建分支线程
    pthread_t tid1, tid2;
    if (pthread_create(&tid1, NULL, callback1, NULL) != 0)
    {
        printf("创建失败");
        return -1;
    }
    if (pthread_create(&tid2, NULL, callback2, NULL) != 0)
    {
        printf("创建失败");
        return -1;
    }
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);
    return 0;
}

练习

例:每次生产3个苹果,线程1每次消耗4个苹果,线程2每次消耗7个苹果

#include <head.h>
int apple = 10;
pthread_mutex_t mutex;
pthread_cond_t cond;
void *callback1(void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&mutex);
        if (apple < 4)
        {
            pthread_cond_wait(&cond, &mutex);
        }
        apple -= 4;
        printf("1#线程消费了4个苹果,现在苹果数量为:%d\n", apple);

        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(NULL);
}
void *callback2(void *arg)
{
    while (1)
    {
        pthread_mutex_lock(&mutex);
        if (apple < 7)
        {
            pthread_cond_wait(&cond, &mutex);
        }
        apple -= 7;
        printf("2#线程消费了7个苹果,现在苹果数量为:%d\n", apple);
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);
    pthread_t tid1, tid2;
    if (pthread_create(&tid1, NULL, callback1, NULL) != 0)
    {
        printf("创建失败\n");
        return -1;
    }
    if (pthread_create(&tid2, NULL, callback2, NULL) != 0)
    {
        printf("创建失败\n");
        return -1;
    }
    while (1)
    {
        apple += 3;
        if (apple >= 7)
        {
            pthread_cond_signal(&cond);
        }
        printf("生产了3个苹果,现在苹果数量为:%d\n", apple);
        sleep(1);
    }
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【计算机毕业设计】基于微信小程序校友会系统的实现

由于APP软件在开发以及运营上面所需成本较高&#xff0c;而用户手机需要安装各种APP软件&#xff0c;因此占用用户过多的手机存储空间&#xff0c;导致用户手机运行缓慢&#xff0c;体验度比较差&#xff0c;进而导致用户会卸载非必要的APP&#xff0c;倒逼 管理者必须改变运营…

2024.5.9 关于 SpringCloud —— Nacos 的安装与配置

目录 Windos 安装步骤 docker 启动 nacos Windos 安装步骤 1&#xff09;点击下方链接&#xff0c;进入并访问 nacos 官网 Nacos官网 | Nacos 官方社区 | Nacos 下载 | Nacos 2&#xff09;按照下图箭头指示下载对应版本的压缩包 3&#xff09;此时我们将得到一个压缩包&…

GDPU JavaWeb 过滤器

再纯净的白开水也过滤不了渣茶。 Servlet登陆页面 引入数据库&#xff0c;创建用户表&#xff0c;包括用户名和密码&#xff1a;客户端通过login.jsp发出登录请求&#xff0c;请求提交到loginServlet处理。如果用户名和密码跟用户表匹配则视为登录成功&#xff0c;跳转到loginS…

【iOS】RunLoop详解(二)

RunLoop详解&#xff08;二&#xff09; RunLoop 的概念RunLoop 与线程的关系RunloopRunloop与线程的关系RunLoop对外的接口Runloop的Mode举例说明小结 RunLoop 的内部逻辑RunLoop的底层实现苹果用RunLoop实现的功能AutoreleasePool事件响应手势识别界面更新定时器PerformSelec…

制作绿色便携式Chrome浏览器

准备环境 chrome离线解压包7zip解压缩软件Chrome Portable便携版启动程序 一、获取Chrome离线解压包 获取官方的离线下载包&#xff0c;使用7zip软件打开压缩包。如果里面看到的事102~表示是离线安装包&#xff0c;如果是chrome.7z表示是离线解压包。 如果是解压包的话&…

Git的安装和配置

一、Git的介绍 代码的一套托管工具&#xff0c;它分为两个仓库&#xff0c;首先将你写的代码提交到本地仓库&#xff0c;这个时候只有你可以看&#xff0c;和你一起开发的同事看不到。将本地仓库的代码推到远程仓库&#xff08;githab、gitee、gitlab等之一&#xff09;&#…

Deckset for Mac:让演示文稿制作更轻松

还在为繁琐的演示文稿制作而烦恼吗&#xff1f;Deckset for Mac来帮您解决&#xff01;它支持Markdown语言&#xff0c;让您只需专注于内容的创作&#xff0c;无需在排版和设计上耗费过多精力。丰富的主题和布局选项&#xff0c;让您能够轻松打造出专业级的演示文稿。快来体验D…

docker 部署SSM项目(包含打包)

一&#xff1a;SSM项目打包 1.这个一定要勾选防止静态资源没打包上 2.第二步 3.第三步 4.更改名字(注意部署到线上的时候这里如果用docker或者window部署的话需要带这个项目名&#xff0c;不然会出现找不到接口的情况) ![在这里插入图片描述](https://img-blog.csdnimg.cn/dir…

CUDA backend requires cuDNN. Please resolve dependency or disable的解决方法

先把 C:\Program Files\NVIDIA\CUDNN\v9.0里面的bin,include,lib文件夹中最里面的文件 复制到 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v12.4中的bin,include,lib文件夹 你的路径或许有点不一样&#xff0c;但大概就是这样 注意&#xff0c;复制完后&#xff0c;文…

网络应用层

叠甲&#xff1a;以下文章主要是依靠我的实际编码学习中总结出来的经验之谈&#xff0c;求逻辑自洽&#xff0c;不能百分百保证正确&#xff0c;有错误、未定义、不合适的内容请尽情指出&#xff01; 文章目录 1.使用协议和序列化1.1.自定义协议&#xff0c;自定义序列化1.2.自…

【python】使用requests爬取网页采集、单词翻译、豆瓣排行、kfc餐厅信息

目录 1.网页采集 2.单词翻译 ​编辑 3.豆瓣排行榜 4.kfs 餐厅信息 实现步骤&#xff1a;&#xff08;1&#xff09;指定url &#xff08;2&#xff09;发起请求 &#xff08;3&#xff09;获取响应数据 &#xff08;4&#xff09;持久化存储 1.网页采集 import requests …

day11-StreamFile

1.Stream流 1.1 体验Stream流 需求&#xff1a;按照下面的要求完成集合的创建和遍历 创建一个集合&#xff0c;存储多个字符串元素 把集合中所有以"杨"开头的元素存储到一个新的集合 把"杨"开头的集合中的长度为3的元素存储到一个新的集合 遍历上一步得到…

【数据结构】图的应用---最小生成树(Prim,Kruskal)、最短路径(BFS,Dijkstra,Floyd)、拓扑排序、关键路径、有向无环图表达式

文章目录 5.图的应用5.1 最小生成树5.1.1 Prim算法5.1.2 Kruskal算法5.1.3 最小生成树代码A.邻接矩阵B.邻接表 5.2 最短路径5.2.1 BFS5.2.2 Dijkstra5.2.3 Floyd5.2.4 三种算法的比较 5.3 有向无环图描述表达式5.4 拓扑排序5.5 关键路径 5.图的应用 5.1 最小生成树 定义 对一个…

uniapp获取当前位置及检测授权状态——支持App、微信小程序

uniapp获取当前位置检测及定位权限——支持App、微信小程序 首先&#xff0c;祝天下母亲&#xff0c;节日快乐~ 文章目录 uniapp获取当前位置检测及定位权限——支持App、微信小程序效果图新增 兼容小程序方法manifest Tips&#xff1a; 上一篇介绍 App端 uniapp获取当前位置及…

分布式与一致性协议之PBFT算法(二)

PBFT算法 如何替换作恶的主节点 虽然PBFT算法可以防止备份节点作恶&#xff0c;因为这个算法是由主节点和备份节点组成的&#xff0c;但是&#xff0c;如果主节点作恶(比如主机点接收到了客户端的请求&#xff0c;但就是默不作声&#xff0c;不执行三阶段协议)&#xff0c;那…

C脚本实现Wincc弹窗重复调用

文章目录 前言一、步骤及解析二、运行画面演示三、总结 前言 在常见的Wincc上位机画面中&#xff0c;点击按钮或控件弹出弹窗&#xff0c;由于不同的弹窗内容不同&#xff0c;变量前缀不同&#xff0c;通常情况下一个弹窗就需要调用一个画面窗口&#xff0c;但画面窗口过多会导…

Redis经典问题:数据不一致

大家好,我是小米,今天我想和大家聊一聊Redis的一个经典问题——数据不一致。在使用Redis的过程中,你是否曾遇到过这样的问题?缓存和数据库中的数据不一致,可能导致应用程序的功能异常。下面,我将详细介绍数据不一致的原因,以及一些有效的解决方案。 什么是数据不一致 …

【muzzik 分享】Cocos 物理帧同步

# 前言 之前没研究帧同步&#xff0c;这是我前端时间没上班时边玩边搞做的 Demo 研究成果&#xff0c;总共时间一周&#xff08;实际2-3天&#xff09;&#xff0c;发布的目的也很简单&#xff0c;打破技术垄断&#xff0c;才能诞生更高端的技术成果。而且就算我没发这篇帖子&…

The 2023 ICPC Asia Hefei Regional Contest

目录 B. Queue Sorting 应该还会再补几题 B. Queue Sorting 题解&#xff1a; Dilworth定理: 【偏序关系与偏序集、Hasse图、极大元、极小元、全序关系、最大元、良序集/三小时讲不完离散数学之集合论/考研复试/期末复习考前冲刺/近世代数/抽象代数】https://www.bilibili.c…

【ARM 嵌入式 C 入门及渐进 16.1 -- C 代码实现CRC32校验函数】

请阅读【嵌入式开发学习必备专栏】 文章目录 CRC32校验函数CRC32 表与函数CRC32 测试函数测试结果 对比测试结果 CRC32校验函数 在C语言中&#xff0c;实现CRC32计算的函数需要一个CRC算法的实现。以下是一个使用查表法实现CRC32的简单例子。这种方法通过预先计算好的CRC表来快…