线程同步 线程安全

这里写目录标题

    • 线程安全
      • 互斥锁
        • **互斥锁初始化**
        • **互斥锁加锁和解锁**
        • **销毁互斥锁**
        • **互斥锁死锁**
        • **互斥锁的属性**
      • 条件变量
        • 条件变量初始化
        • 通知和等待条件变量
        • 条件变量的判断条件
      • 自旋锁
        • 自旋锁初始化
        • 自旋锁加锁和解锁
      • 读写锁
        • 读写锁初始化
        • 读写锁上锁和解锁
        • 读写锁的属性

线程安全

对于一个单线程进程来说,它不需要处理线程同步的问题,所以线程同步是在多线程环境下可能需要注意的一个问题。线程的主要优势在于,资源的共享性,譬如通过全局变量来实现信息共享,不过这种便捷的共享是有代价的,那就是多个线程并发访问共享数据所导致的数据不一致的问题。

在这里插入图片描述

两个线程读写相同变量(共享变量、共享资源)的假设例子。在这个例子当中,线程 A 读取变量的值,然后再给这个变量赋予一个新的值,但写操作需要 2 个时钟周期(这里只是假设);当线程 B 在这两个写周期中间读取了这个变量,它就会得到不一致的值,这就出现了数据不一致的问题。

如何解决对共享资源的并发访问出现数据不一致的问题?
在这里插入图片描述
Linux 系统提供了多种用于实现线程同步的机制,常见的方法有:
互斥锁、条件变量、自旋锁以及读写锁等

互斥锁

互斥锁(mutex)又叫互斥量,从本质上说是一把锁,在访问共享资源之前对互斥锁进行上锁,在访问完成后释放互斥锁(解锁);对互斥锁进行上锁之后,任何其它试图再次对互斥锁进行加锁的线程都会被阻塞,直到当前线程释放互斥锁。如果释放互斥锁时有一个以上的线程阻塞,那么这些阻塞的线程会被唤醒,它们都会尝试对互斥锁进行加锁,当有一个线程成功对互斥锁上锁之后,其它线程就不能再次上锁了,只能再次陷入阻塞,等待下一次解锁。

互斥锁初始化

1、使用 PTHREAD_MUTEX_INITIALIZER 宏初始化互斥锁
2、使用 pthread_mutex_init()函数初始化互斥锁

互斥锁加锁和解锁

调用函数 pthread_mutex_lock()可以对互斥锁加锁、获取互斥锁,而调用函数 pthread_mutex_unlock()可以对互斥锁解锁、释放互斥锁。

以下行为均属错误:
⚫ 对处于未锁定状态的互斥锁进行解锁操作;
⚫ 解锁由其它线程锁定的互斥锁。

当互斥锁已经被其它线程锁住时,调用 **pthread_mutex_lock()**函数会被阻塞,直到互斥锁解锁;如果线程不希望被阻塞,可以使用 pthread_mutex_trylock()函数;调用 pthread_mutex_trylock()函数尝试对互斥锁进行加锁,如果互斥锁处于未锁住状态,那么调用 pthread_mutex_trylock()将会锁住互斥锁并立马返回,如果互斥锁已经被其它线程锁住,调用 pthread_mutex_trylock()加锁失败,但不会阻塞,而是返回错误码 EBUSY。

销毁互斥锁

调用 pthread_mutex_destroy()函数来销毁互斥锁

⚫ 不能销毁还没有解锁的互斥锁,否则将会出现错误;
⚫ 没有初始化的互斥锁也不能销毁。

互斥锁死锁

如果一个线程试图对同一个互斥锁加锁两次,会出现什么情况?情况就是该线程会陷入死锁状态,一直被阻塞永远出不来;这就是出现死锁的一种情况,除此之外,使用互斥锁还有其它很多种方式也能产生死锁。有时,一个线程需要同时访问两个或更多不同的共享资源,而每个资源又由不同的互斥锁管理。当超过一个线程对同一组互斥锁(两个或两个以上的互斥锁)进行加锁时,就有可能发生死锁;譬如,程序中使用一个以上的互斥锁,如果允许一个线程一直占有第一个互斥锁,并且在试图锁住第二个互斥锁时处于阻塞状态,但是拥有第二个互斥锁的线程也在试图锁住第一个互斥锁。因为两个线程都在相互请求另一个线程拥有的资源,所以这两个线程都无法向前运行,会被一直阻塞,于是就产生了死锁。

// 线程 A
pthread_mutex_lock(mutex1);
pthread_mutex_lock(mutex2);
// 线程 B
pthread_mutex_lock(mutex2);
pthread_mutex_lock(mutex1);

在我们的程序当中,如果用到了多个互斥锁,要避免此类死锁的问题.

最简单的方式就是定义互斥锁的层级关系,当多个线程对一组互斥锁操作时,总是应该按照相同的顺序对该组互斥锁进行锁定。譬如在上述场景中,如果两个线程总是先锁定 mutex1 在锁定 mutex2,死锁就不会出现。

使用 pthread_mutex_trylock()以不阻塞的方式尝试对互斥锁进行加锁,在这种方案中,线程先使用函数pthread_mutex_lock()锁定第一个互斥锁,然后使用 pthread_mutex_trylock()来锁定其余的互斥锁。如果任一pthread_mutex_trylock()调用失败(返回 EBUSY),那么该线程释放所有互斥锁,可以经过一段时间之后从头再试。与第一种按照层级关系来避免死锁的方法变比,这种方法效率要低一些,因为可能需要经历多次循环。

互斥锁的属性

如前所述,调用 pthread_mutex_init()函数初始化互斥锁时可以设置互斥锁的属性,通过参数 attr 指定。参数 attr 指向一个 pthread_mutexattr_t 类型对象,该对象对互斥锁的属性进行定义,当然,如果将参数 attr设置为 NULL,则表示将互斥锁属性设置为默认值。

当定义 pthread_mutexattr_t 对象之后,需要使用 pthread_mutexattr_init()函数对该对象进行初始化操作,当对象不再使用时,需要使用 pthread_mutexattr_destroy()将其销毁。可以使用 pthread_mutexattr_gettype()函数得到互斥锁的类型属性,使用 pthread_mutexattr_settype()修改/设置互斥锁类型属性。

  • 普通互斥锁(PTHREAD_MUTEX_NORMAL):
    这是默认类型。它不检测死锁情况,因此如果同一个线程多次加锁,会导致死锁(即锁永远无法释放)。在这种情况下,如果同一个线程尝试再次加锁,行为是未定义的。
  • 错误检查互斥锁(PTHREAD_MUTEX_ERRORCHECK):
    这种类型的互斥锁提供了错误检查机制。如果同一个线程尝试重复加锁,pthread 库会返回错误(EDEADLK)。此外,如果线程在未持有锁的情况下尝试解锁,也会返回错误。这种类型的互斥锁适合于需要确保线程安全的场景,并且希望能够检测到潜在的错误。
  • 递归互斥锁(PTHREAD_MUTEX_RECURSIVE):
    这种类型的互斥锁允许同一个线程多次加锁而不会导致死锁。每次加锁都必须有相应的解锁,只有当最后一次解锁时,互斥锁才会被释放。适合于复杂的线程操作,尤其是在函数递归调用时可能需要再次锁定的场景。

条件变量

条件变量是线程可用的另一种同步机制。条件变量用于自动阻塞线程,知道某个特定事件发生或某个条件满足为止,通常情况下,条件变量是和互斥锁一起搭配使用的。使用条件变量主要包括两个动作:
⚫ 一个线程等待某个条件满足而被阻塞;
⚫ 另一个线程中,条件满足时发出“信号”。

生产者—消费者模式,生产者这边负责生产产品、而消费者负责消费产品,对于消费者来说,没有产品的时候只能等待产品出来,有产品就使用它。

条件变量初始化

条件变量使用 pthread_cond_t 数据类型来表示

使用宏 PTHREAD_COND_INITIALIZER 或者使用函数 pthread_cond_init()

通知和等待条件变量

函数 pthread_cond_signal()和 pthread_cond_broadcast()均可向指定的条件变量发送信号,通知一个或多个处于等待状态的线程。调用 pthread_cond_wait()函数是线程阻塞,直到收到条件变量的通知。

条件变量的主要操作便是发送信号(signal)和等待。发送信号操作即是通知一个或多个处于等待状态的线程,某个共享变量的状态已经改变,这些处于等待状态的线程收到通知之后便会被唤醒,唤醒之后再检查条件是否满足。等待操作是指在收到一个通知前一直处于阻塞状态。

在 pthread_cond_wait()函数内部会对参数 mutex 所指定的互斥锁进行操作,通常情况下,条件判断以及pthread_cond_wait()函数调用均在互斥锁的保护下,也就是说,在此之前线程已经对互斥锁加锁了。调用pthread_cond_wait()函数时,调用者把互斥锁传递给函数,函数会自动把调用线程放到等待条件的线程列表上,然后将互斥锁解锁;当 pthread_cond_wait()被唤醒返回时,会再次锁住互斥锁。

注意注意的是,条件变量并不保存状态信息,只是传递应用程序状态信息的一种通讯机制。如果调用pthread_cond_signal()和 pthread_cond_broadcast()向指定条件变量发送信号时,若无任何线程等待该条件变量,这个信号也就会不了了之。当调用 pthread_cond_broadcast()同时唤醒所有线程时,互斥锁也只能被某一线程锁住,其它线程获取锁失败又会陷入阻塞。


#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
static pthread_mutex_t mutex; // 定义互斥锁
static pthread_cond_t cond;   // 定义条件变量
static int g_avail = 0;       // 全局共享资源
/* 消费者线程 */
static void *consumer_thread(void *arg)
{for (;;){pthread_mutex_lock(&mutex); // 上锁while (0 >= g_avail)pthread_cond_wait(&cond, &mutex); // 等待条件满足while (0 < g_avail)g_avail--;                // 消费pthread_mutex_unlock(&mutex); // 解锁}return (void *)0;
}
/* 主线程(生产者) */
int main(int argc, char *argv[])
{pthread_t tid;int ret;/* 初始化互斥锁和条件变量 */pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);/* 创建新线程 */ret = pthread_create(&tid, NULL, consumer_thread, NULL);if (ret){fprintf(stderr, "pthread_create error: %s\n", strerror(ret));exit(-1);}for (;;){pthread_mutex_lock(&mutex);   // 上锁g_avail++;                    // 生产pthread_mutex_unlock(&mutex); // 解锁pthread_cond_signal(&cond);   // 向条件变量发送信号}exit(0);
}

全局变量 g_avail 作为主线程和新线程之间的共享资源,两个线程在访问它们之间首先会对互斥锁进行上锁,消费者线程中,当判断没有产品可被消费时(g_avail <= 0),调用 pthread_cond_wait()使得线程陷入等待状态,等待条件变量,等待生产者制造产品;调用 pthread_cond_wait()后线程阻塞并解锁互斥锁;而在生产者线程中,它的任务是生产产品(使用g_avail++来模拟),产品生产完成之后,调用pthread_mutex_unlock()将互斥锁解锁,并调用 pthread_cond_signal()向条件变量发送信号;这将会唤醒处于等待该条件变量的消费者线程,唤醒之后再次自动获取互斥锁,然后再对产品进行消费(g_avai–模拟)。

条件变量的判断条件

在这份示例代码中,我们使用了 while 循环、而不是 if 语句,来控制对 pthread_cond_wait()的调用,这是为何呢?
必须使用 while 循环,而不是 if 语句,这是一种通用的设计原则:当线程从 pthread_cond_wait()返回时,并不能确定判断条件的状态,应该立即重新检查判断条件,如果条件不满足,那就继续休眠等待。

从 pthread_cond_wait()返回后,并不能确定判断条件是真还是假,其理由如下:
⚫ 当有多于一个线程在等待条件变量时,任何线程都有可能会率先醒来获取互斥锁,率先醒来获取到互斥锁的线程可能会对共享变量进行修改,进而改变判断条件的状态。譬如示例代码 12.3.2 中,如果有两个或更多个消费者线程,当其中一个消费者线程从 thread_cond_wait()返回后,它会将全局共享变量 g_avail 的值变成 0,导致判断条件的状态由真变成假。
⚫ 可能会发出虚假的通知。

自旋锁

自旋锁与互斥锁很相似,从本质上说也是一把锁,在访问共享资源之前对自旋锁进行上锁,在访问完成后释放自旋锁(解锁);事实上,从实现方式上来说,互斥锁是基于自旋锁来实现的,所以自旋锁相较于互斥锁更加底层。
如果在获取自旋锁时,自旋锁已经处于锁定状态了,那么获取锁操作将会在原地“自旋”,直到该自旋锁的持有者释放了锁。

再来总结下自旋锁与互斥锁之间的区别
⚫ 实现方式上的区别:互斥锁是基于自旋锁而实现的,所以自旋锁相较于互斥锁更加底层;
⚫ 开销上的区别:获取不到互斥锁会陷入阻塞状态(休眠),直到获取到锁时被唤醒;而获取不到自旋锁会在原地“自旋”,直到获取到锁;休眠与唤醒开销是很大的,所以互斥锁的开销要远高于自旋锁、自旋锁的效率远高于互斥锁;但如果长时间的“自旋”等待,会使得 CPU 使用效率降低,故自旋锁不适用于等待时间比较长的情况。
⚫ 使用场景的区别:自旋锁在用户态应用程序中使用的比较少,通常在内核代码中使用比较多;因为自旋锁可以在中断服务函数中使用,而互斥锁则不行,在执行中断服务函数时要求不能休眠、不能被抢占(内核中使用自旋锁会自动禁止抢占),一旦休眠意味着执行中断服务函数时主动交出了CPU 使用权,休眠结束时无法返回到中断服务函数中,这样就会导致死锁!

自旋锁初始化

当定义自旋锁后,需要使用 pthread_spin_init()函数对其进行初始化,当不再使用自旋锁时,调用 pthread_spin_destroy()函数将其销毁.

自旋锁加锁和解锁

可以使用 pthread_spin_lock()函数或 pthread_spin_trylock()函数对自旋锁进行加锁,前者在未获取到锁时一直“自旋”;对于后者,如果未能获取到锁,就立刻返回错误,错误码为 EBUSY。不管以何种方式加锁,自旋锁都可以使用 pthread_spin_unlock()函数对自旋锁进行解锁。

读写锁

在这里插入图片描述
互斥锁或自旋锁要么是加锁状态、要么是不加锁状态,而且一次只有一个线程可以对其加锁。读写锁有3 种状态:读模式下的加锁状态(以下简称读加锁状态)、写模式下的加锁状态(以下简称写加锁状态)和不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是可以有多个线程同时占有读模式的读写锁。

读写锁有如下两个规则:
⚫ 当读写锁处于写加锁状态时,在这个锁被解锁之前,所有试图对这个锁进行加锁操作(不管是以读模式加锁还是以写模式加锁)的线程都会被阻塞。
⚫ 当读写锁处于读加锁状态时,所有试图以读模式对它进行加锁的线程都可以加锁成功;但是任何以写模式对它进行加锁的线程都会被阻塞,直到所有持有读模式锁的线程释放它们的锁为止。

虽然各操作系统对读写锁的实现各不相同,但当读写锁处于读模式加锁状态,而这时有一个线程试图以写模式获取锁时,该线程会被阻塞;而如果另一线程以读模式获取锁,则会成功获取到锁,对共享资源进行读操作。

读写锁初始化

读写锁的初始化可以使用宏 PTHREAD_RWLOCK_INITIALIZER 或者函数pthread_rwlock_init()
当读写锁不再使用时,需要调用pthread_rwlock_destroy()函数将其销毁

读写锁上锁和解锁

以读模式对读写锁进行上锁,需要调用 pthread_rwlock_rdlock()函数;以写模式对读写锁进行上锁,需要调用 pthread_rwlock_wrlock()函数。不管是以何种方式锁住读写锁,均可以调用 pthread_rwlock_unlock()函数解锁。

当读写锁处于写模式加锁状态时,其它线程调用 pthread_rwlock_rdlock()或 pthread_rwlock_wrlock()函数均会获取锁失败,从而陷入阻塞等待状态;当读写锁处于读模式加锁状态时,其它线程调用pthread_rwlock_rdlock()函数可以成功获取到锁,如果调用 pthread_rwlock_wrlock()函数则不能获取到锁,从而陷入阻塞等待状态。如果线程不希望被阻塞,可以调用 pthread_rwlock_tryrdlock()和 pthread_rwlock_trywrlock()来尝试加锁,如果不可以获取锁时。这两个函数都会立马返回错误,错误码为 EBUSY。

读写锁的属性

读写锁与互斥锁类似,也是有属性的,读写锁的属性使用 pthread_rwlockattr_t 数据类型来表示,当定义pthread_rwlockattr_t 对象时,需要使用 pthread_rwlockattr_init()函数对其进行初始化操作,初始化会将pthread_rwlockattr_t 对象定义的各个读写锁属性初始化为默认值;当不再使用 pthread_rwlockattr_t 对象时,需要调用 pthread_rwlockattr_destroy()函数将其销毁

读写锁只有一个属性,那便是进程共享属性。函 数 pthread_rwlockattr_getpshared() 用于 从pthread_rwlockattr_t 对象中获取共享属性,函数 pthread_rwlockattr_setpshared()用于设置 pthread_rwlockattr_t对象中的共享属性

⚫ PTHREAD_PROCESS_SHARED:共享读写锁。该读写锁可以在多个进程中的线程之间共享;
⚫ PTHREAD_PROCESS_PRIVATE:私有读写锁。只有本进程内的线程才能够使用该读写锁,这是读写锁共享属性的默认值。

我的问题?
关于多线程中,线程栈,堆的分配情况是如何的?
堆内存:线程可以动态分配内存(使用 malloc 或 new 等),并将其存储在堆中。所有线程可以访问这些堆分配的内存,通常适用于共享数据。
栈内存:每个线程都有自己的栈,用于存储局部变量和函数调用信息。栈的大小在创建线程时可以指定,通常与线程的生命周期有关。栈中的数据对其他线程不可见,保证了线程间的局部变量独立性。

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

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

相关文章

使用Python和OpenCV实现火焰检测

使用Python和OpenCV实现火焰检测 项目解释&#xff1a; 此 Python 代码是使用 OpenCV、线程、声音和电子邮件功能的火灾探测系统的简单示例。 以下是它的功能的简单描述&#xff1a; 导入库&#xff1a;代码首先导入必要的库&#xff1a; cv2&#xff1a;用于图像和视频处理…

蓝桥杯基本算法~~~一维/二维前缀和问题

文章目录 1.一维前缀和2.二维前缀和3.移动零问题4.颜色的分类问题 1.一维前缀和 问题说明&#xff1a;一维就是表示的是一维数组的计算&#xff0c;我们的这个一维前缀和是基于这个一维数组进行计算的&#xff1b; 什么是前缀和&#xff1a;就是10 20 30 40 50这个数组&#…

ubuntu20.04系统安装

文章目录 前言参考1 一、准备工作1、进入BIOS&#xff0c;设置 UEFI/Legacy Boot选项 为UEFI2、进入BIOS界面将Secure Boot禁用3、USB启动为enable 二、单系统安装1、插入U盘&#xff0c;电脑正常开机后 总结 前言 装了很多次ubuntu系统&#xff0c;整理一篇自己的文章很费时间…

JS | CommonJS、AMD、CMD、ES6-Module、UMD五种JS模块化规范

目录 前言 一、CommonJS 模块化规范 二、ES6 模块化规范 三、AMD 模块化规范 四、CMD 模块化规范 五、UMD模块化规范 前言 这三个规范都是为Js模块化加载而生的&#xff0c;使模块能够按需加载&#xff0c;使系统同庞杂的代码得到组织和管理。模块化的管理代码使多人开发…

【宠物狗狗数据集】 犬类品种识别 宠物狗检测 深度学习 目标检测(含数据集)

一、背景意义 随着人们对宠物狗的喜爱日益增加&#xff0c;犬种的多样性也逐渐受到重视。狗狗不仅是家庭的好伴侣&#xff0c;更在多个领域中发挥着重要作用&#xff0c;如导盲、搜救、疗愈等。因此&#xff0c;准确识别和分类各种犬种显得尤为重要。传统的犬种识别方法往往依赖…

移远通信闪耀2024香港秋灯展,以丰富的Matter产品及方案推动智能家居产业发展

10月27-30日&#xff0c;2024香港国际秋季灯饰展在香港会议展览中心盛大开展。 作为全球领先的物联网整体解决方案供应商&#xff0c;移远通信再次亮相&#xff0c;并重点展示了旗下支持Matter协议以及亚马逊ACK ( Alexa Connect Kit ) SDK for Matter方案的Wi-Fi模组、低功耗蓝…

虚拟机桥接模式连不上,无法进行SSH等远程操作

说明&#xff1a;以下情况在window10上遇到&#xff0c;解决后顺便做了个笔记&#xff0c;以防后续再次用到&#xff0c;也给同道中人提供一个解决方案 一、首先按照以下步骤进行检查 1、是否连接了对应的wifi 2、是否设置了桥接模式 3、上述1、2确认无误的情况下请查看右上…

ApsaraMQ Serverless 能力再升级,事件驱动架构赋能 AI 应用

本文整理于 2024 年云栖大会阿里云智能集团高级技术专家金吉祥&#xff08;牟羽&#xff09;带来的主题演讲《ApsaraMQ Serverless 能力再升级&#xff0c;事件驱动架构赋能 AI 应用》 云消息队列 ApsaraMQ 全系列产品 Serverless 化&#xff0c;支持按量付费、自适应弹性、跨可…

一款专业获取 iOS 设备的 UDID 工具|一键获取iPhone iPad设备的 UDID

什么是UDID&#xff1f; UDID&#xff0c;是iOS设备的一个唯一识别码&#xff0c;每台iOS设备都有一个独一无二的编码&#xff0c;这个编码&#xff0c;我们称之为识别码&#xff0c;也叫做UDID&#xff08; Unique Device Identifier&#xff09; 扫描后系统提示输入密码&am…

IDEA连接EXPRESS版本的SQL server数据库

我安装的版本是SQL2019-SSEI-Expr.exe 也就是EXPRESS版本的SQL Server安排非常简单和快速。 但是默认没有启动sa用户。 启动sa用户名密码登录 默认安装完以后没有启用。 使用Miscrosoft SQL Server Management Studio 使用Windows身份连接后。 在安全性》登录名中找到sa并修改…

大模型,多模态大模型面试问题【计算图,LLama,交叉熵,SiLU,RLHF】

大模型&#xff0c;多模态大模型面试问题【计算图&#xff0c;LLama&#xff0c;交叉熵&#xff0c;SiLU&#xff0c;RLHF】 问题一&#xff1a;讲一讲计算图中pytorch是什么&#xff0c;TensorFlow是什么&#xff1f;1. PyTorch2. TensorFlow区别总结 问题二&#xff1a;Llama…

【AIGC】2024-arXiv-Lumiere:视频生成的时空扩散模型

2024-arXiv-Lumiere: A Space-Time Diffusion Model for Video Generation Lumiere&#xff1a;视频生成的时空扩散模型摘要1. 引言2. 相关工作3. Lumiere3.1 时空 U-Net (STUnet)3.2 空间超分辨率的多重扩散 4. 应用4.1 风格化生成4.2 条件生成 5. 评估和比较5.1 定性评估5.2 …

隨筆 20241025 Kafka数据一致性的韭菜比喻

在Kafka中&#xff0c;数据一致性是通过Leader和Follower副本之间的协调来实现的。为了更容易理解这个复杂的概念&#xff0c;我们可以用韭菜作为比喻。 韭菜的角色 Leader韭菜&#xff1a;代表数据的主导者&#xff0c;它负责更新和维护最新的数据。Follower韭菜&#xff1a…

【CPN TOOLS建模学习】设置变迁的属性

使用Tab键在属性之间进行切换 与一个变迁相关联的四个铭文&#xff0c;均为可选项&#xff1a; 变迁名称守卫(Guard)时间代码段 变迁延迟必须是一个正整数表达式。该表达式前面加上&#xff0c;这意味着时间铭文的形式为 delayexpr。在添加时间铭文之前&#xff0c;铭文的默…

标准正态分布的数据 tensorflow 实现正态分布图,python 编程,数据分析和人工智能...

登录后复制 import tensorflow as tfimport matplotlib.pyplot as plt# 设置随机种子以获得可重复的结果tf.random.set_seed(42)# 生成正态分布的数据# mean0 和 stddev1 表示生成标准正态分布的数据# shape(1000,) 表示生成1000个数据点data tf.random.normal(mean0, stddev1…

005 IP地址的分类

拓扑结构如下 两台主机处于同一个网关下&#xff0c;通过ping命令检测&#xff0c;可以连通 &nbps; 拓扑结构如下 使用ping 检查两台电脑是否相通, 因为网络号不一样&#xff0c;表示两台电脑不在同一个网络&#xff0c;因此无法连通 拓扑结构如下 不在同一网络的PC要相…

HTML--浮动布局练习

<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style>/* 整个浏览器页…

后台管理系统的通用权限解决方案(二)SpringBoot整合Swagger Springfox实现接口日志文档

文章目录 1 Swagger介绍2 Swagger常用注解3 Swagger使用案例 1 Swagger介绍 使用Swagger&#xff0c;我们只需要按照它的规范去定义接口及接口相关的信息&#xff0c;再通过Swagger衍生出来的一系列项目和工具&#xff0c;就可以做到生成各种格式的接口文档&#xff0c;生成多…

【Spring框架】Spring框架的开发方式

目录 Spring框架开发方式前言具体案例导入依赖创建数据库表结构创建实体类编写持久层接口和实现类编写业务层接口和实现类配置文件的编写 IoC注解开发注解开发入门&#xff08;半注解&#xff09;IoC常用注解Spring纯注解方式开发 Spring整合JUnit测试 Spring框架开发方式 前言…