C多线程、锁、同步、信号量

news2024/9/21 14:48:34

文章目录

    • 一 线程函数
      • 1.1 创建线程
      • 1.2 线程退出
      • 1.3 线程回收
      • 1.4 线程分离:
      • 1.5 其他线程函数
        • 1.5.1 线程取消
        • 1.5.2 线程ID比较
    • 二 线程同步
      • 2.1 互斥锁
        • 2.1.1定义
        • 2.1.2 初始化
        • 2.1.3 销毁
        • 2.1.4 加锁 、 常试锁、解锁
        • 2.1.5 互斥锁使用
      • 2.2 死锁
      • 2.3 如何避免死锁
      • 2.4 读写锁
        • 2.4.1 基本信息(特点和记录信息)
        • 2.4.2 类型定义 、 初始化函数 和 销毁函数
        • 2.4.3 读锁 和 尝试读函数
        • 2.4.4 写锁
        • 2.4.5 解锁
        • 2.4.6 读写锁的使用
      • 2.5 条件变量
        • 2.5.1 数据类型、初始化 和 释放
        • 2.5.2 wait 和 timewait
        • 2.5.3 signal 和 broadcast
      • 2.6 信号量
        • 2.6.1 类型定义、初始化 和 销毁
        • 2.6.2 sem_wai 和 sem_trywait
        • 2.6.3 sem_timedwait
        • 2.6.4 sem_getvalue
        • 2.6.5 sem_post
        • 2.6.6 使用

一 线程函数

1.1 创建线程

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

thread 是线程变量地址
attr是线程属性,一般为NULL
start_rount 是函数指针
arg 是函数指针指向函数的参数

1.2 线程退出

void pthread_exit(void *retval);

retval可以把退出值带回去,例子见线程回收

1.3 线程回收

int pthread_join(pthread_t thread, void **retval);
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>

struct Test
{
    int num;
    int age;
};

void* func(void* arg)
{
    struct Test *temp = (struct Test*)arg;

    for(int i = 0 ;i < 5; i++)
    {
        printf("in_sonthread i = %d\n",i);
    }

    temp->age = 12;
    temp->num = 21;

    pthread_exit(temp);
    return NULL;
}



int main()
{
    pthread_t pid;
    struct Test jxk;
     struct Test* jxktemp = &jxk;

    void* ans;

    pthread_create(&pid,NULL,func,&jxk);
    for(int i = 0; i < 5 ;i++)
    {
        printf("in_main,i = %d\n",i);
    }

    pthread_join(pid,&ans);
    struct Test* jxk2 = (struct Test*)ans;
    printf("num is %d,age is %d\n",jxk2->num,jxk2->age);


    return 0;
}   

在这里插入图片描述

1.4 线程分离:

某些情况下,程序的主线程有自己的其他业务,如果让主线程负责子线程的资源回收,调用pthrad_join()只要子线程不退出,主线程就会一致阻塞,主线程的任务也不能执行了。

线程库提供了线程分离函数 pthread_detach(),调用这个函数后指定的子线程可以和主线程分离,当子线程退出是,其占用的内核资源就被操作系统的其他进程接管并回收了。线程分离后,在主线程中使用pthread_join() 就会收不到子线程资源了。

int pthread_detach(pthread_t thread);
#include<stdio.h>
#include<pthread.h>
#include<unistd.h>

struct Test
{
    int num;
    int age;
};

void* func(void* arg)
{
    struct Test *temp = (struct Test*)arg;

    for(int i = 0 ;i < 5; i++)
    {
        printf("in_sonthread i = %d\n",i);
    }

    temp->age = 12;
    temp->num = 21;
    
    printf("子线程:%ld\n",pthread_self());
    pthread_exit(temp);
    return NULL;
}



int main()
{
    pthread_t pid;
    struct Test jxk;
    pthread_create(&pid,NULL,func,&jxk);
    printf("主线程:%ld\n",pthread_self());

    pthread_detach(pid);
    pthread_exit(NULL);

    return 0;
}   

主线程打印了自己的id后便退出了线程,子线程会继续运行
在这里插入图片描述

1.5 其他线程函数

1.5.1 线程取消

线程取消就是在某些特定情况下,在一个线程中杀死另一个线程。使用这个函数杀死一个线程需要分两步:

  1. 在线程A中调用线程取消函数 pthread_cancel,指定杀死线程B,这时候线程B是死不了的
  2. 在线程B中进行一个系统调用(从用户区切换到内核区),否则线程B可以一直运行。
int pthread_cancel(pthread_t thread);

1.5.2 线程ID比较

在Linux中,线程ID本质就是一个无符号长整型,因此可以直接使用比较操作符比较两个线程的ID。但是线程库是可以跨平台使用的,在某些平台上 pthread_t 可能不是一个单纯的整型,这种情况下比较两个线程的ID必须要使用线程比较函数。

 int pthread_equal(pthread_t t1, pthread_t t2);

二 线程同步

假设有4个线程ABCD,当前一个线程A对内存中的共享资源进行访问的时候,其他线程BCD都不可以对这块内存进行操作,直到线程A对这块内存访问完毕为止,BCD中的一个才能访问这块内存,剩下的两个需要继续阻塞等待,以此类推,直到所有的线程都完成对这块内存的操作。

线程内对这块内存的访问方式就称之为线程同步。所谓线程同步不是说多个线程同时对内存进行访问,而是按照先后顺序依次进行的。

例子:两个线程数数字

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>

int Number;

void* func1(void* arg)
{
    for(int i = 0; i < 50; i++)
    {
        int cur = Number;
        cur++;
        usleep(100);
        Number = cur;
        printf("thread A %ld, number is %d\n",pthread_self(),Number);
        
    }
    
    return NULL;
}

void* func2(void* arg)
{
    for(int i = 0; i < 50; i++)
    {
        int cur = Number;
        cur++;
        Number = cur;
        usleep(50);
        printf("thread B %ld, number is %d\n",pthread_self(),Number);
    }

    return NULL;
}




int main()
{
    pthread_t pidA,pidB;
    pthread_create(&pidA,NULL,func1,NULL);
    pthread_create(&pidB,NULL,func2,NULL);
    printf("main thread %ld\n",pthread_self());
    sleep(1);
}   

在这里插入图片描述

2.1 互斥锁

2.1.1定义

pthread_mutex_t mutex;

2.1.2 初始化

int pthread_mutex_init(pthread_mutex_t *restrict mutex,
           const pthread_mutexattr_t *restrict attr);
//attr 是锁属性,一般为NULL

restrict 是一个关键字,用来修饰指针,只有这个关键字修饰的指针可以访问指向的内存地址,其他指针都不行。
// p = mutex, p也不能访问mutex的地址

2.1.3 销毁

int pthread_mutex_destroy(pthread_mutex_t *mutex);

2.1.4 加锁 、 常试锁、解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//不会阻塞,会返回一个错误号

int pthread_mutex_unlock(pthread_mutex_t *mutex);

2.1.5 互斥锁使用

解决上诉数数问题:

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>

int Number;
pthread_mutex_t numMutex;

void* func1(void* arg)
{
    for(int i = 0; i < 50; i++)
    {
        pthread_mutex_lock(&numMutex);
        int cur = Number;
        cur++;
        Number = cur;
        printf("thread A %ld, number is %d\n",pthread_self(),Number);
        pthread_mutex_unlock(&numMutex);
        usleep(100);
        
    }
    
    return NULL;
}

void* func2(void* arg)
{
    for(int i = 0; i < 50; i++)
    {
        pthread_mutex_lock(&numMutex);
        int cur = Number;
        cur++;
        Number = cur;
        printf("thread B %ld, number is %d\n",pthread_self(),Number);
        pthread_mutex_unlock(&numMutex);
        usleep(50);
    }

    return NULL;
}




int main()
{
    pthread_t pidA,pidB;
    if(pthread_mutex_init(&numMutex,NULL) != 0)
    {
        fprintf(stderr,"init mutex lock failed;\n");
        return 0;
    }
    pthread_create(&pidA,NULL,func1,NULL);
    pthread_create(&pidB,NULL,func2,NULL);
    printf("main thread %ld\n",pthread_self());
    
    pthread_join(pidA,NULL);
    pthread_join(pidB,NULL);
    pthread_mutex_destroy(&numMutex);
}   

在这里插入图片描述

2.2 死锁

造成死锁的场景:
多个线程访问共享资源时,需要加锁,如果锁使用不当,就会造成死锁这种现象。如果线程死锁造成的后果是:所有的线程被阻塞,并且进行的阻塞是无法解开的(能解开就不会被阻塞了);

  • 加锁之后忘记解锁
  • 加锁之后,在解锁之前,程序由其他出口跳出当前函数逻辑(异常、满足条件的return等)
  • 重复加锁,造成死锁

2.3 如何避免死锁

  • 避免多次锁定,多检查
  • 对共享资源访问完毕之后,一定要解锁,或者在加锁的时候先使用trylock
  • 如果程序中有多把锁,可以控制对锁的访问顺序(顺序访问资源、但在有些情况下做不到),另外也可以在对其他互斥锁做加锁操作之前,先释放当前线程拥有的互斥锁。
  • 项目程序可以引入一些专门用于死锁检测的模块。

2.4 读写锁

2.4.1 基本信息(特点和记录信息)

读写锁是互斥锁的升级版,在做读操作的时候可以提高程序的执行效率,如果所有的线程都是做读操作,那么读是并行的,但是使用互斥锁,读操作也是串行的。

**读写锁是一把锁。**类型是 pthrad_rwlock_t,有了类型就可以创建一把互斥锁了。

pthread_rwlock_t rwlock;

这把锁记录了这些信息:

  • 锁的状态:锁定/打开
  • 锁定的是什么操作:读操作/写操作,使用读写锁锁定读操作,需要先解锁才能去锁定写操作,反之亦然
  • 哪个线程把这把锁锁上了。

读写锁的特点:

  • 使用读写锁的读锁锁定了临界区,线程对临界区的访问是并行的,读锁是共享的。
  • 使用读写锁的写锁锁定了临界区,线程对临界区的访问是串行的,写锁是独占的。
  • 使用读写锁分别对两个临界区加了读锁和写锁,两个线程要同时访问两个临界区,访问写锁临界区的线程继续运行,访问读锁的临界区线程阻塞,因为写锁的有点急比读锁高。

2.4.2 类型定义 、 初始化函数 和 销毁函数

//类型
pthread_rwlock_t rwlock;

//初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
           const pthread_rwlockattr_t *restrict attr);
//rwlock,读写锁地址
//attr 读写锁属性,一般为NULL

//销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

2.4.3 读锁 和 尝试读函数

//在程序中进行读操作
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
//调用这个函数,如果读写锁是打开的,那么加锁成功;
//如果读写锁已经锁定了读操作,依然可以加锁成功,因为读锁是共享的;
//如果读写锁已经锁定了写操作,调用这个函数会被阻塞。

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
//调用这个函数,如果读写锁是打开的,那么加锁成功;
//如果读写锁已经锁定了读操作,依然可以加锁成功,因为读锁是共享的;
//如果读写锁已经锁定了写操作,调用这个函数加锁失败,但是线程不会被阻塞,可以在程序中对函数返回值进行判断,添加加锁失败后的处理动作。

2.4.4 写锁

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
//调用者函数,如果读写锁是打开的,那么加锁成功;
//如果已经锁定了读操作 或 写操作,调用这个函数线程会阻塞。

2.4.5 解锁

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

2.4.6 读写锁的使用

例:8个线程同时操作一个全局变量,三个线程不定时写资源,5个线程不定时读资源。

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>

int Number;
pthread_rwlock_t rwLock;

void* readNum(void* arg)
{
    for(int i = 0; i < 50; i++)
    {
        pthread_rwlock_rdlock(&rwLock);
        printf("Thread read, id = %ld, number = %d\n",pthread_self(),Number);
        pthread_rwlock_unlock(&rwLock);
        usleep(rand() % 5);
    }
    
    return NULL;
}

void* writeNum(void* arg)
{
    for(int i = 0; i < 50; i++)
    {
        pthread_rwlock_wrlock(&rwLock);
        int cur = Number;
        cur++;
        Number = cur;
         printf("Thread write, id = %ld, number = %d\n",pthread_self(),Number);
        pthread_rwlock_unlock(&rwLock);
        usleep(5);
    }

    return NULL;
}




int main()
{
    pthread_t pidA[5],pidB[3];
    if(pthread_rwlock_init(&rwLock,NULL) != 0)
    {
        fprintf(stderr,"init mutex lock failed;\n");
        return 0;
    }

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

    for(int i = 0; i < 3; i++)
    {
        pthread_create(&pidB[i],NULL,writeNum,NULL);
    }

    //阻塞,资源回收
    for(int i = 0; i < 5; i++)
    {
        pthread_join(pidA[i],NULL);
    }

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

    pthread_rwlock_destroy(&rwLock);

    printf("main thread %ld\n",pthread_self());

    return 0;
}   

在这里插入图片描述

2.5 条件变量

严格意义上说,条件变量的主要作用不是用来处理线程同步,而是进行线程的阻塞。

2.5.1 数据类型、初始化 和 释放

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_destroy(pthread_cond_t *cond);
       

2.5.2 wait 和 timewait

int pthread_cond_wait(pthread_cond_t *restrict cond,
    pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex,
           const struct timespec *restrict abstime);
// 只会阻塞一定的时间长度,时间过了之后就会继续往下执行

time_t mytim = time(NULL);
struct timespec tmsp;
tmsp.tv_nsec = 0;
tmsp.tv_sec = tume(NULL) + 100; //线程阻塞100s

2.5.3 signal 和 broadcast

int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

调用以上两个函数的任意一个,都可以唤醒被 pthread_cond_wait 或者 pthread_cond_timewait 阻塞的线程;
区别在于:

  • pthread_cond_signal 是唤醒最少一个被阻塞的线程(总个数不定);
  • pthread_cond_broadcast是唤醒所有被阻塞的线程。

2.6 信号量

信号量用在多线程多任务同步的,一个线程完成了某一动作就通过信号量告诉别的线程,别的线程再进行某些动作。信号量不一定是锁定某一资源,而是流程上的概念,比如:有AB两个线程,B线程要等A线程完成某一个任务后在进行自己下面的步骤,这个任务并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。

信号量和条件变量一样用于处理生产者和消费者模型,用于阻塞生产者线程或消费者线程的运行,
类型为 sem_t ,对应头文件是 <semaphore.h>

#include<semaphore.h>
sem_t sem;

2.6.1 类型定义、初始化 和 销毁

//类型定义
sem_t sem;

//初始化
int sem_init(sem_t *sem, int pshared, unsigned int value);
// sem:信号量变量地址
// pshared:
//      0:线程同步
//      非0:进程同步
//value: 初始化当前信号量拥有的资源(>=0,如果资源数为0,线程就会被阻塞)

//销毁
int sem_destroy(sem_t *sem);

2.6.2 sem_wai 和 sem_trywait

//调用函数嗲用sem中的资源树就会消耗一个,资源数 -1
int sem_wait(sem_t* sem);

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

//调用函数嗲用sem中的资源树就会消耗一个,资源数 -1
int sem_trywait(sem_t* sem);

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

2.6.3 sem_timedwait

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

struct timespec {
               time_t tv_sec;      /* Seconds */
               long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
           };

2.6.4 sem_getvalue

int sem_getvalue(sem_t *sem, int *sval);


2.6.5 sem_post

int sem_post(sem_t *sem);

2.6.6 使用

例子1:
一个空位,不会出现问题,但是当生产着刚开始给的value大时会出问题,因为访问了公共资源。

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
#include<semaphore.h>

struct Node
{
    int number;
    struct Node* next;
};

struct Node* head = NULL;

sem_t semp,semc;

void* consumerFunc(void *)
{
    while(1)
    {
        //查看是否可以消费
        sem_wait(&semc);

        //消费
        struct Node* node = head;
        printf("消费者,id : %ld, number:%d\n",pthread_self(),node->number);
        head = head->next;
        free(node);

        //消费结束,提示生产者,继续生产
        sem_post(&semp);

        sleep(rand() % 3);

    }


}

void* producerFuc(void *)
{
    while(1)
    {
        //看是否可以生产
        sem_wait(&semp);

        //生产资源
        struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
        newNode->number = rand() % 1000;
        newNode->next = head;
        head = newNode;
        printf("生产者,id: %ld,number: %d\n",pthread_self(),newNode->number);

        //提示消费者可以消费了
        sem_post(&semc);
        sleep(rand() % 3);
    }

    return NULL;
}

int main()
{
    pthread_t consumer[5],producer[5];

    //初始化信号量,只有一个空位供给生产消费
    sem_init(&semp,0,1);
    sem_init(&semc,0,0);

    //创建线程
    for(int i = 0; i < 5; i++)
    {
        pthread_create(&consumer[i],NULL,producerFuc,NULL);
    }

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

    //阻塞回收
    for(int i = 0; i < 5; i++)
    {
        pthread_join(consumer[i],NULL);
    }

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

    //信号量释放
    sem_destroy(&semp);
    sem_destroy(&semc);

    return 0;
}   

加锁解决问题:

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>
#include<semaphore.h>

struct Node
{
    int number;
    struct Node* next;
};

struct Node* head = NULL;

sem_t semp,semc;
pthread_mutex_t mutex;

void* consumerFunc(void *)
{
    while(1)
    {
        //查看是否可以消费
        sem_wait(&semc);
        pthread_mutex_lock(&mutex);
        //加在 sem_wait下,避免死锁

        //消费
        struct Node* node = head;
        printf("消费者,id : %ld, number:%d\n",pthread_self(),node->number);
        head = head->next;
        free(node);

        pthread_mutex_unlock(&mutex);
        //消费结束,提示生产者,继续生产
        sem_post(&semp);

        sleep(rand() % 3);

    }


}

void* producerFuc(void *)
{
    while(1)
    {
        //看是否可以生产
        sem_wait(&semp);
        pthread_mutex_lock(&mutex);
        //生产资源
        struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
        newNode->number = rand() % 1000;
        newNode->next = head;
        head = newNode;
        printf("生产者,id: %ld,number: %d\n",pthread_self(),newNode->number);

        pthread_mutex_unlock(&mutex);
        //提示消费者可以消费了
        sem_post(&semc);
        sleep(rand() % 3);
    }

    return NULL;
}

int main()
{
    pthread_t consumer[5],producer[5];

    //初始化信号量,只有一个空位供给生产消费
    sem_init(&semp,0,1);
    sem_init(&semc,0,0);

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

    //创建线程
    for(int i = 0; i < 5; i++)
    {
        pthread_create(&consumer[i],NULL,producerFuc,NULL);
    }

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

    //阻塞回收
    for(int i = 0; i < 5; i++)
    {
        pthread_join(consumer[i],NULL);
    }

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

    //信号量释放
    sem_destroy(&semp);
    sem_destroy(&semc);

    pthread_mutex_destroy(&mutex);
    return 0;
}   

加锁是加在了sem_wait下,这样可以避免死锁,不然会出现一种情况:
A线程拿到了sem的资源,但是B线程先锁定阻塞在了mutex位置,就会导致最后生产着无法生产,消费者无法消费,死锁了。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

05_从0运行,重定位,初始化,中断再到定时器

总结 这边简单讲讲,代码上电后从0开始发生了什么,为什么要重定位把代码复制到sdram, bin文件前面几条跳转函数都跳转去哪 中断产生后发生什么 重定位问题 1.为什么需要重定位 把程序从一个位置移到另一个位置 叫重定位 可以只重定位部分段的数据 也可以把所有的都重定位到sd…

Linux运维之shell基础

一.流程控制 1.if判断 基本语法&#xff1a; if [ 条件判断式 ] then 程序 elif [ 条件判断式 ] then 程序 else 程序 fi注意事项&#xff1a; ①[ 条件判断式 ]&#xff0c;中括号和条件判断式之间必须有空格②if 后要有空格 例如&#xff0c;现在写一个if.sh脚本文件 #…

echarts关于自定义饼图数据刷新和颜色渲染问题

在使用echarts的自定义饼图Customized Pie时&#xff0c;定义的动态数据会发生颜色无法渲染的问题&#xff0c;如下图所示&#xff1a; 该图表的颜色是根据itemStyle内的color属性而来&#xff0c;如下&#xff1a; itemStyle: {color: #4d90fe, /* 图表的颜色 */shadowBlur:…

【python知识】importlib包详解

importlib — The implementation of import — Python 3.11.3 documentation 目录 一、说明 二、 模块导入简介 2.1 最简单的 importlib用途 2.2 importlib 包的目的有三个 2.3 import_module() 和__import__() 三、高级模块使用 3.1 动态引入 3.2 模块引入检查 3…

SpringBoot整合Mybatis-Plus、Jwt实现登录token设置

Spring Boot整合Mybatis-plus实现登录常常需要使用JWT来生成用户的token并设置用户权限的拦截器。本文将为您介绍JWT的核心讲解、示例代码和使用规范&#xff0c;以及如何实现token的生成和拦截器的使用。 一、JWT的核心讲解 JWT&#xff08;JSON Web Token&#xff09;是一种…

JavaWeb——HTML和CSS

HTML和CSS定义 标记语言 :比如XML:可扩展的标记语言&#xff0c;标签可以自己定义&#xff0c;解析时需要按照定义的规则去解析。 学习目的:掌握常见标签和常见样式的使用 HTML 结构: 特点: 1.不区分大小写&#xff0c;不管是<html>还是<HTML>都是一样的作用 …

错题笔记第一篇

目录 1. strlen的用法2. case3. switch4. 二分查找 1. strlen的用法 正确答案 &#xff1a;C strlen计算的是字符串的长度&#xff0c;二字符串是以\0结尾&#xff0c;而咱们并没有存储\0&#xff0c;后序的空间是未知的&#xff0c;strlen找不到\0就会一直找&#xff0c;所以它…

如何使 VSCode 中 CMake Debug 的输出显示在 cmd 上而不是自带的 debug console

如何使 VSCode 中 CMake Debug 的输出显示在 cmd 上而不是自带的 debug console 首先需要明确的一点是从 VSCode 插件商店下载的 CMake 是默认打印输出的结果在 debug console 中的&#xff0c;就像下面这样&#xff1a; 可以看到&#xff0c;一个问题是在加载 dll 时候会频繁…

82. Python split方法-分割字符串

82. split方法-分割字符串 文章目录 82. split方法-分割字符串1. 什么是split( )函数2. split( )方法的语法格式如下&#xff1a;3. 实操练习4. 列表索引取值知识回顾5. 用split方法分解网址提取有效信息6. 从地址信息中拆分省、市、区信息 1. 什么是split( )函数 split[splɪ…

深度学习模型压缩与优化加速

1. 简介 深度学习&#xff08;Deep Learning&#xff09;因其计算复杂度或参数冗余&#xff0c;在一些场景和设备上限制了相应的模型部署&#xff0c;需要借助模型压缩、系统优化加速、异构计算等方法突破瓶颈&#xff0c;即分别在算法模型、计算图或算子优化以及硬件加速等层…

《LearnUE——基础指南:上篇—2》——GamePlay架构之Level和World

目录 听说世界是由多个Level组成的 1.2.1 引言 1.2.2 建造大陆&#xff08;ULevel&#xff09; 1.2.3构建世界&#xff08;World&#xff09; 1.2.4总结 听说世界是由多个Level组成的 1.2.1 引言 上小节谈到Actor和Component的关系&#xff0c;UE利用Actor的概念组成了世…

GitLab统计代码量

gitlab官方文档&#xff1a;https://docs.gitlab.com/ee/api/index.html 1、生成密钥 登录gitlab&#xff0c;编辑个人资料&#xff0c;设置访问令牌 2、获取当前用户所有可见的项目 接口地址 GET请求 http://gitlab访问地址/api/v4/projects?private_tokenxxx 返回参数 …

【文章学习系列之模型】Non-stationary Transformers

本章内容 文章概况总体结构主要模块Series Stationarization&#xff08;序列平稳化模块&#xff09;De-stationary Attention&#xff08;逆平稳化注意力模块&#xff09; 实验结果消融实验总结 文章概况 《Non-stationary Transformers:Exploring the Stationarity in Time …

Docker 存储

Docker 存储 docker 默认存储方式docker 持久化存储Volumes &#xff08;卷&#xff09;简介推荐使用情况 Bind mounts &#xff08;绑定挂载&#xff09;简介推荐使用情况 绑定挂载与卷注意点 docker 非持久化存储tmpfs mounts &#xff08;tmpfs 挂载&#xff09;简介推荐使用…

leetcode100——相同的树

文章目录 题目详情分析Java完整代码 题目详情 leetcode100 给你两棵二叉树的根节点 p 和 q &#xff0c;编写一个函数来检验这两棵树是否相同。 如果两个树在结构上相同&#xff0c;并且节点具有相同的值&#xff0c;则认为它们是相同的。 示例&#xff1a; 分析 主要想法…

vue3头像上传组件

用到了自定义组件v-model的双向绑定使用input的typefile这个原生html元素&#xff0c;通过监听change事件&#xff0c;获取到选择的文件&#xff08;注意&#xff0c;选择完文件值后&#xff0c;要把这个隐藏的input的typefile元素的value置为空&#xff0c;否则&#xff0c;下…

【关于C++中----异常】

文章目录 一、C语言中处理错误的方式二、C异常概念三、异常的使用3.1 异常的抛出和捕获3.2 异常的重新抛出3.3 异常安全3.4 异常规范 四、自定义异常体系五、C标准库的异常体系六、异常的优缺点 一、C语言中处理错误的方式 C语言中常见的错误类型包括&#xff1a;语法错误、逻…

访问 virtualbox中的mysql

在 mysql.user 中存储这用户可访问的的host地址 select user,host from user;修改访问权限 可以使用sql语句 use mysql; mysql>update user set host % where user root; flush privileges或者使用mysql的权限语句 use mysql; Grant all on *.* to root% identified b…

深入浅出C++ ——异常

文章目录 一、C语言传统的处理错误的方式二、C异常概念三、异常的使用异常的抛出和捕获异常的重新抛出异常安全异常规范 四、自定义异常体系无、C标准库的异常体系六、异常的优缺点 一、C语言传统的处理错误的方式 C语言传统的处理错误的机制&#xff1a; 终止程序&#xff0…

docker打包部署spring boot应用(mysql+jar+Nginx)

文章目录 一、基本准备二、mysql部署二、jar部署三、Nginx部署 一、基本准备 小唐拿的就是之前放置在我们服务器上的应用进行部署&#xff0c;主要就是mysql和jar还有Vue的部署。 目前已经有的是jar、已经打包好的vue 项目参考&#xff1a;小破站数据大屏可视化&#xff08;…