Linux 互斥锁 读写锁 条件变量 信号量 (备查)

线程同步

1)所谓的同步并不是多个线程同时对内存进行访问,而是按照先后顺序依次进行的。
2)如没有对线程进行同步处理,会导致多个线程访问共享资源出现数据混乱的问题。
3)所谓共享资源就是多个线程共同访问的变量,这些变量通常为全局数据区变量或者堆区变量,这些变量对应的共享资源也被称之为临界资源

4)常用的线程同步方式有四种:互斥锁读写锁条件变量信号量。

5)通过锁机制能保证临界区代码最多只能同时有一个线程访问,这样并行访问就变为串行访问了。

互斥锁

1)通过互斥锁可以锁定一个代码块, 被锁定的这个代码块, 所有的线程只能顺序执行(不能并行处理),需要付出的代价就是执行效率的降低

1)一个互斥锁变量只能被一个线程锁定,被锁定之后其他线程再对互斥锁变量加锁就会被阻塞,直到这把互斥锁被解锁,被阻塞的线程才能被解除阻塞
2)一般情况下,每一个共享资源对应一把互斥锁

创建互斥锁
//在Linux中互斥锁的类型为pthread_mutex_t,创建一个这种类型的变量就得到了一把互斥锁:
pthread_mutex_t  mutex;
// 初始化互斥锁
// restrict: 是一个关键字, 用来修饰指针, 只有这个关键字修饰的指针可以访问指向的内存地址, 其他指针是不行的
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
           			   const pthread_mutexattr_t *restrict attr);
参数:
	mutex: 互斥锁变量的地址
	attr: 互斥锁的属性, 一般使用默认属性即可, 这个参数指定为NULL  
	
//函数调用成功会返回0,调用失败会返回相应的错误号:
加锁
// 修改互斥锁的状态, 将其设定为锁定状态, 这个状态被写入到参数 mutex 中
int pthread_mutex_lock(pthread_mutex_t *mutex);

这个函数被调用, 首先会判断参数 mutex 互斥锁中的状态是不是锁定状态
1)如果被锁定了, 其他线程加锁就失败了, 这些线程都会阻塞在这把锁上。
2)当这把锁被解开之后, 这些阻塞在锁上的线程就解除阻塞
3)没抢到锁的线程继续阻塞

// 尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);

调用这个函数对互斥锁变量加锁还是有两种情况:
1)如果这把锁没有被锁定是打开的,线程加锁成功
2)如果锁变量被锁住了,调用这个函数加锁的线程,不会被阻塞,加锁失败直接返回错误号

解锁
// 对互斥锁解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

1)不是所有的线程都可以对互斥锁解锁,哪个线程加的锁, 哪个线程才能解锁成功。

释放互斥锁
// 释放互斥锁资源            
int pthread_mutex_destroy(pthread_mutex_t *mutex);

//函数调用成功会返回0,调用失败会返回相应的错误号:
死锁

1)使用不当, 就会造成死锁现象
2)线程死锁造成的后果是:使用这个共享资源的线程都被阻塞

造成死锁的场景有如下几种:
1)加锁之后忘记解锁。
2)重复加锁, 只解锁一次,造成死锁。
3)有多个共享资源, 两个线程交叉锁定了另一个线程的必要资源

读写锁

读锁是共享的。写锁是独占的。

1)读写锁是互斥锁的升级版, 在做读操作的时候可以提高程序的执行效率
2)读写锁是一把锁,可以做写锁定和读锁定两件事。
3)写锁的优先级高于****读锁

创建读写锁
//在Linux中读写锁的类型为pthread_rwlock_t,创建一个这种类型的变量就得到了一把读写锁
pthread_rwlock_t rwlock;

在这把锁中大致记录了这些信息:
1)锁的状态: 锁定/打开
2)锁定的是什么操作: 读操作/写操作,使用读写锁锁定了读操作,需要先解锁才能去锁定写操作。
3)哪个线程将这把锁锁上了。

// 初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
           				const pthread_rwlockattr_t *restrict attr);
参数:
	rwlock: 读写锁的地址,传出参数
	attr: 读写锁属性,一般使用默认属性,指定为NULL
加读锁
// 对读写锁加读锁, 锁定的是读操作
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
// 尝试加读锁,调用这个函数加锁失败,对应的线程不会被阻塞,这个函数可以有效的避免死锁。
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

// 如果加读锁失败, 不会阻塞当前线程, 直接返回错误号
加写锁
// 对读写锁加写锁, 锁定的是写操作
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

如果读写锁已经锁定了读操作或者锁定了写操作,调用这个函数的线程会被阻塞

// // 尝试加写锁,调用这个函数加锁失败,对应的线程不会被阻塞,这个函数可以有效的避免死锁。
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

// 如果加写锁失败, 不会阻塞当前线程, 直接返回错误号
//可以在程序中对函数返回值进行判断,添加加锁失败之后的处理动作。
解锁
// 解锁, 不管锁定了读还是写都可用解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
释放读写锁
// 释放读写锁占用的系统资源
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
其他

如果说程序中所有的线程都对共享资源做写操作,使用读写锁没有优势,和互斥锁是一样的,
如果说程序中所有的线程都对共享资源有写也有读操作,并且对共享资源读的操作越多,读写锁更有优势。

条件变量

1)条件变量的主要作用不是处理线程同步, 而是进行线程的阻塞
2)一般情况下条件变量用于处理生产者和消费者模型,并且和互斥锁配合使用

创建条件变量
//条件变量类型为 pthread_cond_t,这样就定义一个条件变量类型的变量了
pthread_cond_t cond;

被条件变量阻塞的线程的线程信息会被记录到这个变量中,以便在解除阻塞的时候使用。

// 初始化条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,
     				  const pthread_condattr_t *restrict attr);
参数:
	cond: 条件变量的地址
	attr: 条件变量属性, 一般使用默认属性, 指定为NULL
阻塞线程
// 线程阻塞函数, 哪个线程调用这个函数, 哪个线程就会被阻塞
int pthread_cond_wait(pthread_cond_t *restrict cond, 	//条件变量
					  pthread_mutex_t *restrict mutex);	//互斥锁

在阻塞线程的时候,需要一个互斥锁参数,这个互斥锁主要功能是进行线程同步

该函数会对这个互斥锁做以下几件事情:
1)在阻塞线程时候,如果线程已经对互斥锁mutex上锁,那么会将这把锁打开,这样做是为了避免死锁
2)当线程解除阻塞的时候,函数内部会帮助这个线程再次将这个mutex互斥锁锁上,继续向下访问临界区

// 表示的时间是从1971.1.1到某个时间点的时间, 总长度使用秒/纳秒表示
struct timespec {
	time_t tv_sec;      /* Seconds */
	long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
};
// 将线程阻塞一定的时间长度, 时间到达之后, 线程就解除阻塞了
int pthread_cond_timedwait(pthread_cond_t *restrict cond,	//条件变量
           				   pthread_mutex_t *restrict mutex,	//互斥锁
           				   const struct timespec *restrict abstime);//表示线程阻塞的时长

struct timespec这个结构体中记录的时间是从1971.1.1到某个时间点的时间,总长度使用秒/纳秒表示。

time_t mytim = time(NULL);	// 1970.1.1 0:0:0 到当前的总秒数

struct timespec tmsp;
tmsp.tv_sec = time(NULL) + 100;	// 线程阻塞100s
tmsp.tv_nsec = 0;//初始化为0,避免出错
唤醒线程
// 唤醒阻塞在条件变量上的线程, 至少有一个被解除阻塞
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒阻塞在条件变量上的线程, 被阻塞的线程全部解除阻塞
int pthread_cond_broadcast(pthread_cond_t *cond);

1)调用上面两个函数中的任意一个,都可以唤醒被 pthread_cond_wait 或者 pthread_cond_timedwait 阻塞的线程,
2)区别就在于 pthread_cond_signal 是唤醒至少一个被阻塞的线程(总个数不定), pthread_cond_broadcast 是唤醒所有被阻塞的线程。

释放条件变量
// 销毁释放资源        
int pthread_cond_destroy(pthread_cond_t *cond);

信号量

创建信号量
//信号的类型为sem_t对应的头文件为<semaphore.h>
#include <semaphore.h>
sem_t sem;
// 初始化信号量/信号灯
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
	sem:信号量变量地址
	pshared:
			0:线程同步
			非0:进程同步
	value:初始化当前信号量拥有的资源数(>=0),如果资源数为0,线程就会被阻塞了。
判断 是否阻塞
// 函数被调用sem中的资源就会被消耗1个, 资源数-1
int sem_wait(sem_t *sem);	//阻塞

1)sem中的资源数>0,线程不会阻塞,线程会占用sem中的一个资源,因此资源数-1
2)直到sem中的资源数减为0时,资源被耗尽,因此线程也就被阻塞了。

// 函数被调用sem中的资源就会被消耗1个, 资源数-1
int sem_trywait(sem_t *sem);	//不阻塞

1)并且sem中的资源数>0,线程不会阻塞,线程会占用sem中的一个资源,因此资源数-1
2)直到sem中的资源数减为0时,资源被耗尽,但是线程不会被阻塞,直接返回错误号
3)因此可以在程序中添加判断分支,用于处理获取资源失败之后的情况。

// 表示的时间是从1971.1.1到某个时间点的时间, 总长度使用秒/纳秒表示
struct timespec {
	time_t tv_sec;      /* Seconds */
	long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
};
// 调用该函数线程获取sem中的一个资源,当资源数为0时,线程阻塞,在阻塞abs_timeout对应的时长之后,解除阻塞。
// abs_timeout: 阻塞的时间长度, 单位是s, 是从1970.1.1开始计算的
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

1)sem中的资源数>0,线程不会阻塞,线程会占用sem中的一个资源,因此资源数-1
2)直到sem中的资源数减为0时,资源被耗尽,线程被阻塞
3)当阻塞指定的 abs_timeout 时长之后,线程解除阻塞

唤醒阻塞 线程
// 调用该函数给sem中的资源数+1
int sem_post(sem_t *sem);

1)该函数会将sem中的资源数+1
2)如果有线程在调用 sem_wait、sem_trywait、sem_timedwait 时因为sem中的资源数为0被阻塞了,这时这些线程会解除阻塞,获取到资源之后继续向下运行。

获取现有可用资源数
// 查看信号量 sem 中的整形数的当前值, 这个值会被写入到sval指针对应的内存中
int sem_getvalue(sem_t *sem, int *sval);

// sval是一个传出参数

1)这个函数可以查看sem中现在拥有的资源个数,通过第二个参数 sval 将数据传出,
2)也就是说第二个参数的作用和返回值是一样的。

释放信号量
// 参数 sem 就是 sem_init() 的第一个参数            
int sem_destroy(sem_t *sem);

// 资源释放, 线程销毁之后调用这个函数即可

其他

生产者和消费者 模型+案列

在这里插入图片描述

-------------------生产者消费者 案列(使用条件变量)-----------------------------
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>

// 链表的节点
struct Node
{
    int number;
    struct Node* next;
};

// 定义条件变量, 控制消费者线程
pthread_cond_t cond;
// 互斥锁变量
pthread_mutex_t mutex;
// 指向头结点的指针
struct Node * head = NULL;

// 生产者的回调函数
void* producer(void* arg)
{
    // 一直生产
    while(1)
    {
        pthread_mutex_lock(&mutex);
        // 创建一个链表的新节点
        struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));
        // 节点初始化
        pnew->number = rand() % 1000;
        // 节点的连接, 添加到链表的头部, 新节点就新的头结点
        pnew->next = head;
        // head指针前移
        head = pnew;
        printf("+++producer, number = %d, tid = %ld\n", pnew->number, pthread_self());
        pthread_mutex_unlock(&mutex);

        // 生产了任务, 通知消费者消费
        pthread_cond_broadcast(&cond);

        // 生产慢一点
        sleep(rand() % 3);
    }
    return NULL;
}

// 消费者的回调函数
void* consumer(void* arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        // 一直消费, 删除链表中的一个节点
//        if(head == NULL)   // 这样写有bug
        while(head == NULL)
        {
            // 这函数会自动将线程拥有的锁解开
            pthread_cond_wait(&cond, &mutex);
            // 当消费者线程解除阻塞之后, 会自动将这把锁锁上
            // 这时候当前这个线程又重新拥有了这把互斥锁
        }
        // 取出链表的头结点, 将其删除
        struct Node* pnode = head;
        printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self());
        head  = pnode->next;
        free(pnode);
        pthread_mutex_unlock(&mutex);        

        sleep(rand() % 3);
    }
    return NULL;
}

int main()
{
    // 初始化条件变量
    pthread_cond_init(&cond, NULL);
    pthread_mutex_init(&mutex, NULL);

    // 创建5个生产者, 5个消费者
    pthread_t ptid[5];
    pthread_t ctid[5];
    for(int i=0; i<5; ++i)
    {
        pthread_create(&ptid[i], NULL, producer, NULL);
    }

    for(int i=0; i<5; ++i)
    {
        pthread_create(&ctid[i], NULL, consumer, NULL);
    }

    // 释放资源
    for(int i=0; i<5; ++i)
    {
        // 阻塞等待子线程退出
        pthread_join(ptid[i], NULL);
    }

    for(int i=0; i<5; ++i)
    {
        pthread_join(ctid[i], NULL);
    }

    // 销毁条件变量
    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);

    return 0;
}
顺序执行的线程 案列

1)如果生产者和消费者线程使用的信号量对应的总资源数为1,那么不管线程有多少个,可以工作的线程只有一个,其余线程由于拿不到资源,都被迫阻塞了。

不会出现生产者线程和消费者线程同时访问共享资源的情况,不管生产者和消费者线程有多少个,它们都是顺序执行的。(可不使用互斥锁)

----------------顺序执行的线程 案列------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <semaphore.h>
#include <pthread.h>

// 链表的节点
struct Node
{
    int number;
    struct Node* next;
};

// 生产者线程信号量
sem_t psem;
// 消费者线程信号量
sem_t csem;

// 互斥锁变量
pthread_mutex_t mutex;
// 指向头结点的指针
struct Node * head = NULL;

// 生产者的回调函数
void* producer(void* arg)
{
    // 一直生产
    while(1)
    {
        // 生产者拿一个信号灯
        sem_wait(&psem);
        // 创建一个链表的新节点
        struct Node* pnew = (struct Node*)malloc(sizeof(struct Node));
        // 节点初始化
        pnew->number = rand() % 1000;
        // 节点的连接, 添加到链表的头部, 新节点就新的头结点
        pnew->next = head;
        // head指针前移
        head = pnew;
        printf("+++producer, number = %d, tid = %ld\n", pnew->number, pthread_self());

        // 通知消费者消费, 给消费者加信号灯
        sem_post(&csem);
        

        // 生产慢一点
        sleep(rand() % 3);
    }
    return NULL;
}

// 消费者的回调函数
void* consumer(void* arg)
{
    while(1)
    {
        sem_wait(&csem);
        // 取出链表的头结点, 将其删除
        struct Node* pnode = head;
        printf("--consumer: number: %d, tid = %ld\n", pnode->number, pthread_self());
        head  = pnode->next;
        free(pnode);
        // 通知生产者生成, 给生产者加信号灯
        sem_post(&psem);

        sleep(rand() % 3);
    }
    return NULL;
}

int main()
{
    // 初始化信号量
    // 生产者和消费者拥有的信号灯的总和为1
    sem_init(&psem, 0, 1);  // 生成者线程一共有1个信号灯
    sem_init(&csem, 0, 0);  // 消费者线程一共有0个信号灯

    // 创建5个生产者, 5个消费者
    pthread_t ptid[5];
    pthread_t ctid[5];
    for(int i=0; i<5; ++i)
    {
        pthread_create(&ptid[i], NULL, producer, NULL);
    }

    for(int i=0; i<5; ++i)
    {
        pthread_create(&ctid[i], NULL, consumer, NULL);
    }

    // 释放资源
    for(int i=0; i<5; ++i)
    {
        pthread_join(ptid[i], NULL);
    }

    for(int i=0; i<5; ++i)
    {
        pthread_join(ctid[i], NULL);
    }

    sem_destroy(&psem);
    sem_destroy(&csem);

    return 0;
}


作者: 苏丙榅
链接: https://subingwen.cn/linux/thread-sync/?highlight=%E7%BA%BF%E7%A8%8B%E5%90%8C%E6%AD%A5
来源: 爱编程的大丙
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

详细教程可转

爱编程的大丙

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

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

相关文章

javaweb校车校园车辆管理系统springboot+jsp

结构设计&#xff1a;总体采用B/S结构设计模式 (1)用户登录模块&#xff1a;用户通过手动登录&#xff0c;检测是否是校内人员的车辆。 (2)用户车辆信息编辑、上传、模块&#xff1a;通过上传车辆入场信息的操作权限&#xff0c;以用户的名义发布资料上传至校园停车场系统中。…

可视化数据库管理客户端:Adminer

简介&#xff1a;Adminer&#xff08;前身为phpMinAdmin&#xff09;是一个用PHP编写的功能齐全的数据库管理工具。与phpMyAdmin相反&#xff0c;它由一个可以部署到目标服务器的文件组成。Adminer可用于MySQL、PostgreSQL、SQLite、MS SQL、Oracle、Firebird、SimpleDB、Elast…

Java+SSM+MySQL基于微信的在线协同办公小程序(附源码 调试 文档)

基于微信的在线协同办公小程序 一、引言二、系统设计三、技术架构四、管理员功能设计五、员工功能设计六、系统实现七、界面展示八、源码获取 一、引言 随着科技的飞速发展&#xff0c;移动互联网已经深入到我们生活的各个角落。在这个信息时代&#xff0c;微信作为全球最大的…

头歌JUnit单元测试相关实验入门

一、入门实验 1.1第一个Junit测试程序 任务描述 请学员写一个名为testSub()的测试函数&#xff0c;来测试给定的减法函数是否正确。 相关知识 Junit编写原则 1、简化测试的编写&#xff0c;这种简化包括测试框架的学习和实际测试单元的编写。 2、测试单元保持持久性。 3、利用…

【Python】Python给工作减负-读Excel文件生成xml文件

目录 ​前言 正文 1.Python基础学习 2.Python读取Excel表格 2.1安装xlrd模块 2.2使用介绍 2.2.1常用单元格中的数据类型 2.2.2 导入模块 2.2.3打开Excel文件读取数据 2.2.4常用函数 2.2.5代码测试 2.2.6 Python操作Excel官方网址 3.Python创建xml文件 3.1 xml语法…

计算机组成原理,硬件组成,存储器,控制器,控制器的任务, 运算器,中央处理器CPU,主存

计算机组成原理 课程需求 前导课程&#xff1a; 后继课程 汇编 操作系统 数逻 组成 系统结构 数电 微机原理 课程结构 计算机特性 1 从外部角度来看计算机的特性 快速 通用 准确 逻辑 2从外部特性与内部特性的关系 计算机组成 一 硬件组成 运算器 主要功能是进行算术…

强化学习(一)——基本概念及DQN

1 基本概念 智能体 agent &#xff0c;做动作的主体&#xff0c;&#xff08;大模型中的AI agent&#xff09; 环境 environment&#xff1a;与智能体交互的对象 状态 state &#xff1b;当前所处状态&#xff0c;如围棋棋局 动作 action&#xff1a;执行的动作&#xff0c;…

CRM系统是怎样帮助销售流程自动化的?

销售业绩是衡量企业经营的重要指标&#xff0c;也是销售人员一直要达成的目标。销售业绩能否提高取决于销售人员的能力、客户服务水平&#xff0c;还需要借助有效的工具。CRM系统就是这样的一款软件。企业如何提高销售业绩&#xff1f;不妨试试CRM销售流程自动化。 CRM如何实现…

【从删库到跑路 | MySQL总结篇】事务详细介绍

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【MySQL学习专栏】&#x1f388; 本专栏旨在分享学习MySQL的一点学习心得&#xff0c;欢迎大家在评论区讨论&#x1f48c; 目录 一、事务…

JavaScript 数据结构

JavaScript 数据结构 目录 JavaScript 数据结构 一、标识符 二、关键字 三、常量 四、变量 每一种计算机编程语言都有自己的数据结构&#xff0c;JavaScript脚本语言的数据结构包括&#xff1a;标识符、常量、变量、保留字等。 一、标识符 标识符&#xff0c;说白了&…

使用gcloud SDK 管理和部署 Cloud run service

查看cloud run 上的service 列表&#xff1a; gcloud run services list > gcloud run services listSERVICE REGION URL LAST DEPLOYED BY LAST DEPL…

【QT】Windows环境下,cmake引入QML

这里使用的QT库为5.7版本。 1、添加环境变量 QT库根目录环境变量 QTDIR QT库平台插件环境变量 QT_PLUGIN_PATH QML支持环境变量 QML2_IMPORT_PATH &#xff08;该环境变量仅在需要使用QML时添加&#xff09; QT库动态库环境变量&#xff0c;bin目录下包含了QT程序运行所需的dl…

常见的攻击防护

只做模拟机器使用&#xff0c;不使用真实机器 目录 一、 DHCP饿死和防护应对措施.................................. 1 1&#xff0c; 实验拓扑&#xff1a;...................................................... 2 2&#xff0c; 实验配置............................…

AD23等间距拉线、布线的方法

U M 键进行多根走线&#xff0c; 多根走线想保持10个mil 我可以直接按table键,弹出Multi-Routing ponent&#xff0c;项的Bus Spadng输入框中填充10个mil&#xff0c;新走线产生10个mil的等间距 保持最小的一个规则&#xff0c;可以去到6mil线距。 在拉线操作过程中&#…

详解Spring中BeanPostProcessor在Spring工厂和Aop发挥的作用

&#x1f609;&#x1f609; 学习交流群&#xff1a; ✅✅1&#xff1a;这是孙哥suns给大家的福利&#xff01; ✨✨2&#xff1a;我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 &#x1f96d;&#x1f96d;3&#xff1a;QQ群&#xff1a;583783…

TCP连接为什么是三次握手,而不是两次和四次

答案 阻止重复的历史连接同步初始序列号避免资源浪费 原因 阻止重复的历史连接&#xff08;首要原因&#xff09; 考虑这样一种情况&#xff1a; 客户端现在要给服务端建立连接&#xff0c;向服务端发送了一个SYN报文段&#xff08;第一次握手&#xff09;&#xff0c;以表示请…

Mininet学习记录(常用命令+创建网络拓扑+OpenDaylight显示拓扑结构)

目录 1.Mininet简介2.Mininet常用命令2.1创建网络拓扑常用参数2.2常用的内部交换命令 3.创建网络拓扑的三种方式3.1通过命令行创建3.2通过miniedit可视化界面创建3.3通过python脚本创建 4.问题总结 1.Mininet简介 Mininet 是由一些虚拟的终端节点 (end-hosts) 、交换机、路由器…

【STM32】TIM定时器

第一部分&#xff1a;定时器基本定时的功能&#xff1b; 第二部分&#xff1a;定时器的输出比较功能&#xff1b; 第三部分&#xff1a;定时器输入捕获的功能&#xff1b; 第四部分&#xff1a;定时器的编码接口。 1 TIM简介 TIM&#xff08;Timer&#xff09;定时器&#…

【数据库】数据库基于封锁机制的调度器,使冲突可串行化,保障事务和调度一致性

封锁使可串行化 ​专栏内容&#xff1a; 手写数据库toadb 本专栏主要介绍如何从零开发&#xff0c;开发的步骤&#xff0c;以及开发过程中的涉及的原理&#xff0c;遇到的问题等&#xff0c;让大家能跟上并且可以一起开发&#xff0c;让每个需要的人成为参与者。 本专栏会定期更…

《地理信息系统原理》笔记/期末复习资料(8. 数字高程模型)

目录 8. 数字高程模型 8.1 概述 8.1.1 数字高程模型概念 8.1.2 数字高程模型特点 8.2 DEM数据分布特征 8.2.1 格网状数据 8.2.2 离散数据 8.3 DEM的表示方法 8.3.1 数学方法 8.3.2 图形方法 8.3.3 DEM三维表达方法 8.4 TIN的生成方法 8.4.1 人工方法 8.4.2 程序自…