IO:线程的同步互斥

一、引入

例:

要求定义一个全局变量 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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

创新指南|设计冲刺 – 更快找到成功的创新方案

“ 设计冲刺&#xff08;Design Sprint&#xff09;” 一词与跑步无关&#xff0c;而且不局限于设计&#xff0c;它与引导团队加速创新密切相关。设计冲刺旨在帮助创新团队在很短的时间内解决一个极有价值的问题。本文将深入解析这一法宝&#xff1a;设计冲刺是什么&#xff1f…

Electron | 桌面应用的开发神器

初探 Electron 教程将介绍 Electron 打包应用的全过程&#xff0c;从本地测试&#xff0c;打包&#xff0c;到 GitHub 自动化。讲解 Electron Forge 和 Electron Builder 的用法&#xff0c;以及如何在 GitHub Actions 中自动化生成和发布应用。 官方资源 Electron Document…

GDPU unity游戏开发 角色控制器与射线检测

在你的生活中&#xff0c;你一直扮演着你的角色&#xff0c;别被谁控制了。 小试 1. 创建一个角色控制器&#xff0c;通过键盘控制角色控制器的移动&#xff0c;角色控制器与家具发生碰撞后&#xff0c;通过Debug语句打印出被碰撞物体的信息(搜索OnControllerColliderHit的使用…

windows系统安装Ubuntu子系统

安装前先在 控制面板 中打开 程序与功能选项 &#xff0c;点击 启用或关闭Windows功能&#xff1a; 勾选 适用于 Linux的Windows子系统 和 虚拟机平台 、 Hyper-v 。 重启电脑后再 Microsoft Store Windows应用商店 中下载合适的Ubuntu版本。 运行Ubuntu程序&#xff0c;如出现…

picoCTF-Web Exploitation-Most Cookies

Description Alright, enough of using my own encryption. Flask session cookies should be plenty secure! server.py http://mercury.picoctf.net:53700/ Hints How secure is a flask cookie? 我们先下载server.py&#xff0c;分析逻辑 from flask import Flask, render…

2024年,Web开发新趋势!

随着我们迈入新的一年&#xff0c;现在正是审视2024年网页开发领域开始流行哪些趋势的绝佳时机。回顾2023年的一系列更新&#xff0c;以下是来年一些热门话题的概览。 自主托管有回归的趋势 近些年&#xff0c;自主托管一直是网页开发者和公司托管其应用程序的默认方式。开发…

绿盟之旅——一段安全实习结束

去年&#xff0c;因为着急找实习&#xff0c;拿着简历就开始海投&#xff0c;当时想的是有人让我去就谢天谢地了&#xff0c;第一个约我面试的就是绿盟&#xff0c;也很顺利的通过了面试&#xff0c;当时让我选择在上海还是北京&#xff0c;我选择的是上海&#xff0c;因为学校…

体验GM CHM Reader Pro,享受高效阅读

还在为CHM文档的阅读而烦恼吗&#xff1f;试试GM CHM Reader Pro for Mac吧&#xff01;它拥有强大的功能和出色的性能&#xff0c;能够让你轻松打开和阅读CHM文件&#xff0c;享受高效、舒适的阅读体验。无论是学习、工作还是娱乐&#xff0c;GM CHM Reader Pro都能成为你的得…

探索数据结构(让数据结构不再成为幻想)

目录 什么是数据结构 数据与结构 什么是算法 复杂度分析 时间复杂度与空间复杂度 时间复杂度 思考&#xff1a; 空间复杂度 常数阶O(1) 对数阶O(logn) 线性阶O(n) 以下为空间复杂度为O(n) 线性对数阶O(nlogn) 平方阶O(n) 指数阶O(2^n) 什么是数据结构 数据结构…

OpenCompass 大模型评测实战学习笔记

大模型开源开放评测体系 “司南” (OpenCompass2.0)&#xff0c;用于为大语言模型、多模态模型等提供一站式评测服务。其主要特点如下&#xff1a; 开源可复现&#xff1a;提供公平、公开、可复现的大模型评测方案 全面的能力维度&#xff1a;五大维度设计&#xff0c;提供 70…

AR系列路由器配置VLAN间通信

AR路由器是华为公司推出的企业级路由器产品系列&#xff0c;具有高可靠性、高性能和易管理等特点。AR 系列路由器提供的功能包括路由转发、安全接入、语音、视频、无线等多种业务&#xff0c;支持各种接入方式和协议&#xff0c;并且可以方便地进行扩展和升级。 实验拓扑图&…

动态路由-链路状态路由协议ospf案例

实验拓扑和要求如图 ospf实验 1.设置各个接口地址 2.测试ar5到ar6的连通性 3.配置ospf协议&#xff0c;routerid&#xff0c;area&#xff0c; 详细的网络信息&#xff0c;等待网络收敛后&#xff0c; 查看ospf信息&#xff0c;路由表信息&#xff0c;再次测试连通性 注意区域…

React 第二十七章 Hook useCallback

useCallback 是 React 提供的一个 Hook 函数&#xff0c;用于优化性能。它的作用是返回一个记忆化的函数&#xff0c;当依赖发生变化时&#xff0c;才会重新创建并返回新的函数。 在 React 中&#xff0c;当一个组件重新渲染时&#xff0c;所有的函数都会被重新创建。这可能会…

使用 PXE+Kickstart 批量网络自动装机

前言&#xff1a; 正常安装系统的话使用u盘一个一个安装会非常慢&#xff0c;所以批量安装的技术就出来了。 一、 概念 PXE &#xff08;Preboot eXecute Environment&#xff0c;预启动执行环境&#xff09;是由 Intel 公司开发的技术&#xff0c;可以让计算机通过网络来启动…

Scoop国内安装、国内源配置

安装配置源可参考gitee上的大佬仓库&#xff0c;里面的步骤、代码都很详细&#xff0c;实测速度也很好 glsnames/scoop-installer 也可以结合其它bucket使用 使用Github加速网站&#xff0c;也可以换做其他代理方式&#xff0c;自行测试 例如&#xff1a;https://mirror.ghprox…

【pkuseg】由于网络策略组织下载请求,因此直接在github中下载细分领域模型medicine

【pkuseg】由于网络策略组织下载请求&#xff0c;因此直接在github中下载细分领域模型medicine 写在最前面解决方案pkuseg是什么&#xff1f;报错原因报错详情 &#x1f308;你好呀&#xff01;我是 是Yu欸 &#x1f30c; 2024每日百字篆刻时光&#xff0c;感谢你的陪伴与支持…

OFDM802.11a的FPGA实现(十三)加窗(含verilog和matlab代码)

原文链接&#xff08;相关文章合集&#xff09;&#xff1a;OFDM 802.11a的xilinx FPGA实现 1.前言 添加循环前缀后,对数据还要进行加窗(Windowing)操作。加窗操作可以使OFDM 符号在带宽之外的功率谱密度下降得更快。 2.加窗 对OFDM符号“加窗”意味着令符号周期边缘的幅度…

分形视角观察Linux世界一切皆文件的设计哲学

一切皆文件 我们知道在Linux的世界里&#xff0c;一切皆文件。 而在前面的博客也说过&#xff0c;Linux世界里对文件进行读写、或进行输入/输出&#xff0c;很好地模拟了图灵机模型&#xff0c;所以&#xff0c;它的描述能力是非常强的&#xff01; 图例 常见文件 一切皆…

外观模式详解

外观模式 1 概述 有些人可能炒过股票&#xff0c;但其实大部分人都不太懂&#xff0c;这种没有足够了解证券知识的情况下做股票是很容易亏钱的&#xff0c;刚开始炒股肯定都会想&#xff0c;如果有个懂行的帮帮手就好&#xff0c;其实基金就是个好帮手&#xff0c;支付宝里就…

商场学习之微服务

前言 寒假前在新电脑上配置了java环境&#xff0c;maven仓库&#xff0c;node,js&#xff0c;navicat&#xff0c;MySQL&#xff0c;linux&#xff0c;vmware等环境&#xff0c;创建了6个mysql数据库&#xff0c;77张表。 如此多的表&#xff0c;字段&#xff0c;去手写基础…