4. 初探MPI——集体通信

2024-05-11 21:04
文章标签 通信 初探 mpi 集体

本文主要是介绍4. 初探MPI——集体通信,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

系列文章目录

  1. 初探MPI——MPI简介
  2. 初探MPI——(阻塞)点对点通信
  3. 初探MPI——(非阻塞)点对点通信
  4. 初探MPI——集体通信

文章目录

  • 系列文章目录
  • 前言
  • 一、集体通信以及同步点
  • 二、`MPI_Bcast` 广播
    • 2.1 使用`MPI_Send` 和 `MPI_Recv` 来做广播
    • 2.2 `MPI_Bcast` 和 `MPI_Send` 以及 `MPI_Recv` 的比较
    • 2.3 Blocking or non-blocking ?
  • 三、`MPI Scatter`, `Gather`, and `Allgather`
    • 3.1 `MPI_Scatter`介绍
    • 3.2 `MPI_Gather` 的介绍
    • 3.3 使用 `MPI_Scatter` 和 `MPI_Gather` 来计算平均数
    • 3.4 `MPI_Allgather`
  • 四、并行排名
    • 4.1 问题概述
    • 4.2 并行排名API定义
    • 4.3 解决并行排名问题
      • 4.3.1 对所有进程中的数字进行排序
      • 4.3.2 排序数字并维护所属
      • 4.3.3 整合
    • 4.4 最终结果
  • 五、`MPI Reduce` and `Allreduce`
    • 5.1 归约/归化(reduce)简介
    • 5.2 `MPI_Reduce`
    • 5.3 使用`MPI_Reduce`计算均值
    • 5.4 `MPI_Allreduce`
      • 5.4.1 使用 `MPI_Allreduce` 计算标准差
  • 总结
  • 参考


前言

点对点通信的方式只会涉及两个不同进程之间的通信。而集体通信指的是涉及 communicator 里面所有进程的一个方法。

接下来的内容将要讲述:

  • Broadcast : One process sends a message to every other process
  • Reduction : One process gets data from all the other processes and applies an operation on it (sum, minimum, maximum, etc.
  • Scatter : A single process partitions the data to send pieces to every other process 单个进程将数据分区然后将数据块发送到其他进程
  • Gather : A single process assembles the data from different process in a buffer 单个进程将来自不同进程的数据组装在缓冲区中

一、集体通信以及同步点

同步点:这意味着所有的进程在执行代码的时候必须首先都到达一个同步点才能继续执行后面的代码。

MPI 有一个特殊的函数来做同步进程的这个操作。

MPI_Barrier(MPI_Comm communicator)

在这里插入图片描述

注意:始终记得每一个你调用的集体通信方法都是同步的。也就是说,如果没法让所有进程都完成 MPI_Barrier,那么你也没法完成任何集体调用。如果你在没有确保所有进程都调用 MPI_Barrier的情况下调用了它,那么程序会空闲下来。

二、MPI_Bcast 广播

广播 (broadcast) 是标准的集体通信技术之一。一个广播发生的时候,一个进程会把同样一份数据传递给一个 communicator 里的所有其他进程。广播的主要用途之一是把用户输入传递给一个分布式程序,或者把一些配置参数传递给所有的进程。

在这里插入图片描述
广播可以使用 MPI_Bcast 来做到,函数声明是:

MPI_Bcast(void* data,int count,MPI_Datatype datatype,int root,MPI_Comm communicator)

尽管根节点和接收节点做不同的事情,它们都是调用同样的这个 MPI_Bcast 函数来实现广播。

  • 当根节点(在我们的例子是节点0)调用 MPI_Bcast 函数时, data 变量里的值会被发送到其他的节点上。
  • 当其他的节点调用 MPI_Bcast 时,data 变量会被赋值成从根节点接受到的数据。

2.1 使用MPI_SendMPI_Recv 来做广播

粗略看的话,似乎 MPI_Bcast 仅仅是在 MPI_SendMPI_Recv 基础上进行了一层包装。事实上,我们就可以自己来做这层封装。我们的函数叫做 my_bcast。它跟 MPI_Bcast 接受一样的参数,看起来像这样:

void my_bcast(void* data, int count, MPI_Datatype datatype, int root, MPI_Comm communicator){int rank;MPI_Comm_rank(communicator, &rank);int size;MPI_Comm_size(communicator, &size);if (rank == root){for (int i = 0; i < size; i++){if (i != rank){MPI_Send(data, count, datatype, i, 0, communicator);}}} else {MPI_Recv(data, count, datatype, root, 0, communicator, MPI_STATUS_IGNORE);}
}

这个函数的时间复杂度应该是O(n)的
在这里插入图片描述采用树算法的时间复杂度是O(logn)。

2.2 MPI_BcastMPI_Send 以及 MPI_Recv 的比较

在这里插入图片描述

2.3 Blocking or non-blocking ?

这里仅给出阻塞版本。but you just need to add the I to switch to non-blocking mode (eg MPI_Bcast will become MPI_Ibcast). non-blocking globals require the use of MPI_Wait and MPI_Test to be completed correctly.

三、MPI Scatter, Gather, and Allgather

两个额外的机制来补充集体通信的知识 - MPI_Scatter 以及 MPI_Gather。还会讲一个 MPI_Gather 的变体:MPI_Allgather

3.1 MPI_Scatter介绍

MPI_BcastMPI_Scatter 的主要区别很小但是很重要。

  • MPI_Bcast 给每个进程发送的是同样的数据,
  • MPI_Scatter 给每个进程发送的是一个数组的一部分数据。

在这里插入图片描述
尽管根进程(进程0)拥有整个数组的所有元素,MPI_Scatter 还是会把正确的属于进程0的元素放到这个进程的接收缓存中。

MPI_Scatter 函数的原型:

MPI_Scatter(void* send_data,int send_count,MPI_Datatype send_datatype,void* recv_data,int recv_count,MPI_Datatype recv_datatype,int root,MPI_Comm communicator)
  1. send_data是在根进程上的一个数据数组
  2. send_countsend_datatype分别描述了发送给每个进程的数据数量和数据类型:
    如果send_count 是1,send_datatypeMPI_INT的话,进程0会得到数据里的第一个整数,以此类推。如果send_count是2的话,进程0会得到前两个整数,进程1会得到第三个和第四个整数,以此类推。在实践中,一般来说send_count会等于数组的长度除以进程的数量。除不尽怎么办?会在后面讲这个问题 。
  3. 函数定义里面接收数据的参数跟发送的参数几乎相同。recv_data 参数是一个缓存,它里面存了recv_countrecv_datatype数据类型的元素。
  4. rootcommunicator 分别指定开始分发数组的根进程以及对应的communicator。

3.2 MPI_Gather 的介绍

MPI_GatherMPI_Scatter 是相反的。MPI_Gather 从好多进程里面收集数据到一个进程上面而不是从一个进程分发数据到多个进程。这个机制对很多平行算法很有用,比如并行的排序和搜索。
在这里插入图片描述MPI_Scatter类似,MPI_Gather从其他进程收集元素到根进程上面。元素是根据接收到的进程的秩排序的。MPI_Gather的函数原型跟MPI_Scatter长的一样。

MPI_Gather(void* send_data,int send_count,MPI_Datatype send_datatype,void* recv_data,int recv_count,MPI_Datatype recv_datatype,int root,MPI_Comm communicator)

MPI_Gather中,只有根进程需要一个有效的接收缓存。所有其他的调用进程可以传递NULLrecv_data

注意:别忘记recv_count参数是从每个进程接收到的数据数量,而不是所有进程的数据总量之和。这一点对MPI初学者来说经常容易搞错。

3.3 使用 MPI_ScatterMPI_Gather 来计算平均数

这段代码展示了如何使用MPI来把工作拆分到不同的进程上,每个进程对一部分数据进行计算,然后再把每个部分计算出来的结果汇集成最终的答案。程序步骤是:

  1. 在根进程(进程0)上生成一个充满随机数字的数组。
  2. 把所有数字用MPI_Scatter分发给每个进程,每个进程得到的同样多的数字。
  3. 每个进程计算它们各自得到的数字的平均数。
  4. 根进程收集所有的平均数,然后计算这个平均数的平均数,得出最后结果。
// Author: Wes Kendall
// Copyright 2012 www.mpitutorial.com
// This code is provided freely with the tutorials on mpitutorial.com. Feel
// free to modify it for your own use. Any distribution of the code must
// either provide a link to www.mpitutorial.com or keep this header intact.
//
// Program that computes the average of an array of elements in parallel using
// MPI_Scatter and MPI_Gather
//
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <mpi.h>
#include <assert.h>// Creates an array of random numbers. Each number has a value from 0 - 1
float *create_rand_nums(int num_elements) {float *rand_nums = (float *)malloc(sizeof(float) * num_elements);assert(rand_nums != NULL);int i;for (i = 0; i < num_elements; i++) {rand_nums[i] = (rand() / (float)RAND_MAX);}return rand_nums;
}// Computes the average of an array of numbers
float compute_avg(float *array, int num_elements) {float sum = 0.f;int i;for (i = 0; i < num_elements; i++) {sum += array[i];}return sum / num_elements;
}int main(int argc, char** argv) {if (argc != 2) {fprintf(stderr, "Usage: avg num_elements_per_proc\n");exit(1);}int num_elements_per_proc = atoi(argv[1]);// Seed the random number generator to get different results each timesrand(time(NULL));MPI_Init(NULL, NULL);int world_rank;MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);int world_size;MPI_Comm_size(MPI_COMM_WORLD, &world_size);// Create a random array of elements on the root process. Its total// size will be the number of elements per process times the number// of processesfloat *rand_nums = NULL;if (world_rank == 0) {rand_nums = create_rand_nums(num_elements_per_proc * world_size);}// For each process, create a buffer that will hold a subset of the entire// arrayfloat *sub_rand_nums = (float *)malloc(sizeof(float) * num_elements_per_proc);assert(sub_rand_nums != NULL);// Scatter the random numbers from the root process to all processes in// the MPI worldMPI_Scatter(rand_nums, num_elements_per_proc, MPI_FLOAT, sub_rand_nums,num_elements_per_proc, MPI_FLOAT, 0, MPI_COMM_WORLD);// Compute the average of your subsetfloat sub_avg = compute_avg(sub_rand_nums, num_elements_per_proc);// Gather all partial averages down to the root processfloat *sub_avgs = NULL;if (world_rank == 0) {sub_avgs = (float *)malloc(sizeof(float) * world_size);assert(sub_avgs != NULL);}MPI_Gather(&sub_avg, 1, MPI_FLOAT, sub_avgs, 1, MPI_FLOAT, 0, MPI_COMM_WORLD);// Now that we have all of the partial averages on the root, compute the// total average of all numbers. Since we are assuming each process computed// an average across an equal amount of elements, this computation will// produce the correct answer.if (world_rank == 0) {float avg = compute_avg(sub_avgs, world_size);printf("Avg of all elements is %f\n", avg);// Compute the average across the original data for comparisonfloat original_data_avg =compute_avg(rand_nums, num_elements_per_proc * world_size);printf("Avg computed across original data is %f\n", original_data_avg);}// Clean upif (world_rank == 0) {free(rand_nums);free(sub_avgs);}free(sub_rand_nums);MPI_Barrier(MPI_COMM_WORLD);MPI_Finalize();
}

上面这段代码有点犯懒,我本人没有写,直接copy Wes Kendall的代码,我简单来讲讲这个代码的思路:

// 在根进程中,随机产生elements_per_proc * world_size个随机数字的数组
if (world_rank == 0) {rand_nums = create_rand_nums(elements_per_proc * world_size);
}// 创建一个根进程传给其他进程的子数组作为缓存区,子数组的长度为elements_per_proc
float *sub_rand_nums = malloc(sizeof(float) * elements_per_proc);// 创建好了子数组作为缓存区之后,根进程就开始分发数据了
MPI_Scatter(rand_nums, elements_per_proc, MPI_FLOAT, sub_rand_nums,elements_per_proc, MPI_FLOAT, 0, MPI_COMM_WORLD);// 将每个进程得到的子数组的元素去平均值
float sub_avg = compute_avg(sub_rand_nums, elements_per_proc);// 再创建一个缓存区用来存储其他进程上传它们计算得到的平均值
float *sub_avgs = NULL;
if (world_rank == 0) {sub_avgs = malloc(sizeof(float) * world_size);
}
// 根进程gather到了其他进程传过来的数据
MPI_Gather(&sub_avg, 1, MPI_FLOAT, sub_avgs, 1, MPI_FLOAT, 0,MPI_COMM_WORLD);// 再去计算这些平均数的平均数们就能得到总的平均数
if (world_rank == 0) {float avg = compute_avg(sub_avgs, world_size);
}

3.4 MPI_Allgather

上面两个用来操作多对一或者一对多通信模式,也就是说多个进程要么向一个进程发送数据,要么从一个进程接收数据。

MPI_Allgather却是多个元素到多个进程(也就是多对多通信模式)。

在这里插入图片描述函数原型:

MPI_Allgather(void* send_data,int send_count,MPI_Datatype send_datatype,void* recv_data,int recv_count,MPI_Datatype recv_datatype,MPI_Comm communicator)

MPI_Allgather的方法定义跟MPI_Gather几乎一样,只不过MPI_Allgather不需要root这个参数来指定根节点。

四、并行排名

4.1 问题概述

在这里插入图片描述

4.2 并行排名API定义

在深入研究并行排名问题之前,首先确定函数的行为方式。

  1. 函数需要在每个进程上取一个数字,并返回其相对于所有其他进程中的数字的排名
  2. 需要正在使用的communicator
  3. 被排名的数字的数据类型

函数原型:

TMPI_Rank(void* send_data, //作为缓冲区void* recv_data, // send_datade 排名MPI_Datatype datatype,MPI_Comm comm)

4.3 解决并行排名问题

4.3.1 对所有进程中的数字进行排序

最简单的方法是将所有数字收集到一个进程中并对数字进行排序。gather_numbers_to_root 函数负责将所有数字收集到根进程(root process)。

// 为进程0的TMPI_Rank收集数字。为MPI的数据类型分配空间
// 对进程0返回 void * 指向的缓冲区
// 对所有其他进程返回NULL
void *gather_numbers_to_root(void *number, MPI_Datatype datatype,MPI_Comm comm) {int comm_rank, comm_size;MPI_Comm_rank(comm, &comm_rank);MPI_Comm_size(comm, &comm_size);// 在根进程上分配一个数组// 数组大小取决于所用的MPI数据类型int datatype_size;MPI_Type_size(datatype, &datatype_size);void *gathered_numbers;if (comm_rank == 0) {gathered_numbers = malloc(datatype_size * comm_size);}// 在根进程上收集所有数字MPI_Gather(number, 1, datatype, gathered_numbers, 1,datatype, 0, comm);return gathered_numbers;
}
  • gather_numbers_to_root 函数获取要收集的数字(即 send_data 变量)、数字的数据类型 datatypecomm 通讯器。
  • 根进程必须在此函数中收集 comm_size 个数字,因此它会分配 datatype_size * comm_size 长度的数组.
  • 这里通过使用新的MPI函数- MPI_Type_size 来收集datatype_size变量。

4.3.2 排序数字并维护所属

  • 在我们的排名函数中,排序数字不一定是难题。CPP中提供了许多排序算法(我们也可以自己写)
  • 排序的困难在于,我们必须维护各个进程将数字发送到根进程的次序。 如果我们要对收集到根进程的数组进行排序而不给数字附加信息,则根进程将不知道如何将数字的排名发送回原来请求的进程!
  • 为了便于将所属进程附到对应数字上,我们在代码中创建了一个结构体(struct)来保存此信息。
    该结构体定义如下:
// 保存进程在通讯器中的次序(rank)和对应数字
// 该结构体用于数组排序,
// 并同时完整保留所属进程信息typedef struct {int comm_rank;union {float f;int i;} number;
} CommRankNumber;

CommRankNumber 结构体保存了我们要排序的数字(记住它可以是浮点数或整数,因此我们使用联合体union),并且它拥有该数字所属进程在通讯器中的次序(rank)

get_ranks 函数,负责创建这些结构体并对它们进行排序。

// 这个函数在根进程上对收集到的数字排序
// 返回一个数组,数组按进程在通讯器中的次序排序
// 注意 - 该函数只在根进程上运行int *get_ranks(void *gathered_numbers, int gathered_number_count,MPI_Datatype datatype) {int datatype_size;MPI_Type_size(datatype, &datatype_size);// 将收集到的数字数组转换为CommRankNumbers数组// 这允许我们在排序的同时,完整保留数字所属进程的信息CommRankNumber *comm_rank_numbers = malloc(gathered_number_count * sizeof(CommRankNumber));int i;for (i = 0; i < gathered_number_count; i++) {comm_rank_numbers[i].comm_rank = i;memcpy(&(comm_rank_numbers[i].number),gathered_numbers + (i * datatype_size),datatype_size);}// 根据数据类型对comm_rank_numbers排序if (datatype == MPI_FLOAT) {qsort(comm_rank_numbers, gathered_number_count,sizeof(CommRankNumber), &compare_float_comm_rank_number);} else {qsort(comm_rank_numbers, gathered_number_count,sizeof(CommRankNumber), &compare_int_comm_rank_number);}// 现在comm_rank_numbers是排好序的,下面生成一个数组,// 包含每个进程的排名,数组第i个元素是进程i的数字的排名int *ranks = (int *)malloc(sizeof(int) * gathered_number_count);for (i = 0; i < gathered_number_count; i++) {ranks[comm_rank_numbers[i].comm_rank] = i;}// 清理并返回排名数组free(comm_rank_numbers);return ranks;
}
  • get_ranks 函数首先创建一个CommRankNumber结构体数组,并附上该数字所属进程在通讯器中的次序。 如果数据类型为 MPI_FLOAT ,则对我们的结构体数组调用 qsort 时,会使用特殊的排序函数。类似的,如果数据类型为 MPI_INT ,我们将使用不同的排序函数。

  • 在对数字进行排序之后,我们必须以适当的顺序创建一个排名数组(array of ranks),以便将它们分散(scatter)回到请求的进程中。这是通过创建 ranks 数组并为每个已排序的 CommRankNumber 结构体填充适当的排名来实现的。

4.3.3 整合

现在有了两个主要函数,可以将它们全部整合到 TMPI_Rank 函数中。此函数将数字收集到根进程,并对数字进行排序以确定其排名,然后将排名分散回请求的进程。 代码如下所示:

// 获取send_data的排名, 类型为datatype
// 排名用recv_data返回,类型为datatype
int TMPI_Rank(void *send_data, void *recv_data, MPI_Datatype datatype,MPI_Comm comm) {// 首先检查基本情况 - 此函数只支持MPI_INT和MPI_FLOATif (datatype != MPI_INT && datatype != MPI_FLOAT) {return MPI_ERR_TYPE;}int comm_size, comm_rank;MPI_Comm_size(comm, &comm_size);MPI_Comm_rank(comm, &comm_rank);// 为了计算排名,必须将数字收集到一个进程中// 对数字排序, 然后将排名结果分散传回// 首先在进程0上收集数字void *gathered_numbers = gather_numbers_to_root(send_data, datatype,comm);// 获取每个进程的次序(rank)int *ranks = NULL;if (comm_rank == 0) {ranks = get_ranks(gathered_numbers, comm_size, datatype);}// 分散发回排名结果MPI_Scatter(ranks, 1, MPI_INT, recv_data, 1, MPI_INT, 0, comm);// 清理if (comm_rank == 0) {free(gathered_numbers);free(ranks);}
}

TMPI_Rank 函数使用我们刚刚创建的两个函数 gather_numbers_to_rootget_ranks 来获取数字的排名。然后,函数执行最后的 MPI_Scatter,以将所得的排名分散传回进程。

4.4 最终结果

最终代码参看tutorials/performing-parallel-rank-with-mpi/code/tmpi_rank.c

以下是整个数据流说明:
在这里插入图片描述

五、MPI Reduce and Allreduce

5.1 归约/归化(reduce)简介

这个概念在OpenMPI中介绍过,详情参见C/C++实现高性能并行计算——2.使用OpenMP进行共享内存编程

5.2 MPI_Reduce

函数原型:

MPI_Reduce(void* send_data,void* recv_data,int count,MPI_Datatype datatype,MPI_Op op,int root,MPI_Comm communicator)
  1. send_data 参数是每个进程都希望归约的 datatype 类型元素的数组。
  2. recv_data 仅与具有 root 秩的进程相关。 recv_data 数组包含归约的结果,大小为sizeof(datatype)* count
  3. op 参数是希望应用于数据的操作。 MPI 包含一组可以使用的常见归约运算。 尽管可以定义自定义归约操作,但这里不作介绍。

MPI定义的归约操作包括:
在这里插入图片描述
在这里插入图片描述

5.3 使用MPI_Reduce计算均值

在 第三节 中,展示了使用 MPI_ScatterMPI_Gather 计算平均值。 使用 MPI_Reduce 可以简化上一节的代码。

float *rand_nums = NULL;
rand_nums = create_rand_nums(num_elements_per_proc);// Sum the numbers locally
float local_sum = 0;
int i;
for (i = 0; i < num_elements_per_proc; i++) {local_sum += rand_nums[i];
}// Print the random numbers on each process
printf("Local sum for process %d - %f, avg = %f\n",world_rank, local_sum, local_sum / num_elements_per_proc);// Reduce all of the local sums into the global sum
float global_sum;
MPI_Reduce(&local_sum, &global_sum, 1, MPI_FLOAT, MPI_SUM, 0,MPI_COMM_WORLD);// Print the result
if (world_rank == 0) {printf("Total sum = %f, avg = %f\n", global_sum,global_sum / (world_size * num_elements_per_proc));
}
  1. 每个进程都会创建随机数并计算和保存在 local_sum
  2. 然后使用 MPI_SUMlocal_sum 归约至根进程。
  3. 全局平均值为 global_sum / (world_size * num_elements_per_proc)

5.4 MPI_Allreduce

如果所有进程而不是仅仅在根进程中访问归约的结果。 MPI_Allreduce 将归约值并将结果分配给所有进程。 函数原型如下:

MPI_Allreduce(void* send_data,void* recv_data,int count,MPI_Datatype datatype,MPI_Op op,MPI_Comm communicator)

在这里插入图片描述

5.4.1 使用 MPI_Allreduce 计算标准差

详情代码见tutorials/mpi-reduce-and-allreduce/code/reduce_stddev.c


总结

其实这篇文章到了后期我写的不是很满意,因为代码都是直接复制粘贴,不像之前的都是自己写再去对照别人的代码。可能是状态不好,也有可能是太急于求成了,罪过,罪过。太想进步啦!!!后期我可能会重新编辑这篇文章,代码我也要重新写一遍。

  1. 集体通信和同步点
  2. 广播MPI_Bcast
  3. 发散MPI_Scatter和收集MPI_Gather(所有进程都要一份)MPI_Allgather
  4. 归约/归化 MPI_Reduce,所有进程都要一份归化MPI_Allreduce

参考

  1. MPI Tutorials

这篇关于4. 初探MPI——集体通信的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/980645

相关文章

poj 1502 MPI Maelstrom(单源最短路dijkstra)

题目真是长得头疼,好多生词,给跪。 没啥好说的,英语大水逼。 借助字典尝试翻译了一下,水逼直译求不喷 Description: BIT他们的超级计算机最近交货了。(定语秀了一堆词汇那就省略吧再见) Valentine McKee的研究顾问Jack Swigert,要她来测试一下这个系统。 Valentine告诉Swigert:“因为阿波罗是一个分布式共享内存的机器,所以它的内存访问

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2

vue2 组件通信

props + emits props:用于接收父组件传递给子组件的数据。可以定义期望从父组件接收的数据结构和类型。‘子组件不可更改该数据’emits:用于定义组件可以向父组件发出的事件。这允许父组件监听子组件的事件并作出响应。(比如数据更新) props检查属性 属性名类型描述默认值typeFunction指定 prop 应该是什么类型,如 String, Number, Boolean,

linux中使用rust语言在不同进程之间通信

第一种:使用mmap映射相同文件 fn main() {let pid = std::process::id();println!(

C++编程:ZeroMQ进程间(订阅-发布)通信配置优化

文章目录 0. 概述1. 发布者同步发送(pub)与订阅者异步接收(sub)示例代码可能的副作用: 2. 适度增加缓存和队列示例代码副作用: 3. 动态的IPC通道管理示例代码副作用: 4. 接收消息的超时设置示例代码副作用: 5. 增加I/O线程数量示例代码副作用: 6. 异步消息发送(使用`dontwait`标志)示例代码副作用: 7. 其他可以考虑的优化项7.1 立即发送(ZMQ_IM

VB和51单片机串口通信讲解(只针对VB部分)

标记:该篇文章全部搬自如下网址:http://www.crystalradio.cn/thread-321839-1-1.html,谢谢啦            里面关于中文接收的部分,大家可以好好学习下,题主也在研究中................... Commport;设置或返回串口号。 SettingS:以字符串的形式设置或返回串口通信参数。 Portopen:设置或返回串口

深入理解TCP通信

这大概是自己博客上面第三次写TCP通信demo了,总是写同样的内容也不太好啊,不过每一次都比前一次进步一点。这次主要使用了VIM编辑工具、gdb调试、wireshirk、netstat查看网络状态。 参考《C++服务器视频教程》、《Unix网络编程》 一、VIM常用命令 vim server.cpp #打开一个文件:w 写入文件:wq 保存并退出:q! 不保存退出显示行号

电子电气架构---私有总线通信和诊断规则

电子电气架构—私有总线通信和诊断规则 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节能减排。 无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事.而不是让内心的烦躁、

Java注解初探

什么是注解 注解(Annotation)是从JDK5开始引入的一个概念,其实就是代码里的一种特殊标记。这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过注解开发人员可以在不改变原有代码和逻辑的情况下在源代码中嵌入补充信息。有了注解,就可以减少配置文件,现在越来越多的框架已经大量使用注解,而减少了XML配置文件的使用,尤其是Spring,已经将注解玩到了极致。 注解与XML配置各有