Linux下的系统编程——线程同步(十三)

前言:

在多线程编程中,如果多个线程同时访问和修改共享资源,可能会产生竞争条件和数据不一致的问题。同步机制用于协调线程之间的访问和操作,确保数据的正确性和一致性。为了避免多个线程同时访问和操作共享资源导致的问题,可以使用互斥锁(mutex)来实现线程的互斥访问。互斥锁可以保证同一时间只有一个线程访问共享资源、条件变量用于线程之间的通信和同步。一个线程可以等待某个条件成立,而其他线程可以通过发送信号来改变条件变量的状态,从而唤醒等待的线程。读写锁是一种特殊的锁,用于控制对共享资源的读取和写入。多个线程可以同时进行读操作,但只能有一个线程进行写操作,以确保数据的一致性。

目录

一、同步概念:

1.线程同步:

2.数据混乱原因:

二、互斥量 mutex

1.线程同步与锁:

2.锁的使用注意事项

 3.借助互斥锁管理共享数据实现同步

(1)不加锁:

(2)加mutex:

 1)使用mutex(互斥量、互斥锁)一般步骤:

 2)初始化互斥量:

3)*注意事项:

(3)加锁步骤测试:

三、读写锁:

1.读写锁函数原型:

2.读写锁原理:

四、**死锁:

 五、条件变量

    1.初始化条件变量:

    2.阻塞等待条件:

3.能够借助条件变量,完成生成者消费者

(1)模型分析

(2)代码实现

(3)运行效果​编辑

(4)一个生产者,多个消费者

4.条件变量的优点:

六、信号量: 

1.基本操作

2.生产者消费者信号量模型


一、同步概念:

        

        所谓同步,即同时起步,协调一致。不同的对象,对“同步”的理解方式略有不同。如,设备同步,是指在两 个设备之间规定一个共同的时间参考;数据库同步,是指让两个或多个数据库内容保持一致,或者按需要部分保持 一致;文件同步,是指让两个或多个文件夹里的文件保持一致。等等

        而,编程中、通信中所说的同步与生活中大家印象中的同步概念略有差异。“同”字应是指协同、协助、互相 配合。主旨在协同步调,按预定的先后次序运行

1.线程同步:

协同步调,对公共区域数据按序访问。防止数据混乱,产生与时间有关的错误

因此,所有“多个控制流,共同操作一个共享资源”的情况,都需要同步

2.数据混乱原因:

1. 资源共享(独享资源则不会)

2. 调度随机(意味着数据访问会出现竞争)

3. 线程间缺乏必要的同步机制。

         以上 3 点中,前两点不能改变,欲提高效率,传递数据,资源必须共享。只要共享资源,就一定会出现竞争。 只要存在竞争关系,数据就很容易出现混乱。

二、互斥量 mutex

Linux 中提供一把互斥锁 mutex(也称之为互斥量)。

每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁

资源还是共享的,线程间也还是竞争的, 但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。

1.线程同步与锁:

“与时间有关的错误”(time related)。为了避免这种数据混乱,线程需要同步。

2.锁的使用注意事项

建议锁!对公共数据进行保护。所有线程【应该】在访问公共数据前先拿锁再访问。但锁本身不具备强制性。

 3.借助互斥锁管理共享数据实现同步

        (1)不加锁:

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>void *tfn(void *arg)
{srand(time(NULL));while (1) {printf("hello ");sleep(rand() % 3);	//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误printf("world\n");sleep(rand() % 3);}return NULL;
}int main(void)
{pthread_t tid;srand(time(NULL));pthread_create(&tid, NULL, tfn, NULL);while (1) {printf("HELLO ");sleep(rand() % 3);printf("WORLD\n");sleep(rand() % 3);}pthread_join(tid, NULL);return 0;
}/*线程之间共享资源stdout*/

 子父线程相互争夺cpu,出现数据混淆现象:

(2)加mutex:

    1)使用mutex(互斥量、互斥锁)一般步骤

    pthread_mutex_t 类型。(本质是结构体) 

    1. pthread_mutex_t lock;  创建锁

    2  pthread_mutex_init; 初始化        1

    3. pthread_mutex_lock;加锁           1--    --> 0

    4. 访问共享数据(stdout)        

    5. pthrad_mutext_unlock();解锁       0++    --> 1

    6. pthead_mutex_destroy销毁锁


 

 2)初始化互斥量:

        pthread_mutex_t mutex;

        1. pthread_mutex_init(&mutex, NULL);               动态初始化。

        2. pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;    静态初始化。

3)*注意事项:

        尽量保证锁的粒度越小越好。(访问共享数据前加锁。访问结束【立即】解锁。)

        互斥锁,本质是结构体。 我们可以看成整数。 初值为 1。(pthread_mutex_init() 函数调用成功。)

        加锁:--操作, 阻塞线程。

        解锁:++操作, 换醒阻塞在锁上的线程。

        try锁: 尝试加锁,成功--。失败,返回。同时设置错误号 EBUSY

        restrict关键字: 用来限定指针变量。被该关键字限定的指针变量所指向的内存操作,必须由本指针完成

(3)加锁步骤测试:

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>pthread_mutex_t mutex;    //定义一把互斥锁,可以想象为一个intvoid *tfn(void *arg)
{srand(time(NULL));while (1) {pthread_mutex_lock(&mutex);    //加锁  可以想象成锁--  (1-------  -->0)printf("hello ");sleep(rand() % 3);	//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误printf("world\n");pthread_mutex_unlock(&mutex);    //解锁 可以想象为锁++  (0-------  -->1)sleep(rand() % 3);}return NULL;
}int main(void)
{pthread_t tid;srand(time(NULL));int ret = pthread_mutex_init(&mutex,NULL);   //初始化互斥锁 可以认为锁的值为1if(ret != 0){fprintf(stderr,"mutex init error: %s\n",strerror(ret));exit(1);}pthread_create(&tid, NULL, tfn, NULL);while (1) {pthread_mutex_lock(&mutex);     //加锁  可以想象成锁--  (1-------  -->0)printf("HELLO ");sleep(rand() % 3);printf("WORLD\n");pthread_mutex_unlock(&mutex);    //解锁 可以想象为锁++  (0-------  -->1)sleep(rand() % 3);}pthread_join(tid, NULL);pthread_mutex_destroy(&mutex);    //销毁互斥锁 return 0;
}

三、读写锁:

1.读写锁函数原型:

    锁只有一把。以读方式给数据加锁——读锁。以写方式给数据加锁——写锁。

    读共享,写独占

    写锁优先级高

    相较于互斥量而言当读线程多的时候,提高访问效率

    pthread_rwlock_t  rwlock;

    pthread_rwlock_init(&rwlock, NULL);                //初始化读写锁

    pthread_rwlock_rdlock(&rwlock);        try         //读模式加锁

    pthread_rwlock_wrlock(&rwlock);        try         //写模式加锁

    pthread_rwlock_unlock(&rwlock);                     //解锁

    pthread_rwlock_destroy(&rwlock);·                  //销毁读写锁

2.读写锁原理:

  同时有多个线程对同一全局数据读、写操作


/* 3个线程不定时 "写" 全局资源,5个线程不定时 "读" 同一全局资源 */#include <stdio.h>
#include <unistd.h>
#include <pthread.h>int counter;                          //全局资源
pthread_rwlock_t rwlock;              //全局的读写锁void *th_write(void *arg)
{int t;int i = (int)arg;while (1) {t = counter;                    // 保存写之前的值usleep(1000);pthread_rwlock_wrlock(&rwlock);    //以写模式加锁,写独占printf("=====write %d: %lu: counter=%d ++counter=%d\n", i, pthread_self(), t, ++counter);pthread_rwlock_unlock(&rwlock);    //解锁usleep(9000);               // 给 r 锁提供机会}return NULL;
}void *th_read(void *arg)
{int i = (int)arg;while (1) {pthread_rwlock_rdlock(&rwlock);    //读线程间,读锁共享printf("-----------------read %d: %lu: %d\n", i, pthread_self(), counter);pthread_rwlock_unlock(&rwlock);    //解锁usleep(2000);                // 给写锁提供机会}return NULL;
}int main(void)
{int i;pthread_t tid[8];    //设置一个8个线程的数组pthread_rwlock_init(&rwlock, NULL);    //自定义读写锁for (i = 0; i < 3; i++)pthread_create(&tid[i], NULL, th_write, (void *)i);     //创建3个写线程for (i = 0; i < 5; i++)pthread_create(&tid[i+3], NULL, th_read, (void *)i);    //创建5个读线程for (i = 0; i < 8; i++)pthread_join(tid[i], NULL);             //回收8个线程pthread_rwlock_destroy(&rwlock);            //释放读写琐return 0;
}

四、**死锁:

1.发生死锁原因:

使用锁不恰当导致的现象

        1. 对一个锁反复lock

        2. 两个线程,各自持有一把锁,请求另一把。

2.避免死锁方法: 

        1.保证资源获取顺序,要求每个线程获取资源的顺序一致

·        2.当得不到所有所需资源时,放弃已经获得的资源,等待

 五、*条件变量

    本身不是锁!  但是通常结合锁来使用。 mutex

    pthread_cond_t cond;

    1.初始化条件变量:

        1. pthread_cond_init(&cond, NULL);               动态初始化。

        2. pthread_cond_t cond = PTHREAD_COND_INITIALIZER;    静态初始化。

    2.阻塞等待条件:

        pthread_cond_wait(&cond, &mutex);

        作用:  

            1) 阻塞等待条件变量满足

            2) 解锁已经加锁成功的信号量 (相当于 pthread_mutex_unlock(&mutex))

            3)  当条件满足,函数返回时,重新加锁信号量 (相当于, pthread_mutex_lock(&mutex);)

            1) 和 2)俩步为一个原子操作
    

    pthread_cond_signal():   唤醒阻塞在条件变量上的 (至少)一个线程

    pthread_cond_broadcast(): 唤醒阻塞在条件变量上的所有线程


3.能够借助条件变量,完成生成者消费者

(1)模型分析

(2)代码实现

/*借助条件变量模拟 生产者-消费者 问题*/
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>/*链表作为公享数据,需被互斥量保护*/
struct msg {struct msg *next;int num;
};struct msg *head;/* 静态初始化 一个条件变量 和 一个互斥量*/
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;void *consumer(void *p)//消费者函数
{struct msg *mp;for (;;) {pthread_mutex_lock(&lock);while (head == NULL) {           //头指针为空,说明没有节点    可以为if吗pthread_cond_wait(&has_product, &lock);//发生阻塞}mp = head;      head = mp->next;                 //模拟消费掉一个产品pthread_mutex_unlock(&lock);printf("-Consume %lu---%d\n", pthread_self(), mp->num);free(mp);sleep(rand() % 5);}
}void *producer(void *p)//生产者函数
{struct msg *mp;for (;;) {mp = malloc(sizeof(struct msg));mp->num = rand() % 1000 + 1;        //模拟生产一个产品printf("-Produce ---------------------%d\n", mp->num);pthread_mutex_lock(&lock);mp->next = head;head = mp;pthread_mutex_unlock(&lock);pthread_cond_signal(&has_product);  //将等待在该条件变量上的 一个线程唤醒sleep(rand() % 5);}
}int main(int argc, char *argv[])
{pthread_t pid, cid;srand(time(NULL));pthread_create(&pid, NULL, producer, NULL);//创建生产者pthread_create(&cid, NULL, consumer, NULL);//创建消费者pthread_join(pid, NULL);//回收生产者pthread_join(cid, NULL);//回收消费者return 0;
}

(3)运行效果

(4)一个生产者,多个消费者

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>void err_thread(int ret, char *str)
{if (ret != 0) {fprintf(stderr, "%s:%s\n", str, strerror(ret));pthread_exit(NULL);}
}struct msg {int num;struct msg *next;
};struct msg *head;pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;      // 定义/初始化一个互斥量
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER;      // 定义/初始化一个条件变量void *produser(void *arg)
{while (1) {struct msg *mp = malloc(sizeof(struct msg));mp->num = rand() % 1000 + 1;                        // 模拟生产一个数据`printf("--produce %d\n", mp->num);pthread_mutex_lock(&mutex);                         // 加锁 互斥量mp->next = head;                                    // 写公共区域head = mp;pthread_mutex_unlock(&mutex);                       // 解锁 互斥量pthread_cond_signal(&has_data);                     // 唤醒阻塞在条件变量 has_data上的线程.sleep(rand() % 3);}return NULL;
}void *consumer(void *arg)
{while (1) {struct msg *mp;pthread_mutex_lock(&mutex);                         // 加锁 互斥量while (head == NULL) {pthread_cond_wait(&has_data, &mutex);           // 阻塞等待条件变量, 解锁}                                                   // pthread_cond_wait 返回时, 重新加锁 mutexmp = head;head = mp->next;pthread_mutex_unlock(&mutex);                       // 解锁 互斥量printf("---------consumer id: %lu :%d\n", pthread_self(), mp->num);free(mp);sleep(rand()%3);}return NULL;
}int main(int argc, char *argv[])
{int ret;pthread_t pid, cid;srand(time(NULL));ret = pthread_create(&pid, NULL, produser, NULL);           // 生产者if (ret != 0) err_thread(ret, "pthread_create produser error");ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者if (ret != 0) err_thread(ret, "pthread_create consuer error");ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者if (ret != 0) err_thread(ret, "pthread_create consuer error");ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者if (ret != 0) err_thread(ret, "pthread_create consuer error");pthread_join(pid, NULL);pthread_join(cid, NULL);return 0;
}

4.条件变量的优点:

相较于 mutex 而言,条件变量可以减少竞争。

如直接使用 mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚 (链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引 起消费者之间的竞争。提高了程序效率

六、信号量: 

1.基本操作

    应用于线程、进程间同步。

    相当于 初始化值为 N 的互斥量。  N值,表示可以同时访问共享数据区的线程数

    函数:
        sem_t sem;    定义类型。

        int sem_init(sem_t *sem, int pshared, unsigned int value);//初始化信号量

        参数:
            sem: 信号量 

            pshared:    0: 用于线程间同步
                    
                                 1: 用于进程间同步

            value:        N值:指定同时访问的线程数


       sem_destroy();        //销毁信号量

        sem_wait();        一次调用,做一次-- 操作, 当信号量的值为 0 时,再次 -- 就会阻塞。 (对比 pthread_mutex_lock//加锁

        sem_post();        一次调用,做一次++ 操作. 当信号量的值为 N 时, 再次 ++ 就会阻塞。(对比 pthread_mutex_unlock//解锁

注意:信号量的初值,决定了占用信号量的线程个数

2.生产者消费者信号量模型

使用信号量完成线程间同步,模拟生产者,消费者问题

/*信号量实现 生产者 消费者问题*/#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>#define NUM 5               int queue[NUM];                                     //全局数组实现环形队列
sem_t blank_number, product_number;                 //空格子信号量, 产品信号量void *producer(void *arg)
{int i = 0;while (1) {sem_wait(&blank_number);                    //生产者将空格子数--,为0则阻塞等待queue[i] = rand() % 1000 + 1;               //生产一个产品printf("----Produce---%d\n", queue[i]);        sem_post(&product_number);                  //将产品数++i = (i+1) % NUM;                            //借助下标实现环形sleep(rand()%1);}
}void *consumer(void *arg)
{int i = 0;while (1) {sem_wait(&product_number);                  //消费者将产品数--,为0则阻塞等待printf("-Consume---%d\n", queue[i]);queue[i] = 0;                               //消费一个产品 sem_post(&blank_number);                    //消费掉以后,将空格子数++i = (i+1) % NUM;sleep(rand()%3);}
}int main(int argc, char *argv[])
{pthread_t pid, cid;sem_init(&blank_number, 0, NUM);                //初始化空格子信号量为5, 线程间共享 -- 0sem_init(&product_number, 0, 0);                //产品数为0pthread_create(&pid, NULL, producer, NULL);    //产生生产者pthread_create(&cid, NULL, consumer, NULL);    //产生消费者pthread_join(pid, NULL);pthread_join(cid, NULL);sem_destroy(&blank_number);        //回收生产者sem_destroy(&product_number);      //回收消费者return 0;
}

 

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

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

相关文章

云上亚运:所使用的高新技术,你知道吗?

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 公众号&#xff1a;网络豆云计算学堂 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a; 网络豆的主页​​​​​ 目录 前言 一.什么是云上亚运会 二.为什么要使用云…

Redis缓存相关问题

目录 缓存穿透 缓存雪崩 缓存击穿 Redis集群方案 主从复制Replication 哨兵sentinel 高可用介绍 Redis sentinel介绍 Redis sentinel使用 配置sentinel 启动sentinel 测试sentinel Redis内置集群cluster Redis cluster介绍 哈希槽方式分配数据 Redis cluster的…

使用ElementUI完成登入注册的跨域请求提高开发效率

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《Spring与Mybatis集成整合》​​​​​​​ ⛺️ 生活的理想&#xff0c;为了不断更新自己 ! 目录 ​编辑 1、前言 1.1.什么是ELementUI 2、完成登陆注册前端页面 2.1环境搭建 运行…

八大排序(二)快速排序

一、快速排序的思想 快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法&#xff0c;其基本思想为&#xff1a;任取待排序元素序列中的某元素作为基准值&#xff0c;按照该排序码将待排序集合分割成两子序列&#xff0c;左子序列中所有元素均小于基准值&#xff0c;右…

MinGW相关错误

1、go编译c报错 cc1.exe: sorry, unimplemented: 64-bit mode not compiled in 参考&#xff1a;BeifangCc go编译c报错 cc1.exe: sorry, unimplemented: 64-bit mode not compiled in 说明当前gcc是32位&#xff0c;无法在当前64位机器上正常工作&#xff0c;需要更新gcc 下载…

2023手把手教授neo4j安装及环境配置

安装包下载&#xff1a; 首先进入Neo4j官网&#xff1a;Neo4j Graph Database & Analytics | Graph Database Management System 在上方选择栏中选择“Products”&#xff0c;在其中选择“Deployment Center”&#xff0c;点击“Download Neo4j to get started” 然后往下…

【Tomcat】Tomcat 运行原理

Tomcat 运行原理 一. Servlet 运行原理1. 接收请求2. 根据请求计算响应3. 返回响应 二. Tomcat 的伪代码1. Tomcat 初始化流程2. Tomcat 处理请求流程3. Servlet 的 service 方法的实现 一. Servlet 运行原理 在 Servlet 的代码中我们并没有写 main 方法, 那么对应的 doGet 代…

ARM Linux DIY(十三)Qt5 移植

前言 板子带有屏幕&#xff0c;那当然要设计一下 GUI&#xff0c;对 Qt5 比较熟悉&#xff0c;那就移植它吧。 移植 Qt5 buildroot 使能 Qt5&#xff0c;这里我们只开启核心功能 gui module --> widgets module 编译 $ make ODIY_V3S/ qt5base编译报错&#xff1a;找不…

Flink TaskManger 内存计算实战

Flink TaskManager内存计算图 计算实例 案例一、假设Task Process内存4GB。 taskmanager.memory.process.size4096m 先排减JVM内存。 JVM Metaspace 固定内存 256mJVM Overhead 固定比例 process * 0.1 4096 * 0.1 410m 得到 Total Flink Memory 4096-256-410 3430m 计…

【线性代数】为什么 AA* = |A|E

A A ∗ 矩阵相乘&#xff0c;刚好是行列式展开的定义 AA*矩阵相乘&#xff0c;刚好是行列式展开的定义 AA∗矩阵相乘&#xff0c;刚好是行列式展开的定义 矩阵提取一个因子 ∣ A ∣ &#xff0c;所有元素需要除 ∣ A ∣ 矩阵提取一个因子 |A|&#xff0c;所有元素需要除 |A| 矩…

【C/C++笔试练习】——printf在使用%的注意事项、for循环语句的三个条件、运算符优先级、删除公共字符

文章目录 C/C笔试练习1.%符号在printf用作格式说明符的注意事项&#xff08;1&#xff09;输出%5.3s&#xff08;2&#xff09;判断%中小数点含义 2.for循环语句的三个条件&#xff08;3&#xff09;判断循环次数&#xff08;4&#xff09;判断循环次数 3.运算符优先级&#xf…

【ACDC数据集】:预处理ACDC心脏3D MRI影像数据集到VOC数据集格式,nii转为jpg,label转为png

【Segment Anything Model】做分割的专栏链接&#xff0c;欢迎来学习。 【博主微信】cvxiaoyixiao 本专栏为公开数据集的预处理&#xff0c;持续更新中。 文章目录 1️⃣ ACDC数据集介绍2️⃣ ACDC数据集样例 3️⃣ 预处理ACDC目标 4️⃣ 处理结果样图 5️⃣ 代码 6️⃣ 划分测…

文件高效批量重命名,轻松重命名不同类型的文件名并隐藏编号

你是否曾经因为文件名混乱而感到困扰&#xff1f;你是否希望有一种方法可以快速、简单地管理你的文件名&#xff1f;如果你的答案是肯定的&#xff0c;那么我们的产品——文件重命名工具&#xff0c;将是你的完美解决方案&#xff01; 首先我们要进入文件批量改名高手主页面&a…

Sqilte3初步教程

文章目录 安装创建数据库创建和删除表插入行数据 安装 Windows下安装&#xff0c;首先到下载页面&#xff0c;下载Windows安装软件&#xff0c;一般是 sqlite-dll-win32-*.zip sqlite-tools-win32-*.zip下载之后将其内容解压到同一个文件夹下&#xff0c;我把它们都放在了D:\…

搭建ELK+Filebead+zookeeper+kafka实验

部署 Zookeeper 集群 准备 3 台服务器做 Zookeeper 集群 192.168.10.17 192.168.10.21 192.168.10.22 1.安装前准备 关闭防火墙 systemctl stop firewalld systemctl disable firewalld setenforce 0 安装 JDK yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-…

DolphinDB x 龙蜥社区,打造多样化的数据底座

近日&#xff0c;浙江智臾科技有限公司&#xff08;以下简称“DolphinDB”&#xff09;正式签署 CLA 贡献者许可协议&#xff0c;加入龙蜥社区&#xff08;OpenAnolis&#xff09;。 DolphinDB 主创团队从 2012 年开始投入研发产品。作为一款基于高性能时序数据库&#xff0c;D…

【pytest】 allure 生成报告

1. 下载地址 官方文档; Allure Framework 参考文档&#xff1a; 最全的PytestAllure使用教程&#xff0c;建议收藏 - 知乎 https://github.com/allure-framework 1.2安装Python依赖 windows&#xff1a;pip install allure-pytest 2. 脚本 用例 import pytest class …

代码随想录算法训练营 动态规划part12

一、最佳买卖股票时机含冷冻期 309. 买卖股票的最佳时机含冷冻期 - 力扣&#xff08;LeetCode&#xff09; public class Solution {public int maxProfit(int[] prices) {int len prices.length;if (len < 2) {return 0;}int[] dp new int[3];dp[0] 0;dp[1] -price…

leetcode:2446. 判断两个事件是否存在冲突(python3解法)

难度&#xff1a;简单 给你两个字符串数组 event1 和 event2 &#xff0c;表示发生在同一天的两个闭区间时间段事件&#xff0c;其中&#xff1a; event1 [startTime1, endTime1] 且event2 [startTime2, endTime2] 事件的时间为有效的 24 小时制且按 HH:MM 格式给出。 当两个…

terraform简单的开始-vpc cvm创建

从网络开始 从创建VPC开始 复用前面的main.tf的代码&#xff1a; terraform {required_providers {tencentcloud {source "tencentcloudstack/tencentcloud"version "1.81.25"}} } variable "region" {description "腾讯云地域"…