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

news2024/12/25 9:27:34

前言:

在多线程编程中,如果多个线程同时访问和修改共享资源,可能会产生竞争条件和数据不一致的问题。同步机制用于协调线程之间的访问和操作,确保数据的正确性和一致性。为了避免多个线程同时访问和操作共享资源导致的问题,可以使用互斥锁(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;    //定义一把互斥锁,可以想象为一个int

void *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);   //初始化互斥锁 可以认为锁的值为1
    if(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. 对一个锁反复lock

        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 返回时, 重新加锁 mutex

        mp = 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, 线程间共享 -- 0
    sem_init(&product_number, 0, 0);                //产品数为0

    pthread_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.coloradmin.cn/o/1020891.html

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

相关文章

小程序键盘没有【小数点】输入

<input v-model"formData.number" :auto-height"true" placeholder"请输入" confirm-type"done" type"digit" maxlength"11" input"inputNumber" />number&#xff1a;数字键盘&#xff08;没有小…

Minitab Express for Mac(数据分析软件)附破解补丁 v1.5.0 支持M1

Minitab Express是一款专为Mac用户设计的数据分析和统计软件。它提供了一套全面的工具和功能&#xff0c;用于分析数据、执行统计计算和生成可视化。 下载&#xff1a;Minitab Express for Mac(数据分析软件)附破解补丁 以下是 Minitab Express for Mac 的一些主要功能&#x…

随机森林案例分析

阅读随机森林模型前&#xff0c;建议首先阅读决策树模型手册&#xff08;点击后跳到决策树模型的帮助手册页面&#xff09;&#xff0c;因为随机森林模型实质上是多个决策树模型的综合&#xff0c;决策树模型只构建一棵分类树&#xff0c;但是随机森林模型构建非常多棵决策树&a…

数字虚拟人制作简明指南

如何在线创建虚拟人&#xff1f; 虚拟人&#xff0c;也称为数字化身、虚拟助理或虚拟代理&#xff0c;是一种可以通过各种在线平台与用户进行逼真交互的人工智能人。 在线创建虚拟人变得越来越流行&#xff0c;因为它为个人和企业带来了许多好处。 推荐&#xff1a;用 NSDT编辑…

阿里云无影云电脑和传统PC有什么区别?

阿里云无影云电脑和传统电脑PC有什么区别&#xff1f;区别大了&#xff0c;无影云电脑是云端的桌面服务&#xff0c;传统PC是本地的硬件计算机&#xff0c;无影云电脑的数据是保存在云端&#xff0c;本地传统PC的数据是保存在本地客户端&#xff0c;阿里云百科分享阿里云无影云…

低代码与低代码平台

随着数字化转型和软件需求的不断增长&#xff0c;传统的手写代码开发方式已经无法满足迅速推出应用程序的需求。为了加快软件开发的速度并降低技术门槛&#xff0c;低代码开发模式应运而生。本文将介绍低代码的概念&#xff0c;探讨什么是低代码、什么是低代码平台&#xff1f;…

RFID设备在自动化堆场中的管理应用

随着信息技术的高速发展&#xff0c;带动了港口生产和管理技术的长足进步&#xff0c;港口堆场内的自动化场桥的智能化水平成为码头提高生产率一个重要标签。各地海关着力于现代化科技手段&#xff0c;努力构筑新型的便捷通关模式&#xff0c;在进出口环节做好管理和服务。 全…

已解决 Kotlin Error: Type mismatch: inferred type is String but Int was expected

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页: &#x1f405;&#x1f43e;猫头虎的博客&#x1f390;《面试题大全专栏》 &#x1f995; 文章图文并茂&#x1f996…

idea之maven的安装与配置

我们到maven的官网里下载maven&#xff0c;地址&#xff1a;https://maven.apache.org/download.cgi下载完成后解压即可配置环境变量 此电脑–>右键–>属性–>高级系统设置–>环境变量–>系统变量&#xff08;S&#xff09;–>新建一个系统变量 变量名&…

【开发记录01】开发环境副本/页的导入&带用户权限管理系统

在蒋老师的指导下大概了解了: 1.开发环境的数据导入/导出 共享组件的同步 因为应用程序277是应用程序100的子程序&#xff0c;所以共享组件必须和100保持一致。 但是会出现一个小问题&#xff1a; 在APEX开发过程中同时打开两个不同的应用程序&#xff0c;但是编辑过程中经…

Java 华为真题-猴子爬山

需求&#xff1a; 一天一只顽猴想去从山脚爬到山顶&#xff0c;途中经过一个有个N个台阶的阶梯&#xff0c;但是这猴子有一个习惯&#xff1a;每一次只能跳1步或跳3步&#xff0c;试问猴子通过这个阶梯有多少种不同的跳跃方式&#xff1f; 输入描述 输入只有一个整数N&#xff…

Python - 小玩意 - 键盘记录器

pip install keyboardimport keyboard import timedef get_time():date_time time.strftime("%Y-%m-%d %H:%S", time.localtime())return date_timedef abc(x):if x.event_type down:print(f"{get_time()}你按下了{x.name}")with open(./键盘记录器.txt,…

CG Magic分享同一场景里下,VR渲染器和CR渲染器哪个好?

渲染操作时&#xff0c;VR渲染器和CR渲染器的对比成为常见问题了。这个问题很多人都会问。 今天CG Magic小编通过一个真实的项目&#xff0c;就是同一场景下来比较一下VR渲染器和CR渲染器的区别。 以下图为例是用来测试的场景当年的最终图。采用了当年的一个伊丽莎白大街152号的…

SwiftUI 导航设置

文章目录 一、导航跳转二、导航设置三、Present跳转&#xff08;模态跳转&#xff09;四、返回页面 一、导航跳转 页面A import SwiftUIstruct NavJumpAView: View {State var isNavPush falsevar body: some View {NavigationView {VStack {NavigationLink(isActive: $isNa…

Epub如何转换成PDF格式,收藏好着两个工具!不要太简单~

你是不是经常遇到这样的情况&#xff1a;你下载了一本很想看的Epub格式的电子书&#xff0c;但是你的电脑或者手机没有支持Epub格式的阅读器&#xff0c;或者你觉得Epub格式的阅读效果不够好&#xff0c;你想要将它转换为PDF格式&#xff0c;但是你又不知道用什么软件或者网站可…

6-2 pytorch中训练模型的3种方法

Pytorch通常需要用户编写自定义训练循环&#xff0c;训练循环的代码风格因人而异。&#xff08;养成自己的习惯&#xff09; 有3类典型的训练循环代码风格&#xff1a;脚本形式训练循环&#xff0c;函数形式训练循环&#xff0c;类形式训练循环。 下面以minist数据集的多分类模…

Git --- 基础介绍

Git --- 基础介绍 git 是什么git --- 工作区, 暂存区, 资源库git --- 文件状态git --- branch 和 HEADgit --- 一次正常的git提交流程 git 是什么 Git是一款分布式源代码管理工具(版本控制工具)Git和其他传统版本控制系统比较: 传统的版本控制系统(例如 SVN)是基于差异的版本控…

家政小程序开发制作,家政保洁上门维修小程序搭建

家政小程序开发制作&#xff0c;现如今家政上门服务&#xff0c;也越来越普及到我们的生活中&#xff0c;比如家电清洗&#xff0c;水电维修&#xff0c;家政保洁&#xff0c;上门护理等等方面。那么一个合格的家政小程序&#xff0c;需要满足哪些功能呢&#xff1f;今天就带大…

视频图像处理算法opencv模块硬件设计图像颜色识别模块

1、Opencv简介 OpenCV是一个基于Apache2.0许可&#xff08;开源&#xff09;发行的跨平台计算机视觉和机器学习软件库&#xff0c;可以运行在Linux、Windows、Android和Mac OS操作系统上 它轻量级而且高效——由一系列 C 函数和少量 C 类构成&#xff0c;同时提供了Python、Rub…

Stable Diffusion AI绘图使用记录

1、下载安装使用 官方网站https://github.com/AUTOMATIC1111/stable-diffusion-webui 跟着一步步安装就行&#xff08;英文版的&#xff09; 2、真人转二次元 下载控制插件Contro lnetGitHub - Mikubill/sd-webui-controlnet: WebUI extension for ControlNet 按照官方的安…