linux系统编程重点复习--线程同步

目录

复习目标:

1 互斥锁

1.1互斥锁的使用步骤

1.2 练习

1.3 死锁

2 读写锁

3 条件变量

4 信号量


复习目标:

  • 熟练掌握互斥量的使用
  • 说出什么叫死锁以及解决方案
  • 熟练掌握读写锁的使用
  • 熟练掌握条件变量的使用
  • 理解条件变量实现的生产消费者模型
  • 理解信号量实现的生产消费者模型

1 互斥锁

1.1互斥的使用步骤

  • 第1步:创建一把互斥锁
  • pthread_mutex_t mutex;
  • 初始化互斥锁
  • pthread_mutex_init(&mutex);---相当于mutex=1
  • 在代码中寻找共享资源(也称为临界区)

pthread_mutex_lock(&mutex);  -- mutex = 0

[临界区代码]

pthread_mutex_unlock(&mutex); -- mutex = 1

  • 释放互斥锁资源

pthread_mutex_destroy(&mutex);

注意:必须在所有操作共享资源的线程上都加上锁否则不能起到同步的效果

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <sys/types.h>

//定义一把锁
pthread_mutex_t mutex;

void *mythread1(void *args)
{

    while (1)
    {

        //加锁
        pthread_mutex_lock(&mutex);
        printf("hello ");
        sleep(rand() % 3);

        printf("world\n");

        //解锁
        pthread_mutex_unlock(&mutex);
        sleep(rand() % 3);
    }

    pthread_exit(NULL);
}

void *mythread2(void *args)
{
    while (1)
    {

        //解锁
        pthread_mutex_lock(&mutex);
        printf("HELLO");
        sleep(rand() % 3);

        printf("WORLD\n");
        //解锁
        pthread_mutex_unlock(&mutex);
        sleep(rand() % 3);
    }
    pthread_exit(NULL);
}

int main()
{

    int ret;
    pthread_t thread1;
    pthread_t thread2;

    //随机数种子
    srand(time(NULL));
    //互斥锁初始化
    pthread_mutex_init(&mutex, NULL);

    ret = pthread_create(&thread1, NULL, mythread1, NULL);
    if (ret != 0)
    {
        printf("pthread_create error ,[%s]\n", strerror(ret));
        return -1;
    }
    ret = pthread_create(&thread2, NULL, mythread2, NULL);
    if (ret != 0)
    {
        printf("pthread_create error ,[%s]\n", strerror(ret));
        return -1;
    }
    //等待线程结束
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    //释放互斥锁
    pthread_mutex_destroy(&mutex);

    system("pause");
    return 0;
}

 

1.2 练习

  • 编写思路:

1 定义一把互斥锁,应该为一全局变量

pthread_mutex_t mutex;

2 在main函数中对mutex进行初始化

pthread_mutex_init(&mutex, NULL);

3 创建两个线程,在两个线程中加锁和解锁

4 主线程释放互斥锁资源

pthread_mutex_destroy(&mutex);

1.3 死锁

死锁并不是linux提供给用户的一种使用方法,而是由于用户使用互斥锁不当引起的一种现象。

  • 常见的死锁有两种:
  • 第一种:自己锁自己,如下图代码片段

 第二种 线程A拥有A锁,请求获得B锁;线程B拥有B锁,请求获得A锁,这样造成线程A和线程B都不释放自己的锁,而且还想得到对方的锁,从而产生死锁,如下图所示:

  • 如何解决死锁:
  • 让线程按照一定的顺序去访问共享资源
  • 在访问其他锁的时候,需要先将自己的锁解开
  • 调用pthread_mutex_trylock,如果加锁不成功会立刻返回

2 读写锁

  • 什么是读写锁
  • 读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。
  • 读写锁使用场合
  • 读写锁非常适合于对数据结构读的次数远大于写的情况。
  • 读写锁特性
  • 读写锁是“写模式加锁”时,解锁前,所有对该锁加锁的线程都会被阻塞。
  • 读写锁是“读模式加锁”时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
  • 读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高
  • 读写锁场景练习:
  1. 线程A加写锁成功, 线程B请求读锁
  • 线程B阻塞
  1. 线程A持有读锁, 线程B请求写锁
  • 线程B阻塞
  1. 线程A拥有读锁, 线程B请求读锁
  • 线程B加锁成功
  1. 线程A持有读锁, 然后线程B请求写锁, 然后线程C请求读锁
  • B阻塞,c阻塞 - 写的优先级高
  • A解锁,B线程加写锁成功,C继续阻塞
  • B解锁,C加读锁成功
  1. 线程A持有写锁, 然后线程B请求读锁, 然后线程C请求写锁
  • BC阻塞
  • A解锁,C加写锁成功,B继续阻塞
  • C解锁,B加读锁成功
  • 读写锁总结

读并行,写独占,当读写同时等待锁的时候写的优先级高

  • 读写锁主要操作函数
  1. 定义一把读写锁
  1. pthread_rwlock_t rwlock;
  1. 初始化读写锁
  • int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
  • 函数参数
  1. rwlock-读写锁
  2. attr-读写锁属性,传NULL为默认属性
  • 销毁读写锁

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);        

  • 加读锁

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);

  • 练习:3个线程不定时写同一全局资源,5个线程不定时读同一全局资源。
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>

//读写锁测试程序
int number = 0;

//定义一把读写锁
pthread_rwlock_t rwlock;

//读写锁的回调函数
void *thread_write(void *arg)
{

    int i = *(int *)arg;
    int cur;

    while (1)
    {
        //加读写锁
        pthread_rwlock_wrlock(&rwlock);

        cur = number;
        cur++;
        number = cur;
        printf("[%d]-W:[%d]\n", i, cur);

        //解锁
        pthread_rwlock_unlock(&rwlock);
        sleep(rand() % 3);
    }
}

//读回调函数
void *thread_read(void *arg)
{

    int i = *(int *)arg;
    int cur;

    while (1)
    {

        //加读锁
        pthread_rwlock_rdlock(&rwlock);
        cur = number;
        printf("[%d]-R:[%d]\n", i, cur);

        //解锁
        // pthread_rwlock_unlock(&rwlock);
        pthread_rwlock_unlock(&rwlock);
        sleep(rand() % 3);
    }
}

int main()
{

    int n = 8;
    int i = 0;
    int arr[8];
    pthread_t thread[8];

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

    //创建3个写子线程

    for (i = 0; i < 3; i++)
    {
        arr[i] = i;
        pthread_create(&thread[i], NULL, thread_write, &arr[i]);
    }

    //创建5个读子线程
    for (i = 3; i < n; i++)
    {
        arr[i] = i;
        pthread_create(&thread[i], NULL, thread_read, &arr[i]);
    }

    //回收子线程
    int j = 0;
    for (j = 0; j < n; j++)
    {
        pthread_join(thread[j], NULL);
    }

    //释放锁
    pthread_rwlock_destroy(&rwlock);

    system("pause");
    return 0;
}

  • 3 条件变量

  • 条件本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。
  • 使用互斥量保护共享数据;
  • 使用条件变量可以使线程阻塞, 等待某个条件的发生, 当条件满足的时候解除阻塞.
  • 条件变量的两个动作:
  • 条件不满足, 阻塞线程
  • 条件满足, 通知阻塞的线程解除阻塞, 开始工作.
  • 条件变量相关函数
  • pthread_cond_t  cond;
  • 定义一个条件变量
  • int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
  • 函数描述:初始化条件变量
  • 函数参数: 
  • 函数返回值:成功返回0, 失败返回错误号
  • int pthread_cond_destroy(pthread_cond_t *cond);
  • 函数描述: 销毁条件变量
  • 函数参数: 条件变量
  • 返回值: 成功返回0, 失败返回错误号
  • int pthread_cond_wait(pthread_cond_t *restrict cond,
  • 函数描述: 条件不满足, 引起线程阻塞并解锁;
  • 函数参数:
  • 函数返回值: 成功返回0, 失败返回错误号
  •  int pthread_cond_signal(pthread_cond_t *cond);
  • 函数描述: 唤醒至少一个阻塞在该条件变量上的线程
  • 函数参数: 条件变量
  • 函数返回值: 成功返回0, 失败返回错误号

cond: 条件变量

attr: 条件变量属性, 通常传NULL

               pthread_mutex_t *restrict mutex);

          条件满足, 解除线程阻塞, 并加锁

cond: 条件变量

mutex: 互斥锁变量

使用条件变量的代码片段

上述代码中,生产者线程调用pthread_cond_signal函数会使消费者线程在pthread_cond_wait处解除阻塞。

//使用条件变量实现生产者和消费者模型
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>

typedef struct node
{
    int data;
    struct node *next;
} NODE;

NODE *head = NULL;

//定义一把锁
pthread_mutex_t mutex;

//定义条件变量
pthread_cond_t cond;

//生产者线程
void *producer(void *arg)
{

    NODE *pNode = NULL;

    while (1)
    {

        pNode = (NODE *)malloc(sizeof(NODE));
        if (pNode == NULL)
        {
            perror("malloc error");
            exit(-1);
        }

        pNode->data = rand() % 1000;
        printf("P:[%d]\n", pNode->data);

        //加锁
        pthread_mutex_lock(&mutex);

        pNode->next = head;
        head = pNode;

        //解锁
        pthread_mutex_unlock(&mutex);

        //通知消费者线程解除阻塞
        pthread_cond_signal(&cond);
        sleep(rand() % 3);
    }
}

//消费者线程
void *consumer(void *arg)
{

    NODE *pNode = NULL;
    while (1)
    {
        //加锁
        pthread_mutex_lock(&mutex);

        if (head == NULL)
        {
            //若条件不满足,需要阻塞等待
            //若条件不满足,则阻塞等待并解锁;
            //若条件满足(被生成者线程调用pthread_cond_signal函数通知),解除阻塞并加锁
            pthread_cond_wait(&cond, &mutex);
        }

        printf("C:[%d]\n", head->data);
        pNode = head;
        head = head->next;

        //解锁
        pthread_mutex_unlock(&mutex);
        free(pNode);
        pNode = NULL;

        sleep(rand() % 3);
    }
}

int main()
{

    int ret;
    pthread_t thread1;
    pthread_t thread2;

    //初始化互斥锁
    pthread_mutex_init(&mutex, NULL);

    //条件变量初始化

    pthread_cond_init(&cond, NULL);

    //创建生产者线程
    ret = pthread_create(&thread1, NULL, producer, NULL);

    if (ret != 0)
    {
        printf("pthread_create error,[%s]\n", strerror(ret));
        return -1;
    }

    //创建消费者线程
    ret = pthread_create(&thread2, NULL, consumer, NULL);
    if (ret != 0)
    {
        printf("pthread_create error,[%s]\n", strerror(ret));
        return -1;
    }

    //等待线程结束
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    //释放互斥锁
    pthread_mutex_destroy(&mutex);

    //释放条件变量
    pthread_cond_destroy(&cond);

    system("pause");
    return 0;
}

多个生产者和消费者

//使用条件变量实现多个生产者和消费者模型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
typedef struct node
{
	int data;
	struct node *next;
}NODE;

NODE *head = NULL;

//定义一把锁
pthread_mutex_t mutex;

//定义条件变量
pthread_cond_t cond;

//生产者线程
void *producer(void *arg)
{
	NODE *pNode = NULL;
	int n = *(int *)arg;
	while(1)
	{
		//生产一个节点
		pNode = (NODE *)malloc(sizeof(NODE));
		if(pNode==NULL)
		{
			perror("malloc error");
			exit(-1);
		}
		pNode->data = rand()%1000;
		printf("P[%d]:[%d]\n", n, pNode->data);

		//加锁
		pthread_mutex_lock(&mutex);

		pNode->next = head;
		head = pNode;

		//解锁
		pthread_mutex_unlock(&mutex);

		//通知消费者线程解除阻塞
		pthread_cond_signal(&cond);

		sleep(rand()%3);
	}
}


//消费者线程
void *consumer(void *arg)
{
	NODE *pNode = NULL;
	int n = *(int *)arg;
	while(1)
	{
		//加锁
		pthread_mutex_lock(&mutex);

		if(head==NULL)
		{
			//若条件不满足,需要阻塞等待
			//若条件不满足,则阻塞等待并解锁;
			//若条件满足(被生成者线程调用pthread_cond_signal函数通知),解除阻塞并加锁 
			pthread_cond_wait(&cond, &mutex);
		}

		if(head==NULL)
		{
			//解锁
			pthread_mutex_unlock(&mutex);	
			continue;
		}

		printf("C[%d]:[%d]\n", n, head->data);	
		pNode = head;
		head = head->next;

		//解锁
		pthread_mutex_unlock(&mutex);

		free(pNode);
		pNode = NULL;

		sleep(rand()%3);
	}
}

int main()
{
	int ret;
	int i = 0;
	pthread_t thread1[5];
	pthread_t thread2[5];

	//初始化互斥锁
	pthread_mutex_init(&mutex, NULL);

	//条件变量初始化
	pthread_cond_init(&cond, NULL);

	int arr[5];
	for(i=0; i<5; i++)
	{
		arr[i]= i;
		//创建生产者线程
		ret = pthread_create(&thread1[i], NULL, producer, &arr[i]);
		if(ret!=0)
		{
			printf("pthread_create error, [%s]\n", strerror(ret));
			return -1;
		}

		//创建消费者线程
		ret = pthread_create(&thread2[i], NULL, consumer, &arr[i]);
		if(ret!=0)
		{
			printf("pthread_create error, [%s]\n", strerror(ret));
			return -1;
		}
	}

	//等待线程结束
	for(i=0; i<5; i++)
	{
		pthread_join(thread1[i], NULL);
		pthread_join(thread2[i], NULL);
	}

	//释放互斥锁
	pthread_mutex_destroy(&mutex);

	//释放条件变量
	pthread_cond_destroy(&cond);

	return 0;
}

4 信号量

1 信号量介绍

信号量相当于多把锁, 可以理解为是加强版的互斥锁

2 相关函数

定义信号量 sem_t sem; int sem_init(sem_t *sem, int pshared, unsigned int value);

pshared: 0表示线程同步, 1表示进程同步

value: 最多有几个线程操作共享数据

sem: 信号量变量

 3 信号量代码片段:

  • 函数描述: 初始化信号量
  • 函数参数:
  • 函数返回值:成功返回0, 失败返回-1, 并设置errno值
  • int sem_wait(sem_t *sem);
  • 函数描述: 调用该函数一次, 相当于sem--, 当sem为0的时候, 引起阻塞
  • 函数参数: 信号量变量
  • 函数返回值: 成功返回0, 失败返回-1, 并设置errno值
  • int sem_post(sem_t *sem);
  • 函数描述: 调用一次, 相当于sem++
  • 函数参数: 信号量变量
  • 函数返回值: 成功返回0, 失败返回-1, 并设置errno值
  • int sem_trywait(sem_t *sem);
  • 函数描述: 尝试加锁, 若失败直接返回, 不阻塞
  • 函数参数: 信号量变量
  • 函数返回值: 成功返回0, 失败返回-1, 并设置errno值
  • int sem_destroy(sem_t *sem);
  • 函数描述: 销毁信号量
  • 函数参数: 信号量变量
  • 函数返回值: 成功返回0, 失败返回-1, 并设置errno值
//使用信号量实现生产者和消费者模型
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
//信号量的头文件
#include <semaphore.h>

typedef struct node
{
    int data;
    struct node *next;
} NODE;

NODE *head = NULL;

//定义信号量
sem_t sem_producer;
sem_t sem_consumer;

//生产者线程

void *producer(void *arg)
{

    NODE *pNode = NULL;
    while (1)
    {

        pNode = (NODE *)malloc(sizeof(NODE));

        if (pNode == NULL)
        {
            perror("malloc error");
            exit(-1);
        }

        pNode->data = rand() % 100;
        printf("P;[%d]\n", pNode->data);

        //加锁
        sem_wait(&sem_producer);

        pNode->next = head;
        head = pNode;

        //解锁
        sem_post(&sem_consumer);
        sleep(rand() % 3);
    }
}

void *consumer(void *arg)
{

    NODE *pNode = NULL;

    while (1)
    {

        //加锁
        sem_wait(&sem_consumer); //相当于--

        printf("C:[%d]\n", head->data);
        pNode = head;
        head = head->next;

        //解锁
        sem_post(&sem_producer); //相当于++

        free(pNode);
        pNode = NULL;

        sleep(rand() % 3);
    }
}

int main()
{

    int ret;
    pthread_t thread1;
    pthread_t thread2;

    //初始化信号量
    sem_init(&sem_producer, 0, 5);
    sem_init(&sem_consumer, 0, 0);

    //创建生产者线程
    ret = pthread_create(&thread1, NULL, producer, NULL);

    if (ret != 0)
    {
        printf("pthread create error,[%s]\n", strerror(ret));
        return -1;
    }
    ret = pthread_create(&thread2, NULL, consumer, NULL);
    if (ret != 0)
    {
        printf("pthread create error,[%s]\n", strerror(ret));
        return -1;
    }

    //等待线程结束
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);

    //释放信号量资源
    sem_destroy(&sem_producer);
    sem_destroy(&sem_consumer);

    system("pause");
    return 0;
}

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

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

相关文章

python简单的病毒编程代码,如何用python写一个病毒

大家好&#xff0c;本文将围绕python简单的病毒编程代码展开说明&#xff0c;如何用python做恶搞病毒是一个很多人都想弄明白的事情&#xff0c;想搞清楚如何用python写一个病毒需要先了解以下几个事情。 1、Python能不能写病毒 国家计算机病毒应急处理中心通过对互联网的监测…

算法leetcode|64. 最小路径和(rust重拳出击)

文章目录 64. 最小路径和&#xff1a;样例 1&#xff1a;样例 2&#xff1a;提示&#xff1a; 分析&#xff1a;题解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 64. 最小路径和&#xff1a; 给定一个包含非负整数的 m x n 网…

windows 安装 mongodb 数据库

软件下载 访问官方的下载地址&#xff1a; https://www.mongodb.com/try/download/community &#xff0c;然后选择对应的版本进行下载 下载好了之后双击进行安装 软件安装 1、点击 next 点击下一步 2、勾选接受协议&#xff0c;点击 next 3、第三页有两个选项&#x…

redisson分布式锁学习

什么是分布式锁? 当有多个线程并发访问同一共享数据时,如果多个线程同时都去修改这个共享数据,且修改操作不是原子操作,就很有可能出现线程安全问题&#xff0c;而产生线程安全问题的根本原因是缺乏对共享数据访问的同步和互斥。 为了解决这个问题&#xff0c;通常我们的做法…

P2P网络NAT穿透原理(打洞方案)

1.关于NAT NAT技术&#xff08;Network Address Translation&#xff0c;网络地址转换&#xff09;是一种把内部网络&#xff08;简称为内网&#xff09;私有IP地址转换为外部网络&#xff08;简称为外网&#xff09;公共IP地址的技术&#xff0c;它使得一定范围内的多台主机只…

SpringBoot超级详解

1.父工程的父工程 在父工程的父工程中的核心依赖&#xff0c;专门用来版本管理的 版本管理。 2.父工程 资源过滤问题&#xff0c;都帮解决了&#xff0c;什么配置文件&#xff0c;都已经配置好了&#xff0c;资源过滤问题是帮助&#xff0c;过滤解决让静态资源文件能够过滤到…

别再分库分表了,来试试它吧

什么是NewSQL传统SQL的问题 升级服务器硬件数据分片NoSQL 的问题 优点缺点NewSQL 特性NewSQL 的主要特性三种SQL的对比TiDB怎么来的TiDB社区版和企业版TIDB核心特性 水平弹性扩展分布式事务支持金融级高可用实时 HTAP云原生的分布式数据库高度兼容 MySQLOLTP&OLAP&#xff…

openssl/bn.h: No such file or directory

报错截图 解决方法 ubuntu apt install libssl-dev -y centos yum install openssl-devel -y

第六章 支持向量机

文章目录 支持向量机间隔和支持向量对偶问题问题推导SMO 核函数实验 支持向量机 ⽀持向量机&#xff08;Support Vector Machines&#xff0c;SVM&#xff09; 优点&#xff1a;泛化错误率低&#xff0c;计算开销不⼤&#xff0c;结果易解释。缺点&#xff1a;对参数调节和核…

Python 教程之标准库概览

概要 Python 标准库非常庞大&#xff0c;所提供的组件涉及范围十分广泛&#xff0c;使用标准库我们可以让您轻松地完成各种任务。 以下是一些 Python3 标准库中的模块&#xff1a; 「os 模块」 os 模块提供了许多与操作系统交互的函数&#xff0c;例如创建、移动和删除文件和…

CLIP-GCD: Simple Language Guided Generalized Category Discovery(论文翻译)

CLIP-GCD: Simple Language Guided Generalized Category Discovery 摘要1 介绍2 相关工作2.1 NCD2.2 无监督聚类2.3 自监督和多模态预训练 3 方法3.1 GCD 问题设置3.2 我们的方法3.2.1 使用CLIP 在GCD 4 实验4.1 模型架构细节4.2 数据集和评估4.3 和最先进水平比较4.4 分析4.5…

Linux下 Docker容器引擎基础(1)

简述&#xff1a; Docker的容器技术可以在一台主机上轻松为任何应用创建一个轻量级的、可移植的、自给自足的容器。通过这种容器打包应用程序&#xff0c;意味着简化了重新部署、调试这些琐碎的重复工作&#xff0c;极大的提高了工作效率。例如&#xff1a;项目从腾讯云迁移阿…

尚硅谷大数据项目《在线教育之采集系统》笔记002

视频地址&#xff1a;尚硅谷大数据项目《在线教育之采集系统》_哔哩哔哩_bilibili 目录 P032 P033 P033 P034 P035 P036 P032 P033 # 1、定义组件&#xff0c;为各组件命名 a1.sources r1 a1.channels c1 a1.sinks - k1# 2、配置sources&#xff0c;描述source a1.sour…

ALLEGRO之Route菜单

本文主要介绍了ALLEGRO的Route菜单。 &#xff08;1&#xff09;Connect&#xff1a;走线&#xff1b; &#xff08;2&#xff09;Slide&#xff1a;推挤&#xff1b; &#xff08;3&#xff09;Timing Vision&#xff1a;等长设计时使用&#xff1f;暂不清楚&#xff1b; &…

oracle,获取每日24*60,所有分钟数

前言&#xff1a; 为规范用户的时间录入&#xff0c;因此我们采用下拉的方式&#xff0c;让用户选择需要的时间&#xff0c;因此我们需要将一天24小时的时间拆分为类似00:00,00:01...23:00,23:01,23:59。因此我们需要生成24*601440行的下拉复选值。具体效果如下图所示。 思路 1…

C语言字串函数、内存函数介绍以及模拟实现

目录 前言 本期内容介绍&#xff1a; 一、字符串函数 strlen介绍 strlen 模拟实现&#xff08;三种方式&#xff09; 方法一&#xff1a;计数器法 方法二&#xff1a;递归法&#xff08;不创建临时变量法&#xff09; 方法三&#xff1a;指针-指针 strcpy介绍 strcpy模…

SSIS对SQL Server向Mysql数据转发表数据 (完结)

1、对于根据主键进行更新和插入新的数据&#xff0c;根据前面的文章&#xff0c;对于组件已经很熟悉了&#xff0c;我们直接加入一个 查找 组件 &#xff0c;如下所示 2、右键点击"查找"&#xff0c;然后“编辑” &#xff0c;选择“连接”,选中我们的目标连接器&…

Vue2 第七节 Vue监测数据更新原理

&#xff08;1&#xff09;Vue会监视data中所有层次的数据 &#xff08;2&#xff09;如何监测对象中的数据 通过setter实现监视&#xff0c;且要在new Vue时传入要监测的数据对象中后追加的属性&#xff0c;Vue默认不做响应式处理如果要给后添加的属性做响应式&#xff0c;使…

Docker私有仓库

Docker私有仓库 Docker官方的Docker hub&#xff08;https://hub.docker.com&#xff09;是一个用于管理公共镜像的仓库&#xff0c;我们可以从上面拉取镜像到本地&#xff0c;也可以把我们自己的镜像推送上去。但是&#xff0c;有时候我们的服务器无法访问互联网&#xff0c;…

初阶数据结构——二叉树题目

文章目录 一、单值二叉树二、检查两颗树是否相同三、另一棵树的子树四、二叉树的前序遍历五、对称二叉树 一、单值二叉树 单值二叉树 如果二叉树每个节点都具有相同的值&#xff0c;那么该二叉树就是单值二叉树。只有给定的树是单值二叉树时&#xff0c;才返回 true&#xff…
最新文章