MPI编程指南
MPI编程指南
一、 MPI概述
1.1 MPI的发展史
MPI标准化涉及到大约60个国家的人们,他们主要来自于美国和欧洲的40个组织,这包括并行计算机的多数主要生产商,还有来自大学、政府实验室和工厂的研究者们。1992年4月,并行计算研究中心在Williamsburg,Virginia,召开了一个关于消息传递的标准的工作会议,会议上讨论了标准消息传递的必要的、基本的特点,并建立了工作组继续进行标准化工作。
1992年10月,MPI的初步草稿MPI1形成,MPI1主要包含的是在Williamsburg工作组会议上讨论的基本消息传递的接口,因为它的基本目的就是促进讨论并继续此项工作,所以它主要集中在点对点的通信。1993年1月,第一届MPI会议在Dallas举行,1993年2月MPI1修定版本公布;之后定期召开了一系列关于MPI的核心的研讨会;1993年11月MPI的草稿和概述分别发表于Supercomputing'93和the proceedings。直到1994年5月,MPI标准正式发布。1994年7月发布了MPI标准的勘误表。
现在该工作组正在着手扩展MPI、完善MPI,从事于MPI2(MPI的扩展标准)的制定工作。
1.2 MPI的优点
●可移植性和易于使用。以低级消息传递程序为基础的较高级和(或)抽象程序所构成的分布存储通信环境中,标准化的效益特别明显。
●MPI是被正式的详细说明的:它已经成为一个标准。消息传递标准的定义能提供给生产商清晰定义的程序库,以便他们能有效地实现这些库或在某些情况下为库程序提供硬件支持,因此加强了可扩展性。
●MPI有完备的异步通信:使得send,recieve能与计算重叠。
●可以很有效的在MPP上或Cluster上用MPI编程:MPI的虚拟拓扑反映了应用程序所申请的一组结点的通信模式,在MPP上实现的MPI便可以利用这种信息来优化处理器间的通信路径。
1.3 主要内容
●点对点通信(point_to_point communication)
●群体操作(collective operations)
●进程组(process groups)
●通信上下文(communication contexts)
●进程拓扑结构(process topologies)
●与Fortran 77和C语言的邦定(bindings for Fortran 77 and C)
●环境的管理与查询(environmental management and inquiry)
●轮廓管理(profiling interface)
1.4 MPI的各种实现
在国外有许多自从MPI标准制定以来在各种机器上的MPI的portable的实现,并且他们仍从事于自己的MPI实现的性能改进,其中最著名的一些见表1。
表1 国外MPI的各种实现
名称 |
单 位 |
网 址 |
CHIMP |
U. of Edinburgh |
|
LAM |
Ohio state |
tbag.osc.edu/pub/lam/ |
MPICH |
Argonne_Mississippi state |
|
Unify |
Mississippi state |
二、 MPI入门介绍
以下均以MPICH为例。
2.1 MPI的编程方式
对于基本的应用,MPI同其它消息传送系统一样易于使用。下面是一个简单的基于C语言的MPI样本代码。它是SPMD方式的,即每个进程都执行该程序,通过返回的进程编号来区分不同的进程。该程序完成进程0向进程1传递数据buf。
/* first.c */
#include "mpi.h" /*MPI的头函数,提供基本的MPI定义和类型*/
#include <stdio.h>
int main( argc, argv )
int argc;
char **argv;
{
int rank, size, tag=333;
int buf[20]
MPI_Status status
MPI_Init( &argc, &argv ); /*MPI的初始化函数*/
MPI_Comm_rank( MPI_COMM_WORLD, &rank ); /*该进程的编号*/
MPI_Comm_size( MPI_COMM_WORLD, &size ); /*总的进程数目*/
if (rank==0)
MPI_Send( buf, 20, MPI_Int, 1, tag, MPI_COMM_WORLD); /*发送buf到进程1*/
if (rank==0)
MPI_Recv( buf, 20, MPI_Int, 0, tag, MPI_COMM_WORLD, &status); /*从进程0接收buf*/
MPI_Finalize(); /*MPI的结束函数*/
return 0;
}
2.2 MPI的基本语句介绍
1、MPI世界
进程由一个唯一的“标识数”(整数)表示,进程的标识数为数0、1、2、……、N-1。MPI_COMM_WORLD指“MPI应用中的所有进程”,它称为通信子,并且提供消息传递所需的所有信息。可移植库对通信子做更多的工作,以提供大多数其它系统所不能处理的同步保护。
2、进入和退出MPI
与其它系统一起,提供两个函数来初始化和结束MPI进程:
int MPI_Init(int *argc, char ***argv) /*初始化*/
int MPI_Finalize(void) /*结束*/
3、我是谁?他们是谁?
典型地,并行程序中的进程需要知道它是谁(它的标识数)以及其它进程是怎样存在的。一个进程通过调用MPI_Comm_rank( )来发现它自己的标识数,用MPI_Comm_size( )来返回进程总数:
int MPI_Group_size(MPI_Group group, int *size)
int MPI_Group_rank(MPI_Group group, int *rank)
4、发送消息
消息是一组给定数据类型的元素。消息被发给一个指定的进程,而且用一个由用户说明的标识来标记。标识用来区分不同的由一个进程所能发送/接受的消息类型。
int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
5、接受消息
接受进程说明标识和发送进程的标识数。MPI_ANY_TAG和MPI_ANY_SOURCE可选地用于接受任意标识和从任意发送进程而来的消息。
int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)
有关所接受消息的信息在一个状态变量status中返回。
通过这些函数,你会准备编写任意的应用。在MPI中有许多其它、更特异的函数,但是迄今为止所有这些函数可以在这里提到的函数上构造。
2.3 MPI的环境设置
在并行机上的环境一般是设置好的,不需要变动。而在NOW上运行MPI程序需要设置一些配置文件:
1、 由于加载程序到结点上运行调用了Unix系统的rsh命令,所以需要在每个结点上设置 .rhosts文件,以使rsh能正确执行;
2、 由于NOW环境的异构性,需要在启动时指定运行结点的体系结构;若未指定,是指使用与启动并行程序的结点具有相同体系结构的结点。在启动并行程序的机器里,具有相同体系结构的几台机器的名字存放在一个名为$MPICH/util/machines/machines.<arch>的文件中,一台机器的名字占有文件的一行,其中$MPICH是一个环境变量,指明MPICH软件安装后所在的目录。并行程序加载运行时是按照文件中机器名字的先后顺序依次加载的。
2.4 MPI的编译和运行
对于简单的程序,可以使用专门的编译命令。对于大的项目,最好使用标准的Makefile。MPICH提供的编译命令有mpicc和mpif77,它们分别是C和Fortran的编译命令:
mpicc -o first first.c
mpif77 -o first firstf.f
对于编译得到的目标程序,运行的命令为:
mpirun –arch xxx –np yyy first
其中xxx为machines.<arch>的<arch>,yyy为申请的进程数目。
2.5 例子
1、 环的C语言实现
#include "mpi.h"
#include <stdio.h>
#define T_SIZE 2000
int main(argc,argv)
int argc;
char *argv[];
{
int ierr, prev, next, tag, rank, size;
MPI_Status status;
double send_buf[T_SIZE], recv_buf[T_SIZE];
MPI_Init(&argc,&argv);
MPI_Comm_rank( MPI_COMM_WORLD, &rank);
MPI_Comm_size( MPI_COMM_WORLD, &size);
next = rank + 1;
if (next > size) next = 0;
prev = rank – 1;
if (prev < 0) prev = size – 1;
if (rank == 0) {
MPI_Send(send_buf, T_SIZE, MPI_DOUBLE, next, tag, MPI_COMM_WORLD);
MPI_Recv(recv_buf, T_SIZE, MPI_DOUBLE, prev, tag+1, MPI_COMM_WORLD, status);
} else {
MPI_Recv(recv_buf, T_SIZE, MPI_DOUBLE, prev, tag, MPI_COMM_WORLD, status);
MPI_Send(recv_buf, T_SIZE, MPI_DOUBLE, next, tag+1, MPI_COMM_WORLD);
}
MPI_Finalize();
}
2、 环的Fortran语言实现
program ring
include 'mpif.h'
integer ierr
call MPI_INIT(ierr)
call test_ring
call MPI_FINALIZE(ierr)
end
subroutine test_ring
include 'mpif.h'
integer T_SIZE
parameter (T_SIZE=2000)
integer ierr, prev, next, tag, rank, size, status(MPI_STATUS_SIZE)
real send_buf( T_SIZE ), recv_buf ( T_SIZE )
call MPI_COMM_RANK( MPI_COMM_WORLD, rank, ierr )
call MPI_COMM_SIZE( MPI_COMM_WORLD, size, ierr )
next = rank + 1
if (next .ge. size) next = 0
prev = rank - 1
if (prev .lt. 0) prev = size - 1
if (rank .eq. 0) then
call MPI_SEND(send_buf, T_SIZE, MPI_REAL, next, tag, MPI_COMM_WORLD, ierr)
call MPI_RECV(recv_buf, T_SIZE, MPI_REAL, prev, tag+1, MPI_COMM_WORLD, status, ierr)
else
call MPI_RECV(recv_buf, T_SIZE, MPI_REAL, prev, tag, MPI_COMM_WORLD, status, ierr)
call MPI_SEND(recv_buf, T_SIZE, MPI_REAL, next, tag+1, MPI_COMM_WORLD, ierr)
end if
3、 PI的C语言实现
#include "mpi.h"
#include <stdio.h>
#include <math.h>
double f(a)
double a;
{
return (4.0 / (1.0 + a*a));
}
int main(argc,argv)
int argc;
char *argv[];
{
int done = 0, n=100, myid, numprocs, i;
double PI25DT = 3.141592653589793238462643;
double mypi, pi, h, sum, x, a,startwtime, endwtime;
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&numprocs);
MPI_Comm_rank(MPI_COMM_WORLD,&myid);
if (myid == 0) startwtime = MPI_Wtime();
h = 1.0 / (double) n;
sum = 0.0;
for (i = myid + 1; i <= n; i += numprocs)
{ x = h * ((double)i - 0.5);
sum += f(x); }
mypi = h * sum;
MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
if (myid == 0) {
printf("pi is %.16f, Error is %.16f\n", pi, fabs(pi - PI25DT));
endwtime = MPI_Wtime();
printf("wall clock time = %f\n", endwtime-startwtime);
}
MPI_Finalize();
}
4、 计算PI的Fortran语言实现
program main
include 'mpif.h'
double precision PI25DT
parameter (PI25DT = 3.141592653589793238462643d0)
double precision mypi, pi, h, sum, x, f, a
integer n, myid, numprocs, i, rc
f(a) = 4.d0 / (1.d0 + a*a)
call MPI_INIT( ierr )
call MPI_COMM_RANK( MPI_COMM_WORLD, myid, ierr )
call MPI_COMM_SIZE( MPI_COMM_WORLD, numprocs, ierr )
n=100
h = 1.0d0/n
sum = 0.0d0
do 20 i = myid+1, n, numprocs
x = h * (double(i) - 0.5d0)
sum = sum + f(x)
20 continue
mypi = h * sum
call MPI_REDUCE(mypi,pi,1,MPI_DOUBLE_PRECISION,MPI_SUM,0, MPI_COMM_WORLD, ierr)
if (myid .eq. 0) then write(6, 97) pi, abs(pi - PI25DT)
97 format(' pi is : ', F18.16, ' Error is: ', F18.16)
endif
30 call MPI_FINALIZE(ierr)
end
三、 MPI中级介绍
3.1 基本概念
- 消息包
在消息传递过程中,发送和接收的消息除了数据部分外, 消息还带有用于识别消息和选择接收消息的信息。这个信息是由确定的域组成的,我们称为信封,这些域有:source(源)、destination(目的)、tag(标识)、communicator(通信子)。
消息的source隐含地由消息发送者的标识来决定。其他域是由发送操作的参数决定。整型值的消息tag能被程序用于识别不同的消息,有效标识值的范围依赖于实现。
- 组和通信子
一个组(group)是进程的一个有序集。组内的每个进程与一个整数rank相联系。序列号是连续的并从0开始。组用模糊的组对象来描述,因此不能直接从一个进程到另一个进程传送。可在一个通信子中使用组来描述通信空间中的参与者并对这些参与者进行分级(rank)。
上下文是通信子所具有的一个特性,它允许对通信空间进行划分。一个上下文所发送的消息不能被另一个上下文所接收。
一个通信子(communicator)指定一个通信操作的上下文。每个通信上下文提供一个单独的“通信全域”;消息总是在一个的上下文内被发送和接收,不同的上下文发送的消息互不干涉。通信子也指定共享这个通信上下文的进程组。这个进程组被编号,并且进程是由这个组中的进程号标识。
一个预定义的通信子MPI_COMM_WORLD是由MPI提供。MPI初始化后, 它允许和可存取的全部进程通信, 进程是由他们在MPI_COMM_WORLD组中的进程号所标识。
- 基本数据类型
异构计算要求包含消息的数据是有类型的或者已描述的,使得它的机器的表达可以在计算机体系结构之间转换。MPI系统地描述了消息的数据类型,从简单的原始机器类型到复杂的结构、数组和下标。
与C语言邦定的基本数据类型:
MPI_CHAR |
Signed char |
MPI_SHORT |
Signed short int |
MPI_INT |
Signed int |
MPI_LONG |
Signed long int |
MPI_UNSIGNED_CHAR |
Unsigned char |
MPI_UNSIGNED_SHORT |
Unsigned short int |
MPI_UNSIGNED |
Unsigned int |
MPI_UNSIGNED_LONG |
Unsigned long int |
MPI_FLOAT |
Float |
MPI_DOUBLE |
Double |
MPI_LONG_DOUBLE |
Long double |
MPI_BYTE |
|
MPI_PACKED |
与Fortran语言邦定的基本数据类型:
MPI_INTEGER |
INTEGER |
MPI_REAL |
REAL |
MPI_DOUBLE_PRECISION |
DOUBLE PRECISION |
MPI_COMPLEX |
COMPLEX |
MPI_DOUBLE_COMPLEX |
DOUBLE COMPLEX |
MPI_LOGICAL |
LOGICAL |
MPI_CHARACTER |
CHARCTER(1) |
MPI_BYTE |
|
MPI_PACKED |
3.2 点对点通信
点对点通信是MPI中比较复杂的一部分,它有两种消息传递的机制:阻塞的和非阻塞的。对于阻塞的方式,它必须等到消息从本地送出之后,才可以执行后续的语句,保证了消息缓冲区等资源的可再用性;而非阻塞的方式不须等到消息从本地送出,就可以执行后续的语句,从而允许通信和计算的重叠,利用合适的硬件可使得计算和数据通信同时执行,但是非阻塞调用的返回并不保证资源的可再用性。
点对点通讯的发送和接收语句必须是匹配的,为了区分不同进程或同一进程发送来的不同消息,可以采用通讯体和标志位(tag)来实现相应的语句的匹配,也可以接收不确定source和tag的消息,例如MPI_ANY_SOURCE, MPI_ANY_TAG。
1、 阻塞通信
对于阻塞的标准通信,MPI_Send和MPI_Recv函数的格式为
int MPI_Send(void* buf, int count, MPI_Datatype datatype, int dest, int tag,
MPI_Comm comm)
int MPI_Recv(void* buf, int count, MPI_Datatype datatype, int source, int tag,
MPI_Comm comm, MPI_Status *status)
发送的含义是将包含count个datatype类型的首地址为buf的消息发送到dest进程,该消息的是与标志tag和通信体comm封装在一块的;接收的含义是将source进程接收标志为tag和通信体为comm的消息,将该消息写入首地址为buf 的缓冲区,返回值status中包含大小、标志、源进程等信息。
在C语言中, status是一个结构, 其包含两个域, 名字为 MPI_SOURCE和MPI_TAG, 该结构可以包含附加域。在Fortran中,status是一个MPI_STATUS_SIZE大小的INTEGER型数组,status(MPI_SOURCE)和status(MPI_TAG)分别包含被接收消息的源和标识。
2、 非阻塞通信
对于非阻塞的标准通信,MPI_Isend和MPI_Irecv函数的格式为
int MPI_Isend(void* buf, int count, MPI_Datatype datatype, int dest, int tag,
MPI_Comm comm, MPI_Request *request)
int MPI_Irecv(void* buf, int count, MPI_Datatype datatype, int source, int tag,
MPI_Comm comm, MPI_Request *request)
对于非阻塞通信,可以用MPI_WAIT,MPI_TEST等来结束非阻塞通信,
int MPI_Wait(MPI_Request *request, MPI_Status *status)
int MPI_Test(MPI_Request *request,int *flag, MPI_Status *status)
其中MPI_Test用来检测非阻塞操作提交的任务是否结束并立即返回,MPI_Wait则一直要等到非阻塞操作提交的任务结束才返回。我们可以认为:
阻塞通信=非阻塞通信 + MPI_Wait;
MPI_Wait = while (flag==0) MPI_Test。
3、 消息的检测
MPI_Probe和MPI_Iprobe操作允许在没有实际收到输入消息的情况下对其进行检查。用户于是可以根据这次返回的信息决定如何接收他们(该信息一般用status返回),或可以根据被检查消息的长度分配结束缓冲区大小,或进行条件分支等。
他们的格式如下:
int MPI_Probe(int source, int tag, MPI_Comm comm, MPI_Status *status)
int MPI_Iprobe(int source, int tag, MPI_Comm comm, int *flag,
MPI_Status *status)
如果MPI_Iprobe返回flag=true,则状态目标(status)才能够被获取source,tag及消息的长度。而MPI_Probe是一个阻塞的语句,它必须等到相匹配的消息(source,tag)到达才完成。
4、点对点通信的例子
#include <stdio.h>
#include "mpi.h"
int main(argc, argv)
int argc;
char** argv;
{
int locId,data[100], tag=8888;
MPI_Status status;
MPI_Init(&argc, &argv) ;
MPI_Comm_rank(MPI_COMM_WORLD, &locId) ;
if(locId == 0) {
MPI_Request events;
MPI_Isend(data, 100, MPI_INT, 1, tag , MPI_COMM_WORLD, &events) ;
MPI_Wait(&events, &status) ;
}
if(locId == 1) {
MPI_Probe(MPI_ANY_SOURCE, tag, MPI_COMM_WORLD, &status);
if (status.MPI_SOURCE==0)
MPI_Recv(data, 100, MPI_INT, 0, tag, MPI_COMM_WORLD, &status) ;
}
MPI_Finalize() ;
}
3.3 群体通信
群体通信意味着一个通信子中的所有进程调用同一例程,所有的群体操作都是阻塞的,它包括如下一些:
●同步(barrier)
●从一个进程到本组内的所有进程的播送(broadcast)(如图 3.3 的(a))
●从本组所有处理收集数据到一个进程(gather)(如图 3.3 的(b))
●从一个进程分散数据到本组内的所有进程(sactter)(如图 3.3 的(b))
●将gather的数据不是送到某一进程,而是要送到所有本组内的进程(allgather) (如图 3.3 的(c))
●组内的多对多的分散/收集(alltoall)(如图 3.3 的(d))
●求和,最大值,最小值及用户定义的函数等的reduce操作
●scan或prefix操作
1、同步
int MPI_Barrier(MPI_Comm comm)
它使得调用者阻塞,直到该通信子内所有进程都调用它。
2、广播
int MPI_Bcast(void *buffer, int count, MPI_Datatype datatype, int root,
MPI_Comm comm);
所有进程使用同一计数、数据类型、根和通信子。在操作前,根缓冲区包含一个消息。操作后,所有缓冲区包含来自根进程的消息。
3、散播
int MPI_Scatter(void *sndbuf, int sndcnt, MPI_Datatype sndtype, void *rcvbuf,
int rcvcnt, MPI_Datatype rcvtype, int root, MPI_Comm comm);
所有进程使用同一计数、数据类型、根和通信子。在操作前,根发送缓冲区包含长度为`sndcnt * N'的消息,这里N是进程数目。操作后,相等地划分消息,并且分散到随后标识数序的所有进程(包括根)。
4、归约
int MPI_Reduce(void *sndbuf, void *rcvbuf, int count, MPI_Datatype datatype,
MPI_Op op, int root, MPI_Comm comm);
所有进程使用同一计数、数据类型、根和通信子。操作后,根进程在它的接受缓冲区中有所有进程的发送缓冲区的归约结果,包括:MPI_MAX, MPI_MIN, MPI_SUM, MPI_PROD, MPI_LAND, MPI_BAND, MPI_LOR, MPI_BOR, MPI_LXOR, MPI_BXOR,或者是用户定义的归约函数。
5、收集
int MPI_Gather(void *sndbuf, int sndcnt, MPI_Datatype sndtype, void *rcvbuf,
int rcvcnt, MPI_Datatype rcvtype, int root, MPI_Comm comm);
所有进程使用同一计数、数据类型、根和通信子。此例程是MPI_Scatter()的相反:操作后,根进程在它的接受缓冲区中包含所有进程的发送缓冲区的连接(包括它自己),所有消息长度为`rcvcnt * N', 这里N是进程数目。按照随后的标识数序收集消息。
6、群体通信的例子
下面简单的代码段使用了四个基本的集合例程以操纵一个静态的已划分的规则区域(这里是一维)。全域的长度从根进程广播到所有其它进程。初始数据集在进程间分配(分散)。在每一计算步骤之后,确定全局的最大数并由根所使用。根然后收集最终的数据集。
#include <mpi.h>
{
int i,myrank,size,root,full_domain_length,sub_domain_length;
double global_max,local_max,*full_domain, *sub_domain;
MPI_Comm_rank(MPI_COMM_WORLD, &myrank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
root = 0;
if (myrank == root) get_full_domain(&full_domain, &full_domain_length);
MPI_Bcast(&full_domain_length, 1, MPI_INT, root, MPI_COMM_WORLD);
sub_domain_length = full_domain_length / size;
sub_domain = (double *) malloc(sub_domain_length * sizeof(double));
MPI_Scatter(full_domain, sub_domain_length, MPI_DOUBLE, sub_domain,
sub_domain_length, MPI_DOUBLE, root, MPI_COMM_WORLD);
compute(sub_domain, sub_domain_length, &local_max);
MPI_Reduce(&local_max, &global_max, 1, MPI_DOUBLE, MPI_MAX,
root, MPI_COMM_WORLD);
MPI_Gather(sub_domain, sub_domain_length, MPI_DOUBLE, full_domain,
sub_domain_length, MPI_DOUBLE, root, MPI_COMM_WORLD);
}
3.3 环境管理与查询
MPI的环境管理与查询包括:设置正确的参数、执行环境(出错句柄),以及进入MPI和退出MPI等。这些函数对于写正确的、健壮的程序很重要,特别是对于建筑更高层的可移植的消息传递的程序尤其重要。
1、 时钟
MPI定义了一个计时器,因为对并行程序计时在“性能调试”中是很重要的,而已存在的计时器要么是不方便的,要么是没能提供对高分辨率计时器的足够的访问。
double MPI_Wtime(void)
它返回当前的系统的墙壁时钟的时间,是一个浮点的秒数。
double MPI_Wtick()
MPI_Witck返回秒中MPI_WTIME的精度。即,它返回连续时钟滴答的秒数,是一双精度值。例如,如果由硬件实现的时钟作为每毫秒递增的计数器,那么由MPI_WTICK返回的值应为10e-3。
2、 MPI的初始与结束
int MPI_Init(int *argc,char ***argv)
int MPI_Finalize(void)
它是MPI的初始化函数及MPI的结束函数,所有的MPI程序必须含有MPI_Init,并且必须放在所有MPI的其它调用之前,MPI_Finalize清除所有的MPI的申明,用户必须确保所有的通信必须在调用MPI_Finalize之前完成。
MPI编程指南的更多相关文章
- OpenGL编程指南(第七版)
OpenGL编程指南(第七版) 转自:http://blog.csdn.net/w540982016044/article/details/21287645 在接触OpenGL中,配置显得相当麻烦,特 ...
- 编译opengl编程指南第八版示例代码通过
最近在编译opengl编程指南第八版的示例代码,如下 #include <iostream> #include "vgl.h" #include "LoadS ...
- 高质量C++/C编程指南(林锐)
推荐-高质量C++/C编程指南(林锐) 版本/状态 作者 参与者 起止日期 备注 V 0.9 草稿文件 林锐 2001-7-1至 2001-7-18 林锐起草 V 1.0 正式文件 林锐 20 ...
- iOS ---Extension编程指南
当iOS 8.0和OS X v10.10发布后,一个全新的概念出现在我们眼前,那就是应用扩展.顾名思义,应用扩展允许开发者扩展应用的自定义功能和内容,能够让用户在使用其他app时使用该项功能.你可以开 ...
- Lambda 表达式(C# 编程指南) 微软microsoft官方说明
Visual Studio 2013 其他版本 Lambda 表达式是一种可用于创建委托或表达式目录树类型的匿名函数. 通过使用 lambda 表达式,可以写入可作为参数传递或作为函数调用值返回的本地 ...
- KVC/KVO原理详解及编程指南
一.简介 1.KVC简介 2.KVO简介 二.KVC相关技术 1.Key和Key Path 2.点语法和KVC 3.一对多关系(To-Many)中的集合访问器方法 4.键值验证(Key-Value V ...
- iOS多线程编程指南(二)线程管理
当应用程序生成一个新的线程的时候,该线程变成应用程序进程空间内的一个实体.每个线程都拥有它自己的执行堆栈,由内核调度独立的运行时间片.一个线程可以和其他线程或其他进程通信,执行I/O操作,甚至执行任何 ...
- Core Animation编程指南
本文是<Core Animation Programming Guide>2013-01-28更新版本的译文.本文略去了原文中关于OS X平台上Core Animation相关内容.因为原 ...
- 【Todo】【读书笔记】Java多线程编程指南-设计模式篇
下了这本书<Java多线程编程指南-设计模式篇>, 还有另一本<JAVA多线程设计模式>,据说内容有重复,结合着看.
随机推荐
- 蓝桥杯 算法训练 ALGO-142 P1103
算法训练 P1103 时间限制:1.0s 内存限制:256.0MB 编程实现两个复数的运算.设有两个复数 和 ,则他们的运算公式为: 要求:(1)定义一个结构体类型来描述复数. (2)复数之间 ...
- 转载:trap 的用法 /etc/init.d/rcS trap :1 2 3 24
在有些情况下,我们不希望自己的shell脚本在运行时刻被中断,比如说我们写得shell脚 本设为某一用户的默认shell,使这一用户进入系统后只能作某一项工作,如数据库备份, 我 们可不希望用户使用c ...
- clone对象的克隆
用一句简单的话来说就是浅拷贝,只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,而且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不同地址的指针. 等多 http:/ ...
- HTML5通信
跨文档消息传输 HTML5中提供了在网页文档之间互相接收与发送信息的功能.使用这个功能只要获取到网页所在窗口对象的实例,无论是否同源都可以实现跨域通信.经常用于不同frame之间的通信. 当我们想要接 ...
- Oracle RMAN 学习:恢复
Oracle RMAN 学习:恢复 6 rman恢复 Rman中的恢复对应restore,recover Restore,数据修复,利用备份集的数据文件来替换已损坏的数据文件或将其恢复到另外一个位置, ...
- 深入VR之前 你应该知道VR头显透镜原理
转自:http://www.gamelook.com.cn/2016/03/246817 要理解虚拟现实头显透镜的工作原理,首先要搞懂眼睛是如何看到事物的. 眼睛瞳孔后有晶状体,也就是眼珠子.眼睛的背 ...
- 微信小程序基础语法总结
本文介绍微信小程序语法 配置文件 app.json的配置(全局) { // 用来配置页面的路径 "pages":[ "pages/index/index", / ...
- paramiko分开执行多条命令 不像之前一样使用\n
#!/usr/bin/env python#-*- encoding -*- import paramiko transport = paramiko.Transport(('192.168.11.1 ...
- xcode减小静态库的大小(转)
减小静态库的大小 编译生成的.a文件太大,但又没有冗余的文件可以删除已减少体积,找了很久才找到解决办法,如下: Build Settings-->Generate Debug Symbols 将 ...
- python爬虫(2)--Urllib库的高级用法
1.设置Headers 有些网站不会同意程序直接用上面的方式进行访问,如果识别有问题,那么站点根本不会响应,所以为了完全模拟浏览器的工作,我们需要设置一些Headers 的属性. 拆分这些请求,我们只 ...