当前位置: 首页 > news >正文

系统编程day10-同步与互斥

1.同步与互斥

互斥:是指散布在不同任务之间的若干程序片段,当某个任务运行其中一个程序片段时,其他任务就不能运行他们之中的任一程序片段,只能等到该任务运行完成这个程序片段后才可以运行。

同步:是指散布在不同人物之间的若干程序片段,他们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定任务。最基本的任务场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如B任务的运行依赖于A任务产生的数据。

结合后面互斥锁去理解:

最基本的场景就是:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。

互斥:当多个进程或者线程需要使用同一个公共资源的时候,他们不可能同时使用(互斥),需要去抢,谁抢到就是谁的。

同步:当多个进程或者线程需要使用同一个公共资源的时候,他们不可能同时使用,但是不需要抢,按照约定好的顺序依次使用。

无论互斥还是同步,一个公共资源一次只能由一个进程或者线程使用。所以同步本质上也是一种特殊的互斥。

1.1互斥锁

线程里也有这么一把锁,互斥锁(mutex),也叫互斥量,互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即加锁(lock)和解锁(unlock)。

互斥锁的操作流程如下:

  • 在访问共享资源后临界区域前,对互斥锁进行加锁。 谁加上锁,就归谁使用
  • 在访问完成后释放互斥锁导上的锁。
  • 对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。

注意:这里的上锁指的是拥有使用权,而不是把他关起来。

互斥锁的数据类型是: pthread_mutex_t。

1.2互斥锁的初始化

初始化互斥锁:pthread_mutex_init 函数

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutex_attr_t *restrict attr);

功能:

        初始化一个互斥锁。

参数:

        mutex:互斥锁地址。类型是 pthread_mutex_t 。

        attr:设置互斥量的属性,通常可采用默认属性,即可将 attr 设为 NULL

拓展:可以使用宏 PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁,比如:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER   等价于使用 NULL 指定的 attr 参数调用 pthread_mutex_init(&mutex ,NULL) 来完成动态初始化,不同之处在于 PTHREAD_MUTEX_INITIALIZER 宏不进行错误检查

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER  //用宏初始化   
pthread_mutex_init(&mutex ,NULL)  //用函数初始化,两个等价

返回值:

成功:0,成功申请的锁默认是打开的。

失败:非 0 错误码

1.3互斥锁的销毁

pthread_mutex_destroy函数

#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);

功能:

        销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。

参数:

        mutex:互斥锁地址。

返回值:

        成功:0

        失败:非 0 错误码

1.4互斥锁的使用

上锁(lock):

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);

功能:

        对互斥锁上锁,若互斥锁已经上锁,则调用者阻塞,直到互斥锁解锁后再上锁。

参数:

        mutex:互斥锁地址。

返回值:

        成功:0

        失败:非 0 错误码

解锁(unlock):

#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);

功能:

        对互斥锁解锁

参数:

        mutex:互斥锁地址。

返回值:

        成功:0

        失败:非 0 错误码

1.5互斥锁的案例

不加锁的危害:

#include <pthread.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>void show_str(void *str);
void * myfun1(void *argv);
void * myfun2(void *argv);int main(int argc, char const *argv[])
{pthread_t pt1,pt2;pthread_create(&pt1,NULL,myfun1,"hello world");pthread_create(&pt2,NULL,myfun2,"xiazhoujian ");pthread_join(pt1,NULL);pthread_join(pt2,NULL);return 0;
}void show_str(void *str)
{char * p = (char *)str;int i =0;while (*p != '\0'){printf("%c",p[i]);fflush(stdout); //强制刷新输出缓冲区i++;sleep(1);}}void * myfun1(void *argv)
{show_str(argv);return NULL;
}void * myfun2(void *argv)
{show_str(argv);return NULL;}

输出是乱序的。

加锁之后:

#include <pthread.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
pthread_mutex_t mutex;void show_str(void *str);
void *myfun1(void *argv);
void *myfun2(void *argv);int main(int argc, char const *argv[])
{pthread_t pt1, pt2;pthread_mutex_init(&mutex, NULL); // 初始化锁pthread_create(&pt1, NULL, myfun1, "hello world");pthread_create(&pt2, NULL, myfun2, "xiazhoujian ");pthread_join(pt1, NULL);pthread_join(pt2, NULL);pthread_mutex_destroy(&mutex); // 销毁锁return 0;
}void show_str(void *str)
{char *p = (char *)str;int i = 0;while (*p != '\0'){printf("%c", p[i]);fflush(stdout); // 强制刷新输出缓冲区i++;sleep(1);}
}void *myfun1(void *argv)
{pthread_mutex_lock(&mutex); // 上锁show_str(argv);pthread_mutex_unlock(&mutex); // 解锁return NULL;
}void *myfun2(void *argv)
{pthread_mutex_lock(&mutex); // 上锁show_str(argv);pthread_mutex_unlock(&mutex); // 解锁return NULL;
}

注意:实现互斥只需要一把锁

2.死锁

2.1死锁情况

情况1:

线程A抢到资源上锁以后,突然由于某种原因消失,那么就一直未解锁,那么B就无法使用

情况2:

情况3:

解决死锁的方法:

1: 所有进程开始前,必须一次性地申请所需的所有资源,这样运行期间就不会再提出资源要求,破坏了请求条件,即使有一种资源不能满足需求,也不会给它分配正在空闲的资源,这样它就没有资源,就破坏了保持条件,从而预防死锁的发生。

2.允许一个进程只获得初期的资源就开始运行,然后再把运行完的资源释放出来。然后再请求新的资源。

3.条件变量

与互斥锁不同,条件变量是用来等待而不是用来上锁的,条件变量本身不是锁!条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。 条件变量的两个动作: 条件不满, 阻塞线程 当条件满足, 通知阻塞的线程开始工作。

条件变量的类型: pthread_cond_t;

3.1条件变量的API

初始化:

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

功能:

        初始化一个条件变量

参数:

        cond:指向要初始化的条件变量指针。

        attr:条件变量属性,通常为默认值,传 NULL 即可

也可以使用静态初始化的方法:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;等价于:pthread_cond_init(&cond,NULL)

返回值:

        成功:0

        失败:非 0 错误号

销毁:

#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);

功能:

        销毁一个条件变量

参数:

        cond:指向要初始化的条件变量指针

返回值:

        成功:0

        失败:非 0 错误号

等待:

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

功能:
阻塞等待一个条件变量
a) 阻塞等待条件变量 cond(参 1)满足
b) 释放已掌握的互斥锁(解锁互斥量)相当于 pthread_mutex_unlock(&mutex);   a)b)                      两步为一个原子操作(一起执行,没有中间态)。
c) 当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥锁 pthread_mutex_lock(&mutex);
参数:
cond:指向要初始化的条件变量指针
mutex:互斥锁
返回值:
成功:0
失败:非 0 错误号

唤醒所有:

int pthread_cond_broadcast(pthread_cond_t *cond);

功能:

        唤醒全部阻塞在条件变量上的线程

参数:

        cond:指向要初始化的条件变量指针

返回值:

        成功:0

        失败:非 0 错误号

4.读写锁

特点:

读写锁的特点如下:

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

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

规则如下:

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

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

4.1读写锁API:

读写锁的数据类型是: pthread_rwlock_t

初始化pthread_rwlock_init:

#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);

功能:

        用来初始化 rwlock 所指向的读写锁。

参数:

        rwlock:指向要初始化的读写锁指针。

        attr:读写锁的属性指针。如果 attr 为 NULL 则会使用默认的属性初始化读写锁,否则使用指定的 attr 初始化读写锁。

可以使用宏 PTHREAD_RWLOCK_INITIALIZER 静态初始化读写锁,比如:

pthread_rwlock_t my_rwlock = PTHREAD_RWLOCK_INITIALIZER;等价于pthread_rwlock_init(&rwlock,NULL) 来完成动态初始化,不同之处在于 PTHREAD_RWLOCK_INITIALIZER 宏不进行错误检查。

返回值:

        成功:0,读写锁的状态将成为已初始化和已解锁。

        失败:非 0 错误码。

销毁destory:

#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

功能:

        用于销毁一个读写锁,并释放所有相关联的资源(所谓的所有指的是由 pthread_rwlock_init() 自动申请的资源) 。

参数:

        rwlock:读写锁指针。

返回值:

        成功:0

        失败:非 0 错误码

申请读锁:

#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

功能:

        如果调用线程未获取读锁,则它将阻塞直到它获取了该锁。一个线程可以在一个读写锁上多次执行读锁定。(如果有人在写,那么就不能申请读锁,所以会阻塞)

        线程可以成功调用 pthread_rwlock_rdlock() 函数 n 次,但是之后该线程必须调用 pthread_rwlock_unlock() 函数 n。次才能解除锁定。(保持次数一致)

参数:

        rwlock:读写锁指针。

返回值:

        成功:0

        失败:非 0 错误码

写锁:

#include <pthread.h>
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

功能:

        如果调用线程未获取写锁,则它将阻塞直到它获取了该锁。(因为别的线程写的时候,不能申请写锁,所以会阻塞)

参数:

        rwlock:读写锁指针

返回值:

        成功:0

        失败:非 0 错误码

释放:

#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

功能:
无论是读锁或写锁,都可以通过此函数解锁。
参数:
rwlock:读写锁指针。
返回值:
成功:0
失败:非 0 错误码

这里我们的系统编程就完结了,下周开始网络编程。

接上篇,为了庆祝粉丝过100,恭喜下面这个小伙伴喜提奶茶一杯。


文章转载自:

http://Piq2EqCR.bLdmb.cn
http://0XI5zNsy.bLdmb.cn
http://scciUGUd.bLdmb.cn
http://YXBCwMMB.bLdmb.cn
http://IDHHOnLu.bLdmb.cn
http://1Bhpiplf.bLdmb.cn
http://JIXhQ9Wv.bLdmb.cn
http://pWODiiEd.bLdmb.cn
http://QTSUzpw1.bLdmb.cn
http://2FxT9Uqk.bLdmb.cn
http://MI0TcOy9.bLdmb.cn
http://tV4z6uz5.bLdmb.cn
http://5AEiniCn.bLdmb.cn
http://IH4woAgb.bLdmb.cn
http://nT1FsNUL.bLdmb.cn
http://MBwdN6a5.bLdmb.cn
http://ZS9mDah4.bLdmb.cn
http://47j6G5Oq.bLdmb.cn
http://kMttNXqv.bLdmb.cn
http://LJy5stQd.bLdmb.cn
http://baxOpr2g.bLdmb.cn
http://cg3zwXU1.bLdmb.cn
http://6dCDPOae.bLdmb.cn
http://G4oShGZE.bLdmb.cn
http://0ZRwsJkN.bLdmb.cn
http://BWyt3nf0.bLdmb.cn
http://Z5D1tbk7.bLdmb.cn
http://VXXqJJyM.bLdmb.cn
http://8PFxWtLk.bLdmb.cn
http://nR7imvJP.bLdmb.cn
http://www.dtcms.com/a/382180.html

相关文章:

  • Spring Boot 整合 Mockito 进行单元测试
  • 【C++】C++11介绍(Ⅱ)
  • HTML新属性
  • 分库分表是否真的要退出历史舞台?
  • [BJ2012.X4] 统计车牌
  • 【Rust】一个从Modelscope下载模型CLI工具
  • 第三方服务商接入美团核销接口:零侵入对接的关键要点
  • 电压监控器原理
  • python面向对象的三大特性
  • 从 MySQL 到 TiDB:分布式数据库的无缝迁移与实战指南
  • Ansible的jinja2 模板、Roles角色详解
  • Linux内核的PER_CPU机制
  • 树莓派组建nas,云服务器及家庭影院
  • 二叉树hot100-中等
  • MX 模拟赛二总结
  • windows rocketmq 启动时报 java.lang.NullPointerException
  • 文本处理三剑客——grep、sed、awk
  • o2oa待办流程和已办流程表
  • 【WebSocket✨】入门之旅(三):WebSocket 的实战应用
  • 闪电科创-交通信号灯仿真SUMO
  • 【自动化】深入浅出UIAutomationClient:C#桌面自动化实战指南
  • 自定义类型:结构体、联合与枚举(1)
  • 在 Ubuntu 系统中基于 Miniconda 安装 VLLM 并启动模型 + Dify 集成指南
  • JavaWeb--day4--WebHttp协议Tomcat
  • Linux命令行的核心理念与实用指南(进阶版)
  • 机器学习-模型验证
  • 3-机器学习与大模型开发数学教程-第0章 预备知识-0-3 函数初步(多项式、指数、对数、三角函数、反函数)
  • 使用Aop和自定义注解实现SpringTask定时任务中加锁逻辑的封装
  • 远程依赖管理新范式:cpolar赋能Nexus全球协作
  • 【个人项目】【前端实用工具】OpenAPI to TypeScript 转换器