相关章节:第13章组通信MPI程序设计。

MPI组通信与点到点通信的一个重要区别就是:组通信需要特定组内所有成员参与,而点对点通信只涉及到发送方和接收方。

由于需要组内所有成员参与,因此也是一种比较复杂的通信方式。程序员在设计组通信语句的时候,需要同时考虑两点:

a. 程序运行起来之后,当前正在运行的进程的行为方式

b. 将组通信作为一个整体,考虑所有进程的行为方式

(1)概述

组通信从功能上实现了三个方面:

a. 通信:完成组内数据传输(广播、收集、散发、组收集、全互换各种数据交换传输方式

b. 同步:能够让组内所有进程在特定的位置上执行进度上取得一致,组通信虽然可以有同步功能,但是并不意味这同步一定发生(MPI_Barrier同步函数

c. 计算:通过给定数据的OP归约操作完成(分为MPI预定义OP归约操作、用户自定义OP归约操作

下面分别阐述上述三个方面的相关代码示例,采用的方法是把书上的代码段扩充成完整的可执行的程序来体会。

(2)各部分代码

下面阐述几个代码示例,把通信、同步、计算的例子都融入到具体的代码中。

示例1(Bcast广播通信)

从root进程读入一个数,并Bcast广播给组内所有进程并打印到终端上,代码如下:

#include "mpi.h"
#include <stdio.h>
#include <stdlib.h> int main(int argc, char *argv[])
{
int rank, value=;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
while(value>=)
{
if (==rank) {
scanf("%d", &value);
}
MPI_Bcast(&value, , MPI_INT, , MPI_COMM_WORLD);
printf("Process %d got %d\n", rank, value);
}
MPI_Finalize();
return ;
}

代码执行结果如下:

上述代码中涉及到了Bcast函数的基本用法,在每个进程中都要出现,然后通过函数参数中的root进程号,进而区分哪个进程是发送方。

示例2(Alltoall全互换通信)

全互换。全互换是形式上最复杂的一种组通信方式,内容上是各种通信形式的全集。

全互换通信模式中,在组内所有通信涉及到的进程中:

a. 每个进程发送给其他进程的数据是不同的

b. 每个进程从其他进程获得数据也是不同的

这样描述还是太抽象,通过具体的图来解释一下:

上图描述了全互换发生前和发生后,数据变化的情况:

纵轴代表不同进程编号;左图的横轴代表发送缓冲区的段位大小,右图的横轴代表接受缓冲区的大小。

a. 第i个进程的发送缓冲区中的第j段数据(即左图Aij)代表的意义是:第i个进程发送给第j个进程的数据

b. 第j个进程的接受缓冲区中的第i段数据(即右图的Aji)代表的意义是:第j个进程从第i个进程中接收到的数据

结合a、b两点,我们可以知道全互换这种方式类似:将“进程-发送缓冲区”矩阵,转置成“进程-接受缓冲区”矩阵

对于同一对(i,j),左图的Aij的大小要求一定与右图的Aji的大小一样。

比如第0个进程发送给第2个进程的数据(即A02),必须与第2个进程给第0个进程留出来的接受缓冲区大小一致(即A20),满足这样的条件,就实现了全互换。

每个进程在全通信中既向所有进程发送数据,又同时从所有进程接受数据,但是需要注意上述的发送、接受缓冲区大小匹配。

看如下的代码:

 #include "mpi.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h> int main(int argc, char *argv[])
{
int rank, size;
int chunk = ; // 发送到一个进程的数据块大小
int i,j;
int *sbuf, *rbuf; MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
// 申请发送缓冲区 和 接收缓冲区
sbuf = (int*)malloc(size*chunk*sizeof(int)); // 申请发送缓冲区
if (!sbuf) {
perror("can't allocate send buffer");
MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
}
rbuf = (int*)malloc(size*chunk*sizeof(int)); // 申请接收缓冲区
if (!rbuf) {
perror("can't allocate recv buffer");
free(sbuf);
MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
}
// 设置每个进程的发送缓冲区 和 接收缓冲区的数据
for(i=; i<size; i++) // i代表进程编号
{
for(j=; j<chunk; j++) // j代表myid发送给进程i的第j个数据
{
sbuf[i*chunk+j] = rank+i*chunk+j; // 设置发送缓冲区的数据
printf("myid = %d, send to id = %d, data[%d] = %d\n",rank ,i ,j, sbuf[i*chunk+j]);
rbuf[i*chunk+j] = ; // 接收缓冲区清零
}
}
// 执行all to all调用
MPI_Alltoall(sbuf, chunk, MPI_INT, rbuf, chunk, MPI_INT, MPI_COMM_WORLD);
// 打印接收缓冲区从其他进程接收的数据
for(i=; i<size; i++)
{
for(j=; j<chunk; j++)
{
printf("myid = %d, recv from id = %d, data[%d] = %d\n",rank ,i ,j, rbuf[i*chunk+j]);
}
}
free(rbuf);
free(sbuf);
MPI_Finalize();
return ;
}

程序执行结果如下:

一共有2个进程,每个进程共有4个数据;可以看到执行全互换后的效果。

示例3

利用近似积分公式求圆周率π的大小。(Bcast广播、Barrier同步、REDUCE归约)

圆周率的大小可以用一个积分表达式来求解,求这个积分的过程可以用MPI组通信技术实现

代码如下:

 #include "mpi.h"
#include <stdio.h>
#include <stdlib.h> double f(double); int main(int argc, char *argv[])
{
int rank, size;
int n; // 细分出来的小矩阵个数
double pi; // 存放pi的计算值 MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &size);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
if (==rank) {
printf("number of small rectangle:\n");
scanf("%d",&n);
}
MPI_Bcast(&n, , MPI_INT, , MPI_COMM_WORLD); // 将n值广播到各个进程中
int i;
double x, h, sum=0.0;
for (i=rank+; i<=n; i+=size)
{
x = (i-0.5)/(double)n;
sum += f(x);
}
sum = sum/n;
MPI_Reduce(&sum, &pi, , MPI_DOUBLE, MPI_SUM, , MPI_COMM_WORLD);
if (==rank) {
printf("Approximately of pi is : %.16f\n",pi);
fflush(stdout);
}
MPI_Finalize();
} double f(double x) { return 4.0/(1.0+x*x); }

上述代码是按照如下思路实现的:

比如总共要把积分区间按X轴划分成1000份,即计算任务是求1000个小矩形的面积,

而MPI计算进程共有5个;则为了负载均衡,考虑让每个计算节点平均搞定200个小矩形的面积。

代码技巧:

这里有个需要处理的地方是:如何告诉每个计算节点(进程),需要计算哪些小矩形的面积呢?

一种直观的思路是,让第0个进程处理0~199小矩形,让第1个进程处理200~399小矩形...,以此类推。

上述的思路没有错误,但是对边界条件的处理可能麻烦一些:因为用这种连续区间的任务分配方式,就需要知道提前知道头和尾各在哪里,需要额外处理。

书上给的代码技巧是通过间隔划分的方式,比如有5个计算进程,第0个进程计算0,5,10,...这些矩形面积,第1个进程计算1,6,11,...这些矩形面积。

这样一来,任务分配的代码就简单了,一个循环直接搞定,而且不同进程不用区别对待。

上述代码执行结果如下:

小矩形个数越多,计算的值约逼近真实值。

示例4

有一个大数据集,每个计算进程中各有数据集中的一部分,现在想让每个进程都有全部的数据集(Bcast广播、Barrier同步)

代码如下:

 #include "mpi.h"
#include <stdlib.h>
#include <stdio.h> int main(int argc, char *argv[])
{
int rank,size,i;
int *table;
int errors = ;
MPI_Aint address;
MPI_Datatype type, newtype;
int lens; MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
table = (int*)calloc(size, sizeof(int));
table[rank] = rank + ; // 准备要广播的数据
MPI_Barrier(MPI_COMM_WORLD);
for(i=; i<size; i++) MPI_Bcast(&table[i], , MPI_INT, i, MPI_COMM_WORLD); // 每个进程只当一次root
for(i=; i<size; i++)
{
if (table[i]!=i+) {
errors++;
}
}
MPI_Barrier(MPI_COMM_WORLD);
if (==rank) {
for(i=;i<size;i++) printf("table[%d]:%d\n",i,table[i]);
}
MPI_Finalize();
}

代码执行结果如下:

代码分析:

a. line19执行了第一次Barrier同步,目的是让各个进程都准备好各自拥有数据集的一部分(这里同步的目的是,让各个进程一方面table分配好空间,并且准备好数据)

b. line27执行了第二次Barrier同步,目的是让Bcast都完成,然后在root进程中查看table数据是否都发送过去了

其中line20的代码技巧也值得学习,每个进程只当一次root,而其余的时候全都是当配角,通信中的接收一方

示例5

组通信中的死锁。组通信的相同函数有可能是同步的,也有可能是异步的,这个受软硬件平台影响;为了构建可移植性好的代码,在设计组通信程序的时候,无论是异步还是同步的,都应该保证程序不出现死锁。下面用几个例子解释死锁出现的情况。

代码1. 同一个通信域中的死锁示例

 #include "mpi.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> #define LEN 10 int main(int argc, char *argv[])
{
int rank,size; MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size); if(!=size) MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
int* buf1 = (int*)malloc(sizeof(int)*LEN);
int* buf2 = (int*)malloc(sizeof(int)*LEN);
buf1[] = ;
buf2[] = ;
if (==rank) {
MPI_Bcast(buf1,LEN,MPI_INT,,MPI_COMM_WORLD);
MPI_Bcast(buf2,LEN,MPI_INT,,MPI_COMM_WORLD);
printf("0 done\n");
}
if (==rank) {
MPI_Bcast(buf2,LEN,MPI_INT,,MPI_COMM_WORLD);
MPI_Bcast(buf1,LEN,MPI_INT,,MPI_COMM_WORLD);
printf("1 done\n");
}
free(buf1);
free(buf2);
MPI_Finalize();
}

代码执行结果如下:

可以看到并没有出现deadlock。原因是由于LEN定义的发送和接收数据长度太小,MPI给Bcast执行方式选择的是缓存非同步方式,即Bcast不必等到所有需要参加组通信的进程都完成了再返回。

如果我们把LEN定义的长度改为100000,则执行结果如下:

执行结果表明deadlock出现了。原因是由于LEN定义的发送和接受的数据长度较大,MPI给Bcast选择的执行方式是同步方式,即Bcast必须等到所有需要参加广播通信的进程都完成了,才能够正确返回。

书上并没有提Bcast是异步还是同步的事情,我在stackoverflow上提问才知道了上面的道理(http://stackoverflow.com/questions/35628524/why-this-mpi-bcast-related-code-not-deadlock)。感谢stackoverflow上的认真回答。

代码2. 不同通信域中的死锁示例

这部分内容与后面第15章的进程组和通信域知识相关,需要调换顺序提前补充。

总共有3个进程,三个进程在MPI_COMM_WORLD中的rank分别是{0,1,2}

现在构造三个新的通信域comm0 comm1 comm2

comm0中包含MPI_COMM_WORLD中rank为0,1的进程

comm1中包含MPI_COMM_WORLD中rank为1,2的进程

comm2中包含MPI_COMM_WORLD中rank为2,0的进程

需要注意是,虽然进程客观上只有3个,但是相同的进程在不同的通信域中的rank号是不同的。

下面的代码构造了一个循环依赖的deadlock

 #include "mpi.h"
#include <stdio.h>
#include <stdlib.h> #define LEN 100000 int main(int argc, char *argv[])
{
MPI_Init(&argc, &argv);
int rank, size;
MPI_Comm_size(MPI_COMM_WORLD, &size);
if (!=size) {
printf("wrong proc num\n");
MPI_Abort(MPI_COMM_WORLD, EXIT_FAILURE);
}
const int ranks[] = {,,};
MPI_Group world_group;
MPI_Comm_group(MPI_COMM_WORLD, &world_group);
// 构造三个新的进程组对象
MPI_Group group0, group1, group2;
const int ranks0[] = {,};
const int ranks1[] = {,};
const int ranks2[] = {,};
MPI_Group_incl(world_group, , ranks0, &group0);
MPI_Group_incl(world_group, , ranks1, &group1);
MPI_Group_incl(world_group, , ranks2, &group2); // 利用三个进程对象构造通信域
MPI_Comm comm0, comm1, comm2;
MPI_Comm_create(MPI_COMM_WORLD, group0, &comm0);
MPI_Comm_create(MPI_COMM_WORLD, group1, &comm1);
MPI_Comm_create(MPI_COMM_WORLD, group2, &comm2);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
int *buf1 = (int*)malloc(sizeof(int)*LEN);
int *buf2 = (int*)malloc(sizeof(int)*LEN);
buf1[] = ;
buf2[] = ;
int sub_rank;
if (==rank) {
MPI_Group_translate_ranks(world_group,,&ranks[],group0,&sub_rank);
MPI_Bcast(buf1, LEN, MPI_INT, sub_rank, comm0);
MPI_Group_translate_ranks(world_group,,&ranks[],group2,&sub_rank);
MPI_Bcast(buf2, LEN, MPI_INT, sub_rank, comm2);
}
if (==rank) {
MPI_Group_translate_ranks(world_group,,&ranks[],group1,&sub_rank);
MPI_Bcast(buf1, LEN, MPI_INT, sub_rank, comm1);
MPI_Group_translate_ranks(world_group,,&ranks[],group0,&sub_rank);
MPI_Bcast(buf2, LEN, MPI_INT, sub_rank, comm0);
}
if (==rank) {
MPI_Group_translate_ranks(world_group,,&ranks[],group2,&sub_rank);
MPI_Bcast(buf1, LEN, MPI_INT, sub_rank, comm2);
MPI_Group_translate_ranks(world_group,,&ranks[],group1,&sub_rank);
MPI_Bcast(buf2, LEN, MPI_INT, sub_rank, comm1);
}
MPI_Finalize();
}

代码执行结果是deadlock,这个deadlock本身不难理解,就是comm0依赖comm1,comm1依赖comm2,comm2依赖comm0,循环依赖造成了死锁,互相都等着。

书上只给出来了关键代码的原型,并没有给出来通信域是怎么构造的,Bcast中的sub_rank是怎么得来的,需要补充一些进程组和通信域的知识。

进程组和通信域使得MPI程序与传统的串行程序的设计思路上有很大不同,初学甚至有些别扭,我也没有全部领会。

额外参考了https://www.rc.usf.edu/tutorials/classes/tutorial/mpi/chapter9.html

只能先把与这个例子相关的知识记录下来:

a. 通信域(Communicator)是大的概念,进程组是通信域的一个属性

b. 一个通信域对应一个进程组

c. 一个进程可以在多个进程组中,因此也可以在多个通信域中

d. 通过进程组来构造通信域是通信域的一种生成方式

e. 相同的进程在不同的通信域中的rank是不同的,可以通过函数来查阅这种对应关系

f. 比如,调用MPI_Comm_create函数,利用进程组{0,1}构造一个通信域comm0;那么进程2也会执行这条语句,则进程2中对应的comm0就是MPI_COMM_NULL

关于上面提到的f,我用http://mpitutorial.com/tutorials/introduction-to-groups-and-communicators/里面的一个代码示例进行了验证,加深理解:

 #include "mpi.h"
#include <stdio.h>
#include <stdlib.h> int main(int argc, char *argv[])
{
MPI_Init(&argc, &argv);
int world_rank, world_size;
MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
MPI_Comm_rank(MPI_COMM_WORLD, &world_size); MPI_Group world_group;
MPI_Comm_group(MPI_COMM_WORLD, &world_group); int n = ;
const int ranks[] = {,,,,,,}; MPI_Group prime_group;
// 构造新的进程组 这个进程组是客观的 不管当前进程是哪个
MPI_Group_incl(world_group, , ranks, &prime_group); MPI_Comm prime_comm;
// 如果当前进程不在prime_group中 则prime_com就是MPI_COMM_NULL
MPI_Comm_create_group(MPI_COMM_WORLD, prime_group, , &prime_comm); int prime_rank = -, prime_size = -;
if (MPI_COMM_NULL!=prime_comm) {
MPI_Comm_rank(prime_comm, &prime_rank);
MPI_Comm_size(prime_comm, &prime_size);
} printf("WORLD RANK/SIZE: %d/%d \t PRIME RANK/SIZE: %d/%d\n",world_rank, world_size, prime_rank, prime_size);
if(MPI_COMM_NULL!=prime_comm) MPI_Barrier(prime_comm);
MPI_Barrier(MPI_COMM_WORLD);
if(MPI_GROUP_NULL!=world_group) MPI_Group_free(&world_group);
if(MPI_GROUP_NULL!=prime_group) MPI_Group_free(&prime_group);
if(MPI_COMM_NULL!=prime_comm) MPI_Comm_free(&prime_comm);
MPI_Finalize();
}

代码执行结果如下:

从原来进程中扣出来几个进程构造新进程;对于没被扣出来的那些进程,执行到MPI_Comm_create这里的时候,prime_comm就是MPI_COMM_NULL。

示例6

归约操作。这部分代码都是书上的内容,认真看书即可。

代码1 MINLOC和MAXLOC

有时候不仅需要知道一堆数据中的最小(最大)值,而且需要知道最小最大值对应的编号(或进程号)是多少。

通过MPI提供的MPI_REDUCE归约函数,以及预定义的归约操作MPI_MINLOC活MPI_MAXLOC即可方便实现。

 #include "mpi.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h> int main(int argc, char *argv[])
{
MPI_Init(&argc, &argv);
int i, myrank, root;
root = ;
double ain[]; // 各个进程的输入数据
double aout[]; // 只有root进程有用 用于存放规约后的数字的具体值
int ind[]; // 只有root进程有用 用于存放规约后的数字的编号
struct{
double val;
int rank;
} in[], out[]; // 规约操作的发送缓冲区和接收缓冲区
MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
srand((unsigned int)(myrank+time(NULL)));
for(i=;i<;i++) ain[i] = (1.0*rand())/RAND_MAX;
for(i=; i<; i++){
in[i].val = ain[i];
in[i].rank = myrank;
}
printf("myrank:%d %.3f,%.3f,%.3f\n",myrank, ain[],ain[],ain[]);
MPI_Reduce(in, out, , MPI_DOUBLE_INT, MPI_MAXLOC, root, MPI_COMM_WORLD);
if (myrank==root) {
printf("reduce result:\n");
for(i=; i<; i++){
aout[i] = out[i].val;
ind[i] = out[i].rank;
printf("%.3f of rank %d\n", aout[i], ind[i]);
}
}
MPI_Finalize();
}

代码执行结果如下:

可以看到这种归约操作:

a. 要求每个进程中的输入数据的大小是一样的(在这个例子中都是长度为3的double数组),

b. 归约的对象是不同进程的数组中相同位置的数据

另外,也允许用户自定义归约操作,如下面代码定义了复数相乘的归约操作(书上只给了代码框架,具体细节中的问题参考http://stackoverflow.com/questions/9285442/mpi-get-processor-with-minimum-value)。

 #include "mpi.h"
#include <stdio.h>
#include <stdlib.h>
#include <time.h> typedef struct{
double real;
double imag;
}Complex; void multiplyOP(Complex *, Complex *, int *, MPI_Datatype *); int main(int argc, char *argv[])
{
MPI_Init(&argc, &argv);
int root = ;
int rank;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
Complex input[], output[];
if (==rank) {
input[].real = ; input[].imag = ;
input[].real = ; input[].imag = ;
}
if (==rank) {
input[].real = ; input[].imag = -;
input[].real = ; input[].imag = ;
}
// 在MPI中注册和构造复数类型
MPI_Datatype ctype;
MPI_Type_contiguous(, MPI_DOUBLE, &ctype); // 在MPI中构造复数结构体 连续的两个DOUBLE类型
MPI_Type_commit(&ctype); // 在MPI中注册刚构造的复数结构体
// 在MPI中构造自定义归约操作
MPI_Op myOp;
MPI_Op_create((MPI_User_function*)multiplyOP, , &myOp); // 生成用户定义的乘积操作
MPI_Reduce(input, output, , ctype, myOp, root, MPI_COMM_WORLD); // 执行复数乘积操作
// 在root中打印结果
if (root==rank) {
printf("reduce result of 0 is : %1.0f+(%1.0f)i\n",output[].real, output[].imag);
printf("reduce result of 1 is : %1.0f+(%1.0f)i\n",output[].real, output[].imag);
}
MPI_Finalize();
} // 计算复数的乘积
void multiplyOP(Complex *in, Complex *inout, int *len, MPI_Datatype *datatype)
{
int i;
Complex c;
for(i=; i<*len; i++){
c.real = inout->real*in->real - inout->imag*in->imag;
c.imag = inout->real*in->imag + inout->imag*in->real;
*inout = c; // 把计算结果存回到inout的位置
in++;
inout++;
}
}

代码的执行结果如下:

【MPI学习5】MPI并行程序设计模式:组通信MPI程序设计的更多相关文章

  1. 【MPI学习6】MPI并行程序设计模式:具有不连续数据发送的MPI程序设计

    基于都志辉老师<MPI并行程序设计模式>第14章内容. 前面接触到的MPI发送的数据类型都是连续型的数据.非连续类型的数据,MPI也可以发送,但是需要预先处理,大概有两类方法: (1)用户 ...

  2. Java进阶7 并发优化2 并行程序设计模式

    Java进阶7 并发优化2 并行程序设计模式20131114 1.Master-worker模式 前面讲解了Future模式,并且使用了简单的FutureTask来实现并发中的Future模式.下面介 ...

  3. 【MPI学习2】MPI并行程序设计模式:对等模式 & 主从模式

    这里的内容主要是都志辉老师<高性能计算之并行编程技术——MPI并行程序设计> 书上有一些代码是FORTAN的,我在学习的过程中,将其都转换成C的代码,便于统一记录. 这章内容分为两个部分: ...

  4. 【MPI学习4】MPI并行程序设计模式:非阻塞通信MPI程序设计

    这一章讲了MPI非阻塞通信的原理和一些函数接口,最后再用非阻塞通信方式实现Jacobi迭代,记录学习中的一些知识. (1)阻塞通信与非阻塞通信 阻塞通信调用时,整个程序只能执行通信相关的内容,而无法执 ...

  5. 【MPI学习7】MPI并行程序设计模式:MPI的进程组和通信域

    基于都志辉老师MPI编程书中的第15章内容. 通信域是MPI的重要概念:MPI的通信在通信域的控制和维护下进行 → 所有MPI通信任务都直接或间接用到通信域这一参数 → 对通信域的重组和划分可以方便实 ...

  6. 【MPI学习3】MPI并行程序设计模式:不同通信模式MPI并行程序的设计

    学习了MPI四种通信模式 及其函数用法: (1)标准通信模式:MPI_SEND (2)缓存通信模式:MPI_BSEND (3)同步通信模式:MPI_SSEND (4)就绪通信模式:MPI_RSEND ...

  7. Java并行程序设计模式小结

    这里总结几种常用的并行程序设计方法,其中部分文字源自<Java程序性能优化>一书中,还有部分文字属于个人总结,如有不对,请大家指出讨论. Future模式 一句话,将客户端请求的处理过程从 ...

  8. 并行程序设计模式--Master-Worker模式

    简介 Master-Worker模式是常用的并行设计模式.它的核心思想是,系统有两个进程协议工作:Master进程和Worker进程.Master进程负责接收和分配任务,Worker进程负责处理子任务 ...

  9. 转 Master-Worker模式 并行程序设计模式--Master-Worker模式

    简介 Master-Worker模式是常用的并行设计模式.它的核心思想是,系统有两个进程协议工作:Master进程和Worker进程.Master进程负责接收和分配任务,Worker进程负责处理子任务 ...

随机推荐

  1. 关于nginx的限速模块

    nginx 使用 ngx_http_limit_req_module和ngx_http_limit_conn_module 来限制对资源的请求 这种方法,对于CC攻击(Challenge Collap ...

  2. c#,关于Big Endian 和 Little Endian,以及转换类

    Big Endian:最高字节在地址最低位,最低字节在地址最高位,依次排列. Little Endian:最低字节在最低位,最高字节在最高位,反序排列. 当在本地主机上,无需注意机器用的是Big En ...

  3. PHP添加Redis模块及连接

    上几篇文章介绍了Redis的安装及使用,下面将介绍php如何添加Redis扩展! php手册并没有提供Redis的类和方法,也没有提供相关的扩展模块,但我们可以在Redis的官网下载PHP的扩展,里面 ...

  4. 避免jQuery名字冲突--noConflict()方法

    众所周知,在jQuery语法中,$符号是jQuery的简写方式.但在某些情况下,可能需要在同一个页面引入其他javascript库(比如Prototype).因为$简短方便,很多的库也是使用$符号.为 ...

  5. Linux下FTP服务(一)—— Ubuntu安装

    参考:http://www.cnblogs.com/likwo/p/3154868.html 实验环境:Ubuntu 14.04 VMware虚拟机1. 安装 apt-get install vsft ...

  6. Kali Linux 网络扫描秘籍 翻译完成!

    Kali Linux 网络扫描秘籍 翻译完成! 原书:Kali Linux Network Scanning Cookbook 译者:飞龙 在线阅读 PDF格式 EPUB格式 MOBI格式 代码仓库 ...

  7. 虚拟机Linux----Ubuntu1204----安装jdk1.8

    1.介绍 这里主要讲一下,如何在Ubuntu1204下通过压缩包的方式安装jdk1.8,rpm的直接运行就行了. 2.步骤 2.1 下载 地址:http://www.oracle.com/techne ...

  8. 分享一个linux环境下快速读取行数的命令

    最初是因为我需要计算一天的日志行数,如果用传统意义上的cat  a.log |wc -l的话因为是单线程,所以需要计算半小时的样子,后来同组的小伙伴教了我一个方法可以有效提高计算速度,将计算时间减半. ...

  9. socket通信入门

    以一个基本的python程序为例解释 源代码如下: #!/usr/bin/env python  #指出代码用什么程序去运行它.首先会到env设置里查找python的安装路径,再调用对应路径下的解释器 ...

  10. hdu 5862 Counting Intersections

    传送门:hdu 5862 Counting Intersections 题意:对于平行于坐标轴的n条线段,求两两相交的线段对有多少个,包括十,T型 官方题解:由于数据限制,只有竖向与横向的线段才会产生 ...