C++实现高性能并行计算——1.pthreads并行编程(上)

news2024/12/24 20:42:45

系列文章目录

  1. pthreads并行编程(上)
  2. pthreads并行编程(中)
  3. pthreads并行编程(下)
  4. 使用OpenMP进行共享内存编程

文章目录

  • 系列文章目录
  • 前言
  • 一、Pthreads
      • Pthreads 库的主要特性包括:
  • 二、`Hello World`程序
    • 2.1 准备工作
    • 2.2 启动线程
    • 2.3 运行线程
      • 2.3.1 线程函数
    • 2.4 停止线程
  • 三、矩阵向量乘法
    • 3.1 定义矩阵和向量结构及线程参数结构
    • 3.2 线程函数
    • 3.3 主函数测试
    • 3.4结果展示
  • 总结
  • 参考文献


前言

共享内存系统:
共享内存系统中的任意处理器核都能访问所有的内存区域。因此,协调各个处理器核工作的一个方法,就是把某个内存设为“共享”
在这里插入图片描述
进程和线程:

  • 进程,不共享内存,是操作系统资源分配的基本单元。比如QQ程序运行占一个进程。
  • 线程,共享内存,是程序执行的基本单位。比如QQ里面的视频聊天占QQ这个进程中的一个线程。

一个程序至少有一个进程,一个进程至少有一个线程,一个线程只属于一个进程。
单个进程可能有多个线程


一、Pthreads

Pthreads(POSIX线程)库是一个在多种操作系统上实现的标准化并发编程接口。它提供了一套用于创建和控制线程的函数,允许开发者在支持 POSIX 标准的类Unix操作系统(如 Linux、macOS 和 UNIX 系统)上进行多线程编程。Pthreads 库是基于 C 语言的,因此它非常适用于需要低层操作和高性能的应用程序。

Pthreads 库的主要特性包括:

  1. 线程的创建和终止

    • 创建:pthread_create 函数用来创建一个新线程,同时可以指定线程的属性和执行的函数。
    • 终止:线程可以通过 pthread_exit 函数自我终止,或被其他线程通过 pthread_cancel 函数取消。
  2. 线程同步

    • 互斥锁(Mutexes):用于控制对共享资源的访问。互斥锁确保同一时间只有一个线程可以访问某个资源。
    • 条件变量:允许线程在某些条件下挂起执行或者等待其他线程的通知。
    • 读写锁:允许更高效的数据访问,多个线程可以同时读取一个资源,但写操作会独占资源。
  3. 线程属性管理

    • 线程可以具有各种属性,如堆栈大小、调度策略等,这些可以通过 Pthreads 提供的函数进行设置。
  4. 线程局部存储

    • 线程局部存储(Thread Local Storage,TLS)允许每个线程有自己的变量实例,线程之间不共享这些变量。
  5. 信号处理

    • 线程可以响应和处理信号,但管理信号在多线程环境中可以变得复杂。

Pthreads 提供了强大的工具集,用于构建复杂的并行应用程序,但也要求开发者仔细管理线程之间的同步,以避免竞态条件、死锁等多线程常见的问题。


二、Hello World程序

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

int thread_count;  //表示线程个数,被线程所共享
void* Hello(void* rank);

int main(int argc, char* argv[]){
	long thread;
	pthread_t* thread_handles;
	thread_count = strtol(argv[1], NULL, 10);
	
	thread_handles = malloc(thread_count * sizeof(pthread_t));
	for (thread = 0; thread < thread_count; thread++){
		pthread_create(&thread_handles[thread], NULL, Hello, (void*)thread);
	}
	
	printf("Hello from the main thread\n");

	for (thread = 0; thread < thread_count; thread++){ 
		pthread_join(thread_handles[thread], NULL);
	}

	free(thread_handles);
	return 0;
}

void *Hello(void* rank){
	long my_rank = (long)rank;
	printf("Hello from thread %ld of %d\n", my_rank, thread_count);
	return NULL;
}

编译

gcc -g -Wall -o pth_hello pth_hello.c -lpthread

运行

./pth_hello <number of threads>

运行结果
在这里插入图片描述在这里插入图片描述可以发现,每次运行结果的顺序都是不固定的

2.1 准备工作

  • 头文件:pthread.h
  • 全局变量:thread_count
  • 获取线程数
    thread_count = strtol(argv[1], NULL, 10);

首先先来解释strtol 函数的基本语法:

//来自<stdlib.h>
long int strtol(const char *str, char **endptr, int base);
  • str:要转换的字符串。
  • endptr:指向转换中断位置的指针的指针。如果不需要这个信息,可以传递 NULL。
  • base:转换所使用的数值基数,如10代表十进制。

这段代码 thread_count = strtol(argv[1], NULL, 10); 是在 C 或 C++ 程序中用于从命令行参数中获取并转换整数值的一种方法。这里的函数 strtol 用于将字符串转换为长整型数(long 类型)。逐部分解析这个函数调用:

  1. argv[1]:
    • argv 是一个字符串数组(char* argv[]),通常在 C 或 C++ 程序的主函数定义中作为参数传入,用来接收命令行参数。
    • argv[0] 通常是程序的名称,argv[1] 是传递给程序的第一个参数。
  2. NULL:
    • 这是 strtol 函数的第二个参数,用来存储指向转换停止处的字符的指针。在这个用法中,我们不关心转换在哪里停止,因此传入 NULL。
  3. 10:
    • 这是 strtol 函数的第三个参数,指定了数值的基数。在这个例子中,10 表示数值是按十进制进行解析的。

综合来看,这行代码的作用是将命令行提供的第一个参数(假定为字符串形式的整数)转换成一个 long 类型的数值,并将这个数值赋给变量 thread_count。这种做法常见于需要用户指定线程数量或其他数值输入的并发程序中。

这段代码就表示从你的命令行参数中的./pth_hello <number of threads>读取这个<number of threads>线程数

2.2 启动线程

  • 显式地启动线程并构造能够存储线程信息的数据结构。这里用的数据结构是顺序表pthread_t *thread_handles

  • 为每个线程的pthread_t对象分配内存,pthread_t数据结构用来存储线程的专有信息,由pthread.h声明
    thread_handles = malloc(thread_count * sizeof(pthread_t));

  • 创建线程
    pthread_create(&thread_handles[thread], NULL, Hello, (void*)thread);
    该函数原型是

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

    函数 pthread_create 是 POSIX 线程库中用于创建新线程的函数。这个函数的原型如下:

    这个函数有四个参数:

    1. thread(第一个参数):

      • 类型为 pthread_t*,这是一个指向线程标识符的指针。在这个例子中,&thread_handles[thread] 是一个指向 thread_handles 数组中相应位置的指针,用来存储新创建的线程的标识符。这里,pthread_create()不能直接分配pthread_t对象,必须在此之前为其分配相应的内存空间
    2. attr(第二个参数):

      • 类型为 const pthread_attr_t *,用于设置线程属性。如果传递 NULL,线程将使用默认属性。在这个例子中,传递了 NULL,表示使用默认的线程属性。
    3. start_routine(第三个参数):

      • 类型为 void *(*)(void *),这是一个函数指针,指向线程将要执行的函数。在这个例子中,函数是 Hello,它接受一个 void* 类型的参数并返回一个 void* 类型的结果。
    4. arg(第四个参数):

      • 类型为 void *,这是传递给 start_routine 的参数。在这个例子中,传递的是 thread 变量的地址,它被强制转换为 void* 类型。这样做是因为 pthread_create 函数要求参数必须是 void* 类型,而 threadlong 类型的变量,表示当前线程的序号。

    pthread_create 被调用时,它会创建一个新线程,并使该线程开始执行指定的 start_routine 函数。在这个例子中,每个线程都执行 Hello 函数,Hello 函数打印出线程的序号和总线程数。

    返回值:

    • pthread_create 函数返回一个整数,表示操作的成功或失败状态。如果成功,返回 0;如果失败,返回错误代码。在实际使用中,通常会检查这个返回值以确保线程创建成功。

2.3 运行线程

注意: 有之前的运行结果可以知道: 循环创建线程:通过pthread_create函数创建thread_count个线程,每个线程执行Hello函数,并传递其序号作为参数。和printf(“Hello from the main thread\n”);:主线程打印一条消息。这两个命令是几乎同时运行的(不存在上个语句先出现所以先运行的情况)

这是因为: 在多线程编程中,pthread_create 函数的调用启动一个新线程,但并不会暂停或阻塞调用它的线程(即主线程)。当你在循环中调用 pthread_create 时,每次调用都快速地启动一个新线程,并立即返回,允许下一次迭代或程序中的其他代码继续执行。这就意味着主线程会在几乎与这些新线程同时的时间内继续执行其余的代码。
因此,当你在循环后立即使用 printf("Hello from the main thread\n"); 时,这条消息的打印并不需要等待之前启动的所有线程完成。主线程会继续执行到这条 printf 语句并输出消息,而此时新创建的线程可能已经开始执行,也可能还在启动过程中。这就是为什么这两个操作看起来是“同时”运行的:
1. 线程并发性pthread_create 启动的线程可能会在任何时间点开始执行,这取决于操作系统的线程调度。因此,这些线程中的任何一个都可能在主线程打印消息之前、同时或之后开始执行其 Hello 函数。
2. 主线程的继续执行:主线程在调用 pthread_create 后不会停止或等待(除非显式调用如 pthread_join 等同步函数),因此它会立即执行后续的 printf 语句。
这种“同时”运行的情况是多线程程序设计的常见特征,反映了并发执行的本质,其中多个线程可以独立或几乎同时进行,而不是顺序执行。

在这里插入图片描述

2.3.1 线程函数

  • pthread_create生成并运行的函数
  • 原型 void *thread_function(void *arg_p)
    在这里插入图片描述
    这个例子中的线程函数是void* Hello(void *rank)

2.4 停止线程

pthread_join()函数等待所有线程完成并最终结束。pthread_join(thread_handles[thread], NULL);该函数原型是:

int pthread_join(pthread_t thread, void **retval);

这个函数有两个参数:

  • thread(第一个参数):
    类型为 pthread_t,代表需要等待的线线程的标识符。这个标识符是在之前调用 pthread_create 时获得的。
  • retval(第二个参数):
    类型为 void**,用于存储线程的返回值。如果不关心线程返回什么值,可以传递 NULL。在你的示例代码中,传递的是 NULL,表示主线程不需要获取从线程 Hello 返回的任何值。

作用:
当你在主线程中调用 pthread_join(thread_handles[thread], NULL); 时,这个调用会阻塞主线程,直到 thread_handles[thread] 指定的线程完成其执行。这个过程中,如果被等待的线程已经结束,pthread_join 会立即返回;如果线程尚未结束,pthread_join 会使主线程阻塞,直到那个线程结束。

为什么使用 pthread_join

  • 资源回收:确保当线程结束时,相关资源被适当回收。这包括线程的内部状态和任何可能分配的内存。
  • 同步:有时候,可能需要确保线程按照特定的顺序完成任务。pthread_join 允许开发者控制程序的执行流程,确保所有线程按预期完成,再继续执行依赖这些线程结果的代码。

示例中的使用
在代码示例中,循环中对每一个线程调用 pthread_join,这确保了所有线程都完成它们的 Hello 函数执行后,主线程才会继续向下执行并最终退出。这个过程是必要的,因为如果主线程在创建的线程还在运行时结束了,整个程序也会结束,这可能导致线程中的任务没有完成就被中断。通过使用 pthread_join,主线程会等待每个线程正确地完成它们的任务。

为什么取名join,因为最后都要合并到主线程上去
在这里插入图片描述
思考:为什么第二个参数是void**而不是void*
是因为这个参数的目的是接收线程函数返回的值的地址。使用 void** 允许 pthread_join 函数把线程函数结束时返回的 void* 类型的值存储在用户提供的地址中。

在 C 语言中,如果想在一个函数内修改某个变量的值,并且希望这个修改在函数外也有效,通常需要传递该变量的地址(即使用指针)。如果传递了一个指向某个数据的指针(即 void*),那么你可以在函数内部修改这个数据的内容。然而,如果想在函数内部修改指针本身的值(例如,使它指向一个不同的地址),需要传递一个指向该指针的指针(即 void**)。

在多线程环境中,每个线程可能会有一个返回值,这个值是通过线程的启动函数的返回来传递的。这个返回值是一个 void* 类型,因为它被设计为足够通用,能够指向任何类型的数据。当 pthread_join 被调用时,它需要一个地方来存储这个返回值,所以需要提供一个指向 void* 的指针(即 void**),pthread_join 将线程的返回值存储在这个指向 void* 的指针所指向的位置。

示例

假设线程函数返回一个指向整数的指针:

void* thread_function(void* arg) {
    int* result = malloc(sizeof(int));
    *result = 42;  // 任意的返回值
    return result;
}

这样使用 pthread_join 来接收这个返回值:

void* thread_result;
pthread_t thread;
pthread_create(&thread, NULL, thread_function, NULL);
pthread_join(thread, &thread_result);
int* result = (int*) thread_result;  // 转换为正确的类型
printf("Thread returned %d\n", *result);
free(result);  // 记得释放内存

在这个例子中,pthread_join(thread, &thread_result); 调用中,&thread_resultvoid** 类型,它提供了一个存储从 thread_function 返回的 void* 值的位置。这样,就可以在 pthread_join 后访问并处理这个线程返回的数据。


三、矩阵向量乘法

在这里插入图片描述
在这里插入图片描述怎么来分配线程呢?最先想到的就是m行矩阵,由t个线程(假设m % t = 0)那么每个线程计算(m/t)行。

3.1 定义矩阵和向量结构及线程参数结构

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

// 定义结构体用于传递数据给线程
typedef struct ThreadData{
    int rank;         // 线程的标识(ID或rank)
    int start_row;
    int end_row;
    int num_cols;
    double* matrix;
    double* vector;
    double* result;
} ThreadData;

/*
**在多线程编程中,尤其是在使用 POSIX 线程(pthreads)库时,ThreadData结构的作用是传递多个数据或参数给线程函数。
**由于 pthread 的线程函数只能接受一个 void* 类型的参数,如果需要传递多个参数到线程,就需要使用一个结构体来封装这些参数。
*/

3.2 线程函数

void* pth_mat_vect(void* arg){
	ThreadData* td = (ThreadData*)arg;
	printf("Thread %d is running. Processing rows from %d to %d.\n", td->rank, td->start_row, td->end_row - 1);
	for (int i = td->start_row; i < td->end_row; i++){
		td->result[i] = 0;  //得先初始化
		for (int j = 0; j < td->num_cols; j++){
			td->result[i] += td->matrix[i * td->num_cols + j] * td->vector[j];
		}
	}
	return NULL;
}

3.3 主函数测试

int main(int argx, char* argv[]){
  int num_threads = strtol(argv[1], NULL, 10);
  int num_rows = 10; // 示例:矩阵行数
  int num_cols = 10; // 示例:矩阵列数和向量的元素数
  double m[num_rows * num_cols];
  double v[num_cols];
  double result[num_rows];
  // 初始化矩阵和向量
  srand(time(0));
  for (int i = 0; i < num_rows; i++) {
      for (int j = 0; j < num_cols; j++) {
          m[i * num_cols + j] = rand() % 10; // 示例初始化
      }
  }
  for (int i = 0; i < num_cols; i++) {
      v[i] = rand() % 20; // 示例初始化
  }

  // 创建线程数组和数据结构数组
  pthread_t threads[num_threads];
  ThreadData thread_data[num_threads];  
  //声明并初始化了一个数组,其每个元素都是 ThreadData 类型的结构体。
  //这个结构体被设计为存储每个线程所需要的所有数据,以便线程可以独立地执行它的任务。

  // 分配每个线程处理的行数
  int rows_per_thread = num_rows / num_threads;
  int extra_rows = num_rows % num_threads;  // 行数除以线程数除不尽,剩下的(余数)
	
  int start_row = 0;
  for (int i = 0; i < num_threads; i++){
  	thread_data[i].rank = i;
	  thread_data[i].start_row = start_row;
	  thread_data[i].end_row = start_row + rows_per_thread + (i < extra_rows ? 1 : 0);	
	  thread_data[i].num_cols = num_cols;
	  thread_data[i].matrix = m;
	  thread_data[i].vector = v;
	  thread_data[i].result = result;
	  pthread_create(&threads[i], NULL, pth_mat_vect, &thread_data[i]);
	  start_row = thread_data[i].end_row;
  } 
	//等待所有线程完成
	for (int i = 0; i < num_threads; i++){
	  pthread_join(threads[i], NULL);
  }

	printf("the matrix is : \n");
	for (int i = 0; i < num_rows; i++) {
      for (int j = 0; j < num_cols; j++) {
      printf("%f  ", m[i * num_cols + j]);
      }
      printf("\n");
  }
  printf("the vector is : \n");
  for (int i = 0; i < num_cols; i++) {
      printf("%d  ", v[i]);
  }
  printf("\n");
	// 输出结果
	printf("the result is: \n");
	for (int i = 0; i < num_rows; i++){
		printf("%f  ", result[i]);
	}
	printf("\n");

  return 0;
}

3.4结果展示

在这里插入图片描述


总结

  1. 多线程编程过程:
    • 创建线程
    • 运行线程
    • 停止线程结束(合并到主线程)
  2. 在创建线程中,值得注意的是要显式地启动线程并构造能够存储线程信息的数据结构。要分配相应的内存(如果使用动态申请内存的话,也可以直接使用数组定义)
  3. 线程函数,非常地amazing啊。只接受一个参数,所以该参数的类型需要自己定义,怎么在线程函数中使得其大放异彩(看矩阵乘法);其中还包括了其计算得到的结果
  4. 当所有线程运行完成之后,才能执行下一个命令。

参考文献

  1. 【团日活动】C++实现高性能并行计算——⑨pthreads并行编程

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

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

相关文章

win10 配置OpenCV LNK2019 无法解析的外部符号 “void __cdecl cv::imshow

1 遇到问题 严重性 代码 说明 项目 文件 行 禁止显示状态 详细信息 错误 LNK2019 无法解析的外部符号 “void __cdecl cv::imshow(class std::basic_string<char,struct std::char_traits,class std::allocator > const &,class cv::debug_build_guard::_InputArray…

照片彻底删除了如何恢复?学会这5招恢复不求人!

我们的日常生活已经离不开视频和照片&#xff0c;很多小伙伴在清理各种电脑文件和SD卡时&#xff0c;很容易误删除一些重要的视频和照片&#xff0c;那么如何找回删除的视频和照片&#xff1f;下面一起来看看↓↓↓ 找回删除的视频和照片教程一、备份恢复 备份是防止数据丢失…

基于springboot+vue+Mysql的生鲜交易系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

系统性文献综述的撰写(Systematic Review)

文献综述 什么是文献综述 对某一个“领域、专业、课题、问题、研究专题”&#xff0c;通过搜集大量的相关资料&#xff08;别人发表的论文&#xff09;&#xff0c;然后通过“阅读、分析、归纳、整理”给出最新进展、学术见解或建议。对其做出综合性介绍和阐述的一种学术论文…

吴恩达机器学习笔记:第 8 周-14降维(Dimensionality Reduction) 14.3-14.5

目录 第 8 周 14、 降维(Dimensionality Reduction)14.3 主成分分析问题14.4 主成分分析算法14.5 选择主成分的数量 第 8 周 14、 降维(Dimensionality Reduction) 14.3 主成分分析问题 主成分分析(PCA)是最常见的降维算法。 在 PCA 中&#xff0c;我们要做的是找到一个方向…

基于SpringBoot和PostGIS的各省与地级市空间距离分析

目录 前言 一、PostGIS时空库 1、时空表设计 2、空间数据管理与查询 二、后台接口设计 1、ORM层设计与实现 2、业务层设计与实现 3、控制层设计 三、web可视化设计与实现 1、省份范围展示 2、城市距离可视化 3、成果展示 总结 前言 在上一篇博客中基于Java和GDAL实…

npm安装时一直idealTree:npm: sill idealTree buildDeps卡住不动

npm安装时一直idealTree:npm: sill idealTree buildDeps卡住不动 解决步骤&#xff1a; 1.去以下的目录中删掉.npmrc文件&#xff08;只在C:\User.npmrc&#xff09; 2.清除缓存&#xff0c;使用npm cache verify 不要用npm cache clean --force&#xff0c;容易出现npm WAR…

科智牧RFID电子耳标识读器-GALLAGHER盖力格平替之王

在畜牧业管理中&#xff0c;RFID技术的应用日益广泛。而科智牧作为畜牧业RFID行业的领先品牌&#xff0c;其RFID电子耳标识读器的性能不输于GALLAGHER盖力格的电子耳标识读器产品。采用RFID无线射频识别技术&#xff0c;能够快速、准确地读取动物耳标中的信息&#xff0c;无需直…

客户端连接ZK失败处理方案

文章目录 背景介绍报错信息处理方案第一步、查看zookeeper启动是否正常第二步、检查本地网络是否正常第三步、检查本地JDK版本 对于zookeeper服务注册中心&#xff0c;在前期【 Dubbo框架注册中心-Zookeeper搭建】博客中有环境搭建部署介绍&#xff0c;感兴趣可以参考安装。 背…

【电路笔记】-RC振荡器电路

RC振荡器电路 文章目录 RC振荡器电路1、概述2、RC 相移网络3、基本RC振荡器电路4、运算放大器RC振荡器5、运算放大器相位滞后RC振荡器电路6、RC振荡器示例11、概述 RC 振荡器使用放大器和 RC 反馈网络的组合,由于级之间的相移而产生输出振荡。 当单级晶体管放大器作为共发射…

iOS 实现视图遮罩效果

有时候&#xff0c;我们会遇到这种需求&#xff0c;只讲视图的某个部分展示出来 这时候&#xff0c;我们可以通过设置该视图layer.mask layerb来实现&#xff0c;需要注意的是&#xff0c;这里的layerb必须要设置backgroundColor&#xff0c;渐变layer有colors,否则达不到效果…

一站式服务:教你搭建AI知识库

在信息化高速发展的今天&#xff0c;知识管理已成为企业提升竞争力的重要因素。而AI知识库&#xff0c;作为知识管理的高级形态&#xff0c;被很多企业选择。那么&#xff0c;如何打造一款高效、智能的AI知识库呢&#xff1f;本文的一站式服务将为您一一解答。 一、明确需求与目…

解决idea不识别${pageContext.request.contextPath}的方法

文章目录 一、产生原因二、解决方法——直接修改web.xml文件三、修改模板——找到web.xml模板&#xff0c;修改替换 一、产生原因 由于web.xml 使用的web-app版本号过低。导致无法识别"{pageContext.request.contextPath}"。 IDEA在创建javaweb项目的时候&#xff0…

畅聊AI生成PPT,ChatPPT神奇测评

大家好&#xff0c;我是清辞&#xff0c;很荣幸能够参与“AI应用有奖试用评测征集【第一期】——畅聊AI生成PPT”的活动。 我们都知道现在AI生成ppt非常火爆&#xff0c;但是许多AI生成的PPT模板显得单调乏味&#xff0c;用词缺乏新意&#xff0c;往往只是对已有数据的简单模仿…

P5931 灯泡

题目描述 相比 Wildleopard 的家&#xff0c;他的弟弟 Mildleopard 比较穷。他的房子是狭窄的&#xff0c;而且在他的房间里仅有一个灯泡。每天晚上&#xff0c;他徘徊在自己狭小的房子里&#xff0c;思考如何赚更多的钱。有一天&#xff0c;他发现他的影子的长度随着他在灯泡…

密文域可逆信息隐藏技术综述(下)

与联合RDH-EI算法相比&#xff0c;可分离RDH-EI算法提取秘密信息时仅需要隐藏密钥&#xff0c;实现了加密者和隐藏者独立操作、互不干扰&#xff0c;扩大了RDH-EI的使用范围&#xff0c;得到了研究者的广泛关注。现有可分离RDH-EI可分为基于加密前预留空间(reserving room befo…

如何消除浏览器SmartScreen对网站“不安全”提示?

面对互联网时代用户对网站安全性和可信度的严苛要求&#xff0c;网站运营者时常遭遇Microsoft Defender SmartScreen&#xff08;SmartScreen&#xff09;提示网站不安全的困扰。本文将剖析SmartScreen判定网站不安全的原因&#xff0c;并为运营者提供应对策略&#xff0c;以恢…

Kubernetes - Dashboard 配置用户名密码方式登录

Kubernetes - Dashboard 配置用户名密码方式登录 前言&#xff1a; 为了 K8s 集群安全&#xff0c;默认情况下 Dashboard 以 Token的形式登录的&#xff0c;那如果我们想以用户名/密码的方式登录该怎么操作呢&#xff1f;其实只需要我们创建用户并进行 ClusterRoleBinding绑定即…

J9inceptionv3

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊# 前言 上周学习了inceptionv1网络&#xff0c;这周学习其改进版本inceptionv3 简介 Inception v3是谷歌研究团队提出的深度卷积神经网络架构&#xff0c;通过…

碳化硅片有哪些比较重要的参数?

知识星球&#xff08;星球名&#xff1a;芯片制造与封测社区&#xff09;里的学员问&#xff1a;请问碳化硅衬底片到客户端验证主要测试什么项目&#xff0c;比较重要的参数有哪些&#xff1f; Lattice Parameters&#xff1a;晶格参数。确保衬底的晶格常数与将要生长的外延层…