钉钉,卸载后,手机不能显示微应用,点击公告,显示“你已退出该企业是否微信群里删除与退出该企业微应用消息”

系统信息提示
地区不存在!
该页面已搬迁,请到这儿瞧瞧:
对于上述信息提示您有任何建议,可以联系我们:
发布您的建议和意见。
您也可以发email给 。
门票线路客服:
网站社区客服:
旅行目的地:
旅行分享:
旅游超市:
游多多客栈:
Copyright (C) 2006-, All rights reserved.老灰菜网络杂记
Linux、FORTRAN、MPI、IDL/GDL、LaTeX等
Mon, 13 Jun 2011
[23:34] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] [发表评论]
如果您从事系统和网络管理工作,那么您将需要 Expect。
更准确地说,您为什么不使用 Expect 呢?对于一些常见的任务,它可以节省大量的时间。尽管您现在可能已经在使用 Expect,但是您可能并不是很清楚下面所描述的一些功能。
要从 Expect 中获益,您并不需要掌握有关它的所有内容;让我们从一个具体的示例开始,研究 Expect 如何在 AIX 或者其他操作系统中简化您的工作:
假设您在一些 UNIX 或者类 UNIX 主机上具有登录帐号,并且您需要更改这些帐号的密码,但是并没有使用网络信息服务(Network Information Service,NIS)、轻量级目录访问协议(Lightweight Directory Access Protocol,LDAP)或者能够在每台计算机上识别出您是相同的登录用户的一些其他机制对这些帐号进行同步。在大多数情况下,登录到一台特定的主机,并运行合适的 passwd 命令并不会花费很长的时间,可能只需要一分钟而已。但是因为无法将您的密码编写在脚本中,所以您必须 进行“手动”登录,是这样吗?
其实并不是这样的。事实上,标准 Expect 分发版(完整的分发版)中包括一种命令行工具(以及描述其使用的手册页面!),该工具恰好可以负责完成这项烦琐的工作。passmass(请参见)是使用 Expect 编写的一个简短的脚本,它可以使得在二十台计算机上进行密码更改的工作就像在一台计算机上进行密码更改那样简单。不需要反复地输入相同的密码,您可以只启动一次 passmass,并允许您的桌面计算机负责更新每个单独的主机。您节省了大量时间并可以稍事休息,同时对于已经输入过的内容,极大地降低了错误输入的可能性。
这个 passmass 应用程序是一个非常优秀的模型,它说明了 Expect 的许多常规特性:
这个工具值得我们去使用和研究:这个实用工具已经编写完成,并且可以免费地下载,它易于安装和使用,可以节省大量的时间和精力。
从某种意义而言,它的作用是“无关紧要的”。如果任何操作都“按照既定的规则”进行(如果您使用了 NIS 或者一些其他的域身份验证或单点登录系统),或者可以通过编写脚本进行登录,那么就不需要使用 passmass 了。但实际情况并不总是这样的,而 Expect 非常适合于处理各种各样现有的问题。也许 Expect 能够帮助您节省更多的时间,以便您能够使您的配置更加合理化,这样一来您就不再需要 Expect 了。在此期间,您可以充分地利用它。
对于分布式的环境,passmass 仅使用 telnet、rlogin 或者 slogin 进行登录。我希望当前所有的 developerWorks 读者都不再使用这些协议,而是使用 ssh,passmasss 并没有 对 ssh 提供全面的支持。
另一方面,几乎所有与 Expect 有关的内容都编写得非常清楚,并且可以免费获得。只需要使用三行简单的内容(至多)就可以对 passmass 进行增强,以支持 ssh 和其他选项。
您可能已经了解了足够多的内容,完全可以开始编写或者修改您自己的 Expect 工具。当然,实际上 passmass 分发版中包含了以 ssh 方式进行登录的代码,但是省略了相应的命令行解析以到达这部分代码。本文介绍了一种方法,您可以修改分发版源代码,对 ssh 与 telnet 以及其他协议进行同样的处理:
} "-rlogin" {
set login "rlogin"
} "-slogin" {
set login "slogin"
} "-ssh" {
set login "ssh"
} "-telnet" {
set login "telnet"
在我自己的代码中,我实际上从这个“样本”中提出了更多的内容。现在,passmass 第 100 行附近的这一连串测试,非常好地说明了 Expect 的可读性。这里并没有涉及到很深的编程技术,不需要面向对象、单体应用程序、协同例程,或者其他巧妙的技术。您只是请求计算机负责您通常进行的输入工作。恰好,这个简单的操作步骤可以节省大量时间和精力。
Expect 究竟是 什么,您应该如何使用它呢?
“Expect”涉及到一些独特的概念,许多经常使用 Expect 的用户对这些概念并不是十分清楚:
Expect 是一种特定的、高级的和通用的编程语言,其语法与 Tcl 相同,并增加了 Tcl 中所没有的一些特殊用途的命令。
Expect 是一种可执行程序,从它正确地处理用 Expect 语言编写的输入的角度来看,它实现了这种语言。
expect 命令是其中的一个命令,Expect 以此对 Tcl 进行了扩展。
Expect 是一个 Tcl 包。一般说来,这意味着任何 Tcl 应用程序都可以在运行时加载 Expect 功能。
Expect 是一个基于 C 源代码的库,而这些 C 源代码则深入到 Expect 可加载的包和 Expect 可执行程序。
Expect 是某种工具的抽象概念,该工具:
实现终端交互的自动化,甚至在涉及到密码或者其他特殊项目的情况下
实现了一种“对话”模型,通过它对消息和响应的简单规律进行编码
在这种抽象中,不存在特定于 Tcl 的内容,实际上,现在有几种使用其他语言(如 Python、Perl,等等)的 Expect 模型的独立实现。尽管本文中的示例都采用基于 Tcl 的 Expect 进行表述,但是可以使用其他语言来编写所有这些示例。没有理由因为您熟悉或者不熟悉 Tcl,而限制您对 Expect 的使用。
请注意,考虑到一些技术细节超出了本文的关注重点,在前面的描述中,我稍微有些歪曲事实;例如,常规的 Expect 可执行程序不仅扩展了 Tcl 命令集,它还可以识别启动时的一些额外的命令行参数。尽管这些内容并不是专门针对主要的主题:一个简短的 Expect 程序所能够为系统管理员完成的工作,超出了大多数人的预期。
在了解了这个背景信息之后,“passmass 是一个 Expect 应用程序”表示:
passmass 是一个以 Expect 语言编写的文本文件。
如果您在正确地安装了 Expect 和 passmass 的主机上执行 expect passmass ...,那么您将得到正确的 passmass 功能。
请注意,具体的执行方法可能有一些变种:可以在一个 Tcl 解释器中以交互的方式加载 Expect、运行 passmass、创建 passmass 作为一个独立的可执行程序,等等。同样地,这些替代方法超出了本文所关注的重点。
让我们考虑一项更大的挑战,网络操作中心的日常操作中一项更典型的操作:检索一组托管的 Cisco 交换机的当前配置信息。尽管有些站点使用 SNMP 或者 HTTP 来进行这些操作,但是更常见的是使用控制台或者频内 telnet 会话来获取该信息。许多管理员认为,完成这项工作唯一可行的方法是输入与 中所示类似的命令。
telnet $MY_ROUTER
[User: admin]
[password: ...]
CCNA01# show running-config
[... Current configuration:
... version 12.0 ...
FIFTY LINES MORE OF CONFIGURATION DETAIL
其实并不是这样的;实际上,通过在 cron、回复邮件(mailback)服务器,或者类似的作业控制机制(请参见)中进行调用,这项工作完全可以实现自动化。
#!/usr/bin/expect
# initialize host, password, ...
package require Expect
set prompt {[00m# }
spawn telnet $host
expect {User: }
send admin\r
expect password:
send $password\r
expect -exact $prompt
send "show running-config\r"
expect -exact $prompt
send exit\r
这个脚本可以自动地检索路由器的配置信息,即在正确地编写了脚本之后,对其进行监视时不再需要输入密码或者进行干预。
这个示例是典型的 Expect 使用情况,其中有几点值得注意。首先,不存在任何适用于各种情况的解决方案。许多管理员在获得 Expect 时都认为它是一个单独使用的工具,以解决他们当前所碰到的问题。那不是 Expect。Expect 与电子铁钉寻找器 (electronic stud finder) 不同,后者可以不依靠任何其他工具、独立地查找铁钉。Expect 更像是一个手钻:您必须将其与合适的钻头,或者转换接头、孔锯,或者其他附件进行组合,才能真正实现它的用途。
同样地,Expect 至少需要进行一些自定义工作。当然,我通常使用 Expect 来解决一些其他工具所无法轻松解决的问题。running-config 的案例说明了,只需要几行内容,Expect 就可以自动地获得大量的信息。即使是这个简单的示例,也呈现出 Expect 所带来的一些问题。例如,这种特定的自动化,需要将管理密码以明文的形式嵌入到 Expect 脚本中;您需要判断,它是否适合于您的具体环境。
这个案例至少在一些更多的方面是非常典型的。$prompt 让我感到有些奇怪。屏幕上所显示的是 CCNA01# ;对于许多设备来说,这是非常典型的,这个提示符实际上嵌入了一些不可见的控制字符。幸运的是,Expect 提供了各种有价值的调试开关,以报告其交互过程;这正是我确定 CCNA01 所生成的各种字符的方法。
另外,如前所述, 返回了整个会话,包括我所需要的配置报告,还加上登录和退出作为其开始和结束。隐含在一般请求中的另一个有关 Expect 的常见误解是“检索一个命令的值”。Expect 并没有提供这些语义。当您坐在键盘前并输入一个命令时,您将它的操作认为是在下一个提示符之前所看到的相关显示。对于网络互连协议(如 telnet),在结果和提示符之间实际上并没有什么区别;您可以作为一个观察者对这些内容的含义进行分析。
与这种网络通信模型完全一致,Expect 并不直接区分结果和提示符。所有的 Expect 都称为对话,即它通过 send 发送的字符序列,以及它所期望(expect)的内容。因此在实际中,我使用一个正则表达式解析来为我提供所需要的细节信息。Expect 的扩展正则表达式功能非常强大,并且很容易通过代码确定格式。现在,让我们重点关注通常无法通过脚本实现自动化的广泛主题。
请记住,Expect 是一种功能强大的、通用的语言。实际的 Expect 应用程序通常用于解析命令行参数、将结果显示在分栏的表格中、从数据库中检索历史数据、显示图形用户界面 (GUI),以及更多的用途。所有这些都是常规的计算工作,本文中的示例并没有展示这些方面的内容。文本的重点是正确地理解 Expect 独特的价值。
使用与 中所示类似的解决方案,网络管理员通常会考虑下一步的“水平方向的”增强:从大量类似的计算机检索这类报告。Expect 提供了循环构造 foreach、while 等等,从而使得这样的工作变得更加简单。
它也可能很快地变得无法接受。假设您负责上百台 LAN 主机,这是一个比较常见的情况。您使用一个 Expect 脚本自动地依次登录到这些计算机,并检索重要的数据。现在,您对该脚本稍微进行一下抽象,以便对整个集合进行遍历。
问题是,运行所得到的脚本可能会花费很长的时间。它登录到一台主机,请求结果,接收结果,注销,然后转向下一台主机,请求一个新的结果,等等。这个过程中的延迟使得人们希望能够使用某种方法一次性地请求所有的结果,并按照结果到达的顺序对其进行收集,导致这些结果不同顺序的原因包括网络滞后、不同的负载,以及其他延迟。
有一种方法可以实现这种操作。实际上,Expect 提供了一些非常好的功能来同时管理多个对话。本文提供了一个程序的示例,该程序多次进行登录,在每个登录上执行一些长时间运行的命令,然后根据结果到达的顺序进行接收;对这个案例进行了整理,以便这些结果返回的顺序与其启动顺序相反(请参见)。
#!/home/claird/local/ActiveTcl/bin/tclsh
package require Expect
log_user 0
# Initialize user, passphrase, ... here.
# Sequentially login and issue time-consuming commands to all
for {set i 0; set delay 8} {$delay & 0} { incr delay -1} {
spawn ssh $user@$host
set sid($i) $spawn_id
expect rsa':
send $passphrase
expect "Last login"
expect -ex $prompt
set active($sid($i)) $i
send -i $sid($i) "echo `date`; sleep $ echo `date`; echo \
'$delay done on $i.'\r"
while {[llength [array names active]]} {
expect -i [array names active] -ex $prompt {
puts "RECEIVED:
$::expect_out(buffer)"
send -i $expect_out(spawn_id) exit\r
expect -i $expect_out(spawn_id) eof
unset active($expect_out(spawn_id))
当您运行这个脚本时,将看到与 所示类似的结果。
echo `date`; sleep 1; echo `date`; echo '1 done on 7.'
Mon Apr 23 22:15:15 UTC 2007
Mon Apr 23 22:15:16 UTC 2007
1 done on 7.
echo `date`; sleep 2; echo `date`; echo '2 done on 6.'
Mon Apr 23 22:15:15 UTC 2007
Mon Apr 23 22:15:17 UTC 2007
2 done on 6.
echo `date`; sleep 3; echo `date`; echo '3 done on 5.'
Mon Apr 23 22:15:15 UTC 2007
Mon Apr 23 22:15:18 UTC 2007
3 done on 5.
echo `date`; sleep 8; echo `date`; echo '8 done on 0.'
Mon Apr 23 22:15:14 UTC 2007
Mon Apr 23 22:15:22 UTC 2007
8 done on 0.
在编程的层次上,请注意,所有这些登录都是通过基于密码的 ssh 登录到相同的主机,并使用相同的用户和密码凭据。在一个更实际的示例中,可以通过更多行的代码来管理各个不同的主机,对每个主机使用不同的帐号和登录协议。尽管对于那些对网络管理员非常有 价值的命令,sleep 是一个很好的模型;但是没有理由先睡眠数秒钟,然后再返回。
即使这个示例,也无法尽述 Expect 的所有功能。Expect 还提供了使用一个内置的套接字编程接口,直接管理 TCP/IP 对话的功能,并且它可以实现半自动化,在两种不同的模式之间来回切换,用户在其中一种模式中输入部分对话,而 Expect 在另一种模式中实现所有操作的自动化。
我们将在以后的文章中介绍这些主题。本文的目标是,展示 Expect 中包含了大多数管理员并不知道的许多内容,具体来说是了解了该工具后,马上就能够解决网络管理工作中各种常见的问题。在脚本中使用密码和密码条目,以及并发地控制多个连接,这些功能都是非常强大的。
Expect 可以完成所有看起来无法实现自动化的工作:在脚本中使用密码条目、登录到远程用户的会话和返回对他或者她的控制,以及更多的工作。尽管它已经得到了广泛使用,但是 Expect 却常常被人们所误解。正确地理解有关 Expect 的一些基本知识(如何调用它、它的对话模型、它的编程辅助,等等),以便在系统和网络管理工作中更充分地发挥它的作用。
[22:55] []
[22:55] [发表评论]
[22:55] []
[22:55] []
[22:55] []
[22:55] []
创新的触觉界面系统实现了一种全新的应用程序交互方式。使用廉价的电子游戏外围设备、嵌入式加速计和从笔记本电脑到手持电话等各种 PC 设备,可以实现控制应用程序的新方式。应用程序通常在界面控制方面比较滞后,由于 API 十分有限,应用程序的功能被局限于键盘和鼠标事件。
本文将演示的技术和代码可以使您将传统桌面计算环境中的应用程序与支持加速计的设备连接起来。
2003 年制造的 IBM(R) ThinkPad 和后期推出的硬盘活动保护系统(Hard Disk Active Protection
System,HDAPS)。如果您不确定自己的硬件配置,请查看 Lenovo Web 站点中的详细技术细节。
您需要一个内核支持 HDAPS 的 Linux(R) 发行版。HDAPS 驱动程序必须包含在内核中以支持加速计访问。较新的内核版本,包括 Red Hat、Debian、Gentoo 和 Ubuntu 都包含 HDAPS 驱动程序。有关如何开始使用 HDAPS 内核驱动程序的更多信息,请参考
小节中有关 HDAPS 的文章。
您需要使用来自 CPAN 的 Time::HiRes 模块,以便在记录数据时提供亚秒级的计时控制。此外,还需要使用 X11::GUITest 模块将合成的 X 事件发送到应用程序(参见 )。
注意,本文介绍的技术和算法应该适用于任何具有板载加速计的笔记本电脑。对于运行除 Linux 以外的其他操作系统的 MacBook 或 ThinkPad ,应用本文给出的代码也不会太困难。手持设备和其他支持加速计的系统也可以用于实现翻转、倾斜和命令之间的转换。如果在其他操作系统中运行,那么可以使用多种方法将合成的键盘和鼠标事件发送给应用程序。如果使用的是 Microsoft(R)
Windows(R),则考虑使用命令的 SendKeys 类实现应用程序访问。
对特定于应用程序的特性进行控制,这通常是通过 API 层的功能钩子实现的。然而,很多应用程序并不具备完整的 API 集,因此需要借助鼠标和键盘输入才能全面地操作应用程序。其他的应用程序没有提供 API,而只通过传统的人机界面设备提供交互。结合本文演示的组件和算法,无需 D-Bus 或其他 API 就可以实现应用程序控制。
要实现对 D-Bus 和没有提供 API 的应用程序的完全控制,向应用程序发送合成的 X 事件是一种重要方法。
您需要花费大量时间将 XTest 库函数调用集成到您的应用程序中。您需要指定键释放速率并在识别窗口中查找所有细微区别,同时跟踪键盘和鼠标状态。或者,可以使用 X11::GUITest 为您完成这些任务。X11::GUITest 模块为 XTest 库提供了一个健壮的包装器,可以显著提高事件处理效率。
对于很多可以响应用户键盘和鼠标事件的应用程序,在使用合成事件时将会以无法预期的方式执行。您可能需要调整键盘重复率和延迟,或寻求不同于标准输入机制的新方法。例如,Google Earth 并不始终以有效的方式响应按键事件,无论重复率和速度如何。本文使用了一种可以代替等价合成输入的方法:按住导航罗盘的中心控制杆。在尝试创建合成 X 事件以执行与实际替代方法相同的功能时,您可能会在其他程序中发现类似的误差或限制。在使用通过编程方式触发的键盘和鼠标事件连接应用程序时,您需要进行实验以找出可提供最佳可用性体验的输入方法。
我们已经启用了传感器并设置了控制框架,因此接下来将在一个 Perl 程序内进行集成,将移动转换为操作。清单 1 展示了 hdapsGoogleEarth.pl 脚本的头部和变量声明。
#!/usr/local/bin/perl -w
# hdapsGoogleEarth.pl - control google earth by the pitch and roll of a thinkpad
use Time::HiRes
qw( usleep );
use X11::GUITest qw( :ALL );
my $threshMove = 3;
# lower limit indicating motion
my $shakMin = 20;
# amount of a shake required to indicate change in mode
my $timeThreshold = 1; # number of seconds between mode changes
my $maxMove = 25;
# maximum movement of mouse from center point
my @avgX = ();
# array of 10 recent hdaps X values for shake detection
my $lastTime =
# record time of last mode change
my $slptim = 25000;
# microseconds to pause between data reads
my $maxTip = 100;
# turn thinkpad on its' side to exit the program
my $restX = 0;
# `resting' positiong of X axis accelerometer
my $restY = 0;
# `resting' positiong of Y axis accelerometer
# array of window id's associated with google earth
my $hdapsFN = "/sys/devices/platform/hdaps/position"; # hdaps sensor
包含了必需的模块后,将设置全局变量来处理移动并保持输入模式。本文演示了以升降或移动模式控制 Google Earth。通过摇动 ThinkPad,能够在多个模式之间快速切换,从而实现更直观的导航。清单 2 可以识别并将焦点置于 Google Earth 窗口。
@windows = FindWindowLike("Google Earth");
die "No google earth windows found" unless( scalar(@windows) & 0 );
my ( $geX, $geY, $geW, $geH, undef, undef ) = GetWindowPos( $windows[0] );
# rose center is left 110 from geX+geW, down 135 from geY
# zoom center is left 22 from geX+geW, down 135 from geY
my $cntrCompX = ($geX + $geW) - 110;
my $cntrCompY = $geY + 135;
my $centerZoomX = ($geX + $geW) - 22;
my $centerZoomY = $geY + 135;
my $maxZoomTop
= $centerZoomY - $maxM
my $maxZoomBottom = $centerZoomY + $maxM
my $maxLeft
= $cntrCompX - $maxM
my $maxRight
= $cntrCompX + $maxM
my $maxTop
= $cntrCompY - $maxM
my $maxBottom = $cntrCompY + $maxM
my $mouseX = $cntrCompX;
my $mouseY = $cntrCompY;
my $controlMode = "movement";
MoveMouseAbs( $cntrCompX, $cntrCompY );
PressMouseButton M_LEFT;
($restX, $restY ) = readPosition();
第一步是使用 X11::GUITest 模块,通过 FindWindowLike 函数定位 Google Earth 窗口。有时可以识别多个 Google Earth 窗口,但是第一个窗口 ID 通常为主窗口。注意,如果第一个窗口 ID 不是 Google Earth 主窗口,程序将不能正确处理输入。如果您的机器没有将 Google Earth 主窗口指定为第一个 Google Earth 窗口 ID,则会出现问题。如果发生这种情况,在 @windows 数组中的不同位置测试窗口 ID,直至找到 Google Earth 主窗口的正确 ID。
确定了 Google Earth 输入窗口的正确坐标并将鼠标移至适当位置之后,可通过 readPosition 确定加速计的基准。清单 3 展示了 readPosition 子例程。
sub readPosition {
my ($posX, $posY) = "";
open(FH," $hdapsFN") or die "can't open hdaps file";
while(my $hdapsLine = &FH&)
$hdapsLine =~ s/(\(|\))//g; # remove parens
($posX, $posY) = split ",", $hdapsL
}# while hdaps line read
close(FH);
return( $posX, $posY );
}#readPosition
从 HDAPS 传感器读取数据非常简单,由于其不可查找特性,每次读取数据时都需要打开和关闭文件。(有关使用特定 HDAPS 传感器的更多信息,请查阅 )。完成了基本变量声明和子例程后,程序将继续进行主程序循环,如下所示。
# get accelerometer values
my ($currX, $currY) = readPosition();
$currX -= $restX;
# adjust for rest data state
$currY -= $restY;
# exit loop if ThinkPad tipped past 'exit' range
last if( abs($currX) & $maxTip || abs($currY) & $maxTip );
my $testX = abs($currX);
# keep original values for trending
my $testY = abs($currY);
if( $controlMode eq "movement" )
stay at the maximum distance to move even if severely tipped
if( $testX & $maxMove ){ $testX = $maxMove }
if( $testY & $maxMove ){ $testY = $maxMove }
$mouseX = ($cntrCompX - $testX) if( $currX & -$threshMove );
$mouseX = ($cntrCompX + $testX) if( $currX & $threshMove );
$mouseY = ($cntrCompY - $testY) if( $currY & -$threshMove );
$mouseY = ($cntrCompY + $testY) if( $currY & $threshMove );
if( $mouseX & $maxLeft )
$mouseX = $maxLeft
if( $mouseX & $maxRight ) {
$mouseX = $maxRight
if( $mouseY & $maxTop )
$mouseY = $maxTop
if( $mouseY & $maxBottom ){
$mouseY = $maxBottom }
# reset the mouse to the center if the current reading is not past
# the threshold
$mouseY = $cntrCompY if( $currY & $threshMove && $currY & -$threshMove );
$mouseX = $cntrCompX if( $currX & $threshMove && $currX & -$threshMove );
每执行一次主程序循环都将从加速计传感器中读取新的值。将从之前创建的引用状态中减去这些值,以便将类似 ‘-379,-380’ 的值转换为更为有用的 ‘0,0’(表示 ThinkPad 保持静止)。接下来,程序将检查 ThinkPad 是否从静止状态变为动态翻转,从而表示一种退出条件。这是必须的,因为全部的键盘和鼠标控制已经由应用程序接管,因此,需要使用一种物理定向(physical-orientation)方法告诉应用程序执行退出。
如果应用程序处于移动处理模式,接下来的两行将移动边界的上限设置为 $maxMove。接下来的四行代码根据翻转 ThinkPad 而超过移动阈值($threshMove)的程度将鼠标移至相应的 X 和 Y 维上。人们很少能够始终将 ThinkPads 保持在理想水平,对于每个维度使用一个倾斜阈值有助于简化基状态检测。
接下来的四行代码将鼠标移动限制为从每个维度中心移动的最大距离。最后,如果没有达到上面提到的移动阈值,则将鼠标位置重置到中心。如果重设中心位置,那么在倾斜超过阈值后将产生一个跳跃的移动,但是在多数应用程序中,缓慢的移动或未知的初始状态比较可取。
清单 5 显示了一个相对简单的升降控制模式。
# zoom control - move up and down only
$mouseY = ( $centerZoomY - $testY ) if( $currY & -$threshMove );
$mouseY = ( $centerZoomY + $testY ) if( $currY & $threshMove
if( $mouseY & $maxZoomTop ){
$mouseY = $maxZoomTop }
if( $mouseY & $maxZoomBottom ){
$mouseY = $maxZoomBottom }
$mouseX = $centerZoomX;
$mouseY = $centerZoomY if( $currY & $threshMove && $currY & -$threshMove )
}#if in zoom mode
MoveMouseAbs( $mouseX, $mouseY);
# move the mouse after coordinates computed
类似上文的移动模式,升降控制模式将根据翻转 ThinkPad 而超过移动阈值的程度在 Y 维度移动鼠标。计算得到的移动将被限制在从 zoom 滑块的中心位置移动的最大距离。同样,如果没有达到移动阈值,则将鼠标位置重设为中心。
计算鼠标位置之后,对于升降或移动模式,都将移动鼠标来控制应用程序。
通过使用上面的代码,您可以通过移动 ThinkPad 来控制 Google Earth 的移动或升降模式。要在模式间进行切换,需要对主程序循环的每次循环执行额外的检查。清单 6 和 7 展示了主程序逻辑的其余部分以及 shakeCheck 子例程,它将捕捉动作并切换模式。
# mode switch by shaking, only if at least timeThreshold seconds have passed
# since last mode switch
if( shakeCheck( $currX ) == 1 && abs($lastTime - time) & $timeThreshold)
$lastTime =
if( $controlMode eq "movement" )
$controlMode = "zoomRotate";
$mouseX = $centerZoomX;
$mouseY = $centerZoomY;
$controlMode = "movement";
$mouseX = $cntrCompX;
$mouseY = $cntrCompY;
}#if movement or zoomRotate mode
ReleaseMouseButton(M_LEFT) if( IsMouseButtonPressed(M_LEFT) );
MoveMouseAbs( $mouseX, $mouseY);
PressMouseButton( M_LEFT );
print "$controlMode mode\n";
}# if a large enough shake in X dimension detected
usleep($slptim);
ReleaseMouseButton M_LEFT;
# let go of button on exit
在移动和升降模式之间切换时,将交替生成一个由 shakeCheck 子例程返回的 true 值,以及一个可接受的模式切换之间的时间间隔。可以将 $timeThreshold 配置为要求模式切换之间具有一定的时间。您可能希望将这个参数设得更大一些,同样,对于 shakeCheck 子例程中显示的 shakMin 变量也一样,如全局定义部分和清单 7 所示。
sub shakeCheck
push @avgX, $xV
# build an array of 10 samples before attempting processing
return(0) if( @avgX & 10 );
my $currAvg = 0;
for my $eachX ( @avgX ){ $currAvg += $eachX };
$currAvg = $currAvg /10;
shift @avgX;
# if current value is a big enough deviation from average value
return(1) if( abs($currAvg - $xVal) & $shakMin );
return(0);
}#shakeCheck
子例程 shakeCheck 只对 X 维构建了过去 10 次读取 HDAPS 传感器的平均值。将 X 维的当前值与平均值比较,如果它们至少偏离了 $shakMin,则设置一个摇动条件并由子例程返回 true。可以很容易地检测到一次快速摇动,这是因为,尽管移动幅度相对较小,但是加速度比较大,足以捕捉到有用的动作。
要运行上面构建的 hdapsGoogleEarth.pl 程序,请启动 Google Earth 应用程序。当显示默认的地球仪时,使用 perl hdapsGoogleEarth.pl 命令启动合成 X 事件转换程序。您应当看到 Google Earth 窗口将获得焦点,同时在右上角鼠标将获取移动控制杆。拿起 ThinkPad 并开始进行导航,并注意在靠近垂直方向上向任意维度倾斜您的 ThinkPad 以便退出程序。
小节中的代码归档文件,其中包含另一个 Perl 脚本,它通过将 ThinkPad 的倾斜和翻转转换为按键事件来控制 Google 街道视图。要使用 hdapsGoogleStreetView.pl 脚本,请确保您使用的是 Firefox Web 浏览器,并在其中打开了 Google Maps 并处于街道视图(street-view)模式。使用 perl hdapsGoogleStreetView.pl 启动应用程序,您将能够通过倾斜和翻转 ThinkPad 导航城市街道。查看源代码了解 hdapsGoogleStreetView.pl
应用程序如何使用按键代替鼠标移动来控制应用程序。
对于诸如地图绘制软件这样需要持续输入的应用程序,非常适合应用直观的导航系统。使用本文演示的代码,您将可以通过合成的键盘和鼠标事件将这种新一代导航系统应用到任何应用程序。
通过 Bluetooth 将您的支持加速计的手机连接到 PC,并通过翻转手机控制应用程序。将一个惯性陀螺仪连接到 ThinkPad,并通过转动、倾斜和翻转动作扫描地图。
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
并行编程曾一度是顶级编程人员面临大型超级计算问题困扰时的唯一关注点。但是,随着面向主流应用的多核处理器的出现,并行编程现已成为所有专业软件开发人员都必须了解和掌握的一项技术。
并行编程也许会很难,但事实上,它只是“不一样”而已,并非“很难”。它不仅具有更为传统的串行编程的所有特点,还包含三个定义完备的附加步骤:
识别并发:分析问题,以识别能够同时执行的任务。
揭露并发:重新构建一个问题,确保高效完成任务。此步骤通常需要确定任务本身与组织源代码之间的依赖程度,以便任务得到高效管理。
表达并发:在源代码中使用并行编程符号来表示并行算法。
以上的每一步都很重要。前两步在最近一本关于并行编程设计模式的书中有详细介绍 [mattson05]。本文将重点介绍第三步:在源代码中使用并行编程符号来表示并行算法。这种符号可以是并行编程语言,也可以是通过程序库界面实施的应用编程接口 (API),或是添加到现有序列语言的语言扩展。
选择一种独特的并行编程符号绝非易事。这些符号的学习难度参差不齐,而且极其耗时。因此,掌握几种符号并从中选择一种进行运用的做法是不切实际的。编程人员所需的是一种能够充分详细地了解不同符号的“优势”,以及各项高级特征的快捷方法,以便明智地决定学习哪种符号。
在本文中,我们将对几种不同的并行编程符号进行高度全面概述,重点介绍其主要使用方法,并对它们的特殊优势和缺陷进行分析。我们将具体介绍以下几种符号:
OpenMP:针对简单并行编程的编译器指示
MPI:支持超高性能便携性的程序库例程
Java:基于领先对象的编程语言并行
为了使我们的讨论尽可能地具体详实,我们在每种情况下都部署了一款知名的 π 程序的并行版本。这是一种利用梯形法则(其中被积函数和积分极限被选中)来运算的简单数值积分,因此从数学角度讲,正确的答案为 π。很多人都将它视为并行编程中最为基本的程序。在本文最后,我们还将简单介绍如何选择一款并行编程符号来使用并掌握。
在微积分研究中,我们了解到一个积分在几何上可以表示为曲线下面积。也就是说,积分的近似值可以通过计算得出。我们先将积分面积划分为许多的梯级,并在每一梯级内画出一个矩形,并让它的高等于该梯级中心的被积函数值。那么这样,每个矩形面积的和就约等于该积分。
如图 1所示:梯形积分----每一竖条都拥有固定宽度的“梯级”。每个竖条的高度便是被积函数的值。将所有竖条的面积加在一起便得出曲线下面积的近似值,即被积函数的值。
我们可以选择一个被积函数及积分极限,那么该积分在数值上便等于 π。这样便可更加直接地进行程序正确性检验。下面我们向您展示了一个执行该算法的简单的 C 程序:
static long num_steps = 100000;void main (){&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& double x, pi, sum = 0.0;&&&&&&&&&&&&&&&&& step = 1.0/(double) num_&&&&&&&&&&&&&&&&& for (i=0;i&= num_ i++){&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& x = (i+0.5)*&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& sum = sum + 4.0/(1.0+x*x);&&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&& pi = step *}
OpenMP [omp] 是一款为共享内存计算机来编写并行应用程序的行业标准 API。OpenMP 的主要目的,是使高性能计算中常见的循环导向型程序更加易于编写。OpenMP 中的各种结构可支持 SPMD、Master worker、管线,以及大多数其他并行算法 [Mattson05]。
OpenMP 是一款非常成功的并行语言。市面上每款共享内存计算机均支持它的运行。最近,英特尔在 OpenMP 上创建了一个变量,从而实现了对集群的支持。OpenMP 还能逐步添加并行的编程方式,因此现有的顺序程序可发展为并行程序。但是,这一优势却也成了OpenMP 的最大缺陷。那是因为通过使用逐步并行,编程人员可能会错过某一程序的大规模重建,从而失去获得最大性能的机会。
OpenMP 的标准仍在不断演进。为此,一家名为“OpenMP 体系结构评审委员会 (OpenMP Architecture Review Board)”的行业团体定期举行会晤,共同开发该语言的全新扩展。OpenMP 的下一版本(3.0 版)将包含一种任务队列能力。这将支持 OpenMP 处理更广泛的控制结构,以及更多的一般递归算法。
OpenMP 基于 fork-join 的编程模式而设计。OpenMP 程序起初以一条单线程的形式开始运行。如果编程人员希望在程序中利用并行,那么就需将额外的线程进行分支,以创建线程组。这些线程在称为“并行区域”的代码区域内并行执行。在并行区域末尾,将等待所有线程全部完成工作,并将其重新结合在一起。那时,最初线程或“主”线程将继续执行,直至遇到下一个并行区域(或程序结束)。
OpenMP 的语言结构根据编译器指示而定义,可为编译器布置任务,以实施理想的并行。在 C 和 C++ 中,这些指示根据制导语句来定义。
OpenMP 制导语句在任何情况下的形式都相同
#pragma omp construct_name one_or_more_clauses
其中“construct_name”规定了编程人员希望执行的并行动作,而“clauses”则对该动作进行修改,或对线程所见的数据环境进行控制。
OpenMP 是一种显式的并行编程语言。一旦线程创建,或者工作已经映射到该线程上,那么编程人员必须对希望执行的动作加以详述。因此,即使是 OpenMP 这样简单的 API 也有诸多结构和子句需要编程人员学习。所幸的是,仅利用整个 OpenMP 语言的一小部分子集,便可完成大量上述工作。
可利用“parallel”结构在 OpenMP 中创建线程。
#pragma omp parallel{…. A block of statements }
独自使用时(没有修改任何子句),程序可创建出一系列线程供运行时环境选择(这些线程通常与处理器或内核的数量相等)。每条线程都将根据并行制导语句来执行语句块。该语句块可以是 C 中的任意合法语句组,但是唯一例外的是:您不能分支到并行语句块之中或之外。您只要稍微思考一下就能明白。如果线程要全面执行语句组,并且该程序的继发行为还要有意义,那么您便不能随意将线程分支到并行区域内的结构之中或之外。这是 OpenMP 的一项公共约束。我们将这种缺乏某些分支的语句块称为“结构块”。
您可以令所有线程执行相同的语句,从而进行大量的并行编程。但是要体验 OpenMP 的全部功能,我们要做的就不止这些。我们需要在多条线程之间共享执行语句集的工作。我们将这种方式称为“工作共享”。最常见的工作分享结构是循环结构,在 C 中即为“for”循环
#pragma omp for
但是,这一结构仅对具有规范形式的简单循环起作用
for(i=lower_ i&upper_ inc_exp)
“for”结构执行循环的迭代,并将其打包至那些利用并行结构创建的早期线程组中。循环极限和循环索引 (inc_exp) 的递增表达式需在编译时完全确定,并且这些符号中使用的任何恒量都必须在线程组中保持相同。您只要思考一下就能明白。系统需要得出循环的迭代数量,然后将其映射到能够分发至线程组的集上。如果所有线程均计算相同的索引集,那么上述工作只有通过持续稳定的方式才能实现。
请注意,“for”结构并不能创建线程,您只能借助并行结构来做到这点。为了简捷起见,您可以将并行结构和“for”结构放在一个制导语句中。
#pragma omp parallel for
此举可创建一个线程组,以便执行紧随其后的循环迭代。
该循环迭代必须是独立的,因此不论迭代的执行顺序如何,或是究竟由哪些线程执行循环的哪些迭代部分,循环结果都将相同。如果一条线程写入变量,另一条线程读取变量,那么将产生循环传递相关性 (loop-carried dependence),程序也将生成错误的结果。编程人员必须仔细分析循环体,以确保没有任何循环传递相关性的发生。在很多情况下,循环传递相关性来源于保存中间结果(用于指定的循环迭代)的变量。在此情况下,您可以通过声明每条线程都将具有自己的变量值,以除去循环传递相关性。这可通过私有子句来实现。例如,如果循环使用名为“tmp”的变量来保存临时值,那么您可将以下子句添加到 OpenMP 结构中,这样它便可用于循环体内部,而不会造成任何循环传递相关性。
private(tmp)
另一种常见情况是循环内出现变量,并用于从每个迭代中累积数值。例如,您可以利用循环将某项计算的所有结果进行求和,得出一个数值。这在并行编程中十分常见,通常被称为“规约”。在 OpenMP 中,我们的规约子句表示为
reduction(+:sum)
同私有子句一样,该子句可添加到 OpenMP 结构中,用以提示编译器等待规约。这时,程序便会创建一个临时私有变量,以便为每条线程计算累积操作的部分结果。当该结构运行到最后时,每条线程的值将结合起来产生最终答案。用于该规约的操作在子句中同样进行了详细说明。在这种情况下,此操作为“+”。根据对遭受质疑的数学操作进行特性识别,OpenMP可定义用于规约的私有变量值。例如,对于“+”来说,该值为零。
当然,OpenMP 还有更为复杂的情况,但是借助这两个结构和两个子句,我们便能够解释如何并行 π 程序。
为了简单起见,我们将统一规范所需的步骤,并且只使用默认数量的线程进行工作。在串行 π 程序中,还有一个需要并行的单循环。除因变量“x”和累积变量“sum”之外,该循环的迭代完全独立。请注意,“x”在此用于计算一个循环迭代内的临时存储。因此,我们可以通过一个私有子句将该变量定位到每条线程,以便对其进行处理
private(x)
从技术层面上讲,循环控制索引可创建一个循环传递相关性。但是,OpenMP 却认为该循环控制索引需要定位到每条线程之中,以使其自动实现对所有线程的私有化。
累积变量“sum”用于计算总和。这是一个经典规约,因此我们可以使用规约子句:
reduction(+:sum)
将这些子句添加到“parallel for”结构中,我们便借助 OpenMP 完成了 π 程序的并行。
#include &omp.h&static long num_steps = 100000;void main (){&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& double x, pi, sum = 0.0;&&&&&&&&&&&&&&&& step = 1.0/(double) num_#pragma omp parallel for private(x) reduction(+:sum)&&&&&&&&&&&&&&&& for (i=0;i&= num_ i++){&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& x = (i+0.5)*&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& sum = sum + 4.0/(1.0+x*x);&&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&& pi = step *}
请注意,我们的 OpenMP 中同样包括标准的 include file
#include &omp.h&
这规定了编程人员有时需要的 OpenMP 类型和运行时程序库例程。请注意,在此程序中,我们虽没有利用该语言的这些特性,但是将 OpenMP include file 包括在内却是一个很好的想法,以避免日后在程序需要时进行修改。
MPI,或者讯息传递界面(Message Passing Interface)是我们当今使用的一种最久的并行编程 API。MPI 程序作为一系列独立进程,主要通过收发讯息实现互动。MPI 的一大优势便是它只占用并行计算机中的极少部分硬件。它的唯一要求,便是处理器或内核共享同一简单网络,从而在任何进程组间充分地路由讯息。这也支持 MPI 在任何通用并行系统上运行----不论从对称多处理器到分布内存,还是从大规模并行超级计算机到各种集群。
MPI 诞生于 20 世纪 90 年代早期,那时集群刚刚兴起,大规模并行处理器(MPP)占据着高性能计算的大壁江山。每个 MPP 厂商都有自己的讯息传递符号。厂商很乐意看到这种状况,因为这样可以将用户锁定在自己的产品线内,但这却让编程人员大伤脑筋。因为软件的寿命远比硬件要长。再者,由于没有一种可移植性符号,所以,每次在研发新电脑时,应用编程人员都不得不将其软件从一种讯息传递符号转换为另一种,耗费了大量精力。
MPI 并不是第一款可移植性讯息传递库,但却是第一款由行业/国家实验室/学术人员联合创建的程序库。MPI 的创建过程集合了几乎所有的业内主要力量,并迅速成为高性能计算领域的标准讯息传递界面。现在,MPI 已走过了大约 15 个春秋,但它仍是高性能计算领域并行编程应用中应用最为常用的符号。
目前,大多数 MPI 程序均使用单程序多数据或 SPMD 模式 [mattson05]。它的原理非常简单:所有处理单元(PE)运行同一程序。并且它们都具有一个独特的整数 ID,以确定其在处理单元集内的排序。这样,程序便可利用该排序分配工作,并决定由哪个 PE 处理哪部分工作。换句话说,程序只有一个,但是由于根据 ID 所做的选择多种多样,因而,PE 间的数据也就有可能不同;即“单程序,多数据”模式。
MPI 是一个可靠实用的讯息传递系统,它专为支持广泛的硬件而设计,并可支持带有完整模块化设计的复杂软件架构。
MPI 的设计理念基于通信子(communicator)。在创建一套进程时,它们可以定义群组。这样,进程组便可共享通信环境,从而更好地进行通信。这种进程组与通信环境的结合,可以定义一个独特的通信子。当您考虑在程序中使用程序库时,这一概念的力量就会凸现出来。如果程序员不细心,那么程序库开发人员创建的信息便可能与程序中用来调用程序库的信息发生干扰。但是通过通信子,程序库开发人员便可创建自己的通信环境,并确保就通过系统传递的相关信息而言,程序库的内部活动不会对其造成干扰。
当 MPI 程序启动时,将创建默认的通信子 MPI_COMM_WORLD。该通信子作为第一个参数被传递到每个 MPI 例程中。其它的参数则用来定义信息来源,以及定义保存信息的缓冲。在这种情况下,MPI 例程就会返回一个整数值作为错误参数,以查询例程执行期间出现的所有问题。
MPI 程序通常在靠近开始的位置上设置三个例程的调用,以设置 MPI 的使用方式。
int my_id,
MPI_Init(&argc, &argv) ;
MPI_Comm_Rank(MPI_COMM_WORLD, &my_id) ;
MPI_Comm_Size(MPI_COMM_WORLD, &numprocs) ;
第一个例程(MPI_Init)用来输入任何 C 程序人员都熟悉的命令行参数,并初始化 MPI 环境。后两个例程用作输入 MPI 通信子(本例中为默认的通信子),并返回调用的进程排序(rank)和进程总数。这里,排序作为该进程的唯一标识符,可从 0 一直排到进程数量减去 1。
关于创建多少进程以及它们将在哪些处理器上运行的详细信息,都被置于 MPI 应用编程接口之外。由于支持 MPI 的平台各异,所以需要使用不同的方法。在大多数情况下,会有一个主机文件按名称顺序列出每颗处理器的信息。这些信息将传递给可用于大多数 MPI 平台的常见 shell script(称为 mpirun),以启动 MPI 程序。鉴于这一简单进程的详情在不同平台上表现各异,我们在这里就不进行讨论了。
在每个 MPI 程序的最后都应该有一个例程来关闭环境。此函数将返回整数值错误代码。
int MPI_Finalize();
MPI 程序运行于这些例程之间。程序的大部分都是由规则的串行代码组成,并以您所选择的语言来表示。正如上文所述,虽然每个进程都在执行相同的代码,但是程序行为却视进程排序而有所不同。并且在进程间需要通信或其它交互的点上,还插入了 MPI 例程。MPI 的第一版上即有超过 120 个例程,最新版本(MPI 2.0)的例程数量更为庞大。但是,大多数程序仅使用 MPI 函数中一个很小的子集。因此,我们只讨论一个程序;一个执行规约并将最终规约结果返回组中各进程的例程。
int MPI_Reduce(void* sendbuf, void* recvbuf,
int count, MPI_Datatype datatype, MPI_OP op,
int root, MPI_COMM comm.)
此函数采用缓冲“sendbuf”中类型为“datatype”的“count”值,并用“op”操作来累积每个进程的结果,最后将结果置于排序为“root”的进程的“recvbuf”缓冲中。借此,MPI_Datatype 和 MPI_OP 就可直观地获得期望的数值,如“MPI_DOUBLE”或“MPI_SUM”。
此外,借助MPI 广播讯息(MPI_Bcast)中的其它常用例程,还可定义“barrier”同步点(MPI_Barrier)、发送讯息(MPI_Send)或接收讯息(MPI_Recv)。您可以通过在线方式,或在 [mpi] 及 [mattson05] 中了解有关 MPI 的更多详情。
MPI π 程序是对最初串行代码进行的直接修改。为了简便起见,我们将继续在程序本身中设定步骤数量,而不是输入数值然后广播其它进程。
程序以 MPI include 文件开始,以定义 MPI 中的数据类型、常量以及各种例程。在此之后,我们添置了标准的 3 个例程,以初始化 MPI 环境并将基本参数(进程的数量和排序)用于程序中。
#include &mpi.h&static long num_steps = 100000; void main (int argc, char *argv[]){&&&&&&&&&&&&&&&& int i, my_id, &&&&&double x, pi, step, sum = 0.0 ;&&&&&&&&&&&&&&&& step = 1.0/(double) num_&&&&&&&&&&&&&&&&&& MPI_Init(&argc, &argv) ;&&&&&&&&&&&&&&&& MPI_Comm_Rank(MPI_COMM_WORLD, &my_id) ;&&&&&&&&&&&&&&&& MPI_Comm_Size(MPI_COMM_WORLD, &numprocs) ;&&&&&&&&&&&&&&&& my_steps = num_steps/&&&&&&&&&&&&&&&& for (i=my_ i&num_ i+numprocs)&&&&&&&&&&&&&&&& {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& x = (i+0.5)*&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& sum += 4.0/(1.0+x*x);&&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&& sum *= &&&&&&&&&&&&&&&& MPI_Reduce(&sum, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& MPI_COMM_WORLD) ;&&&&&MPI_Finalize(ierr);&}
最后,我们利用一种常用技巧将进程集合中的循环迭代进行划分。请注意,这里的循环极限已经改变,它从运行每个进程的 ID,变为了随着组中进程数量而递增的迭代数量。这是由于 MPI 中被定义的排序用作了 ID,并且排序数量可以从 0 一直到进程数量减去 1。本质上,这种简单的转换是以循环方式将循环迭代加以分配,就像我们将一副纸牌分配到不同进程中一样。
每个进程完成后,部分总和会把得到的部分结果纳入变量“sum”中,这种规约主要包含在以下调用中:
&MPI_Reduce(&sum, &pi, 1, MPI_DOUBLE, MPI_SUM, 0,
MPI_COMM_WORLD) ;
相比我们上文所讨论的 MPI_Reduce 定义,这里每个参数的意义应该更加清晰。我们正在使用的部分和中,“sum”作为发送缓冲,变量“pi”作为接收缓冲。根据 MPI_Reduce 例程的第六个参数,这一数值将到达排序为“0”的进程中。“发送缓冲”包含一个带有附加累积操作(即 MPI_SUM)的 MPI_DOUBLE类型数值。最后,涉及到这一规约操作的进程会使用通信子 MPI_COMM_WORLD进行运作。
Java 语言在设计之初便内置了多线程支持。作为Java 技术的一个重要组成部分,线程在语言(语法)级别和 Java 虚拟机,以及类别库(class library)级别上均能够得到支持。Java 线程与 POSIX pthread有很多相似之处。Java 类别库提供的线程类别可以支持丰富的方法集,用以启动、运行或停止线程,并检查线程的状态。
Java 的线程支持包括一套复杂的基于监控和条件变量的同步原语。在语言级别上,类别库或声明为同步的代码块中的方法并不同时运行。这种方法或模块在监控器的控制下运行,有助于确保在这些方法或模块中存取的数据能够始终处于一致状态。所有的 Java 对象都有自己的监控器,通常在第一次使用时由 JVM 展示并激活。监控器的作用非常类似于 pthread 中定义的条件变量对和 mutex。但是与 for pthread 不同的是,Java 线程处于等待状态时可能被打断,例如当它等待事件通知或在 I/O 调用过程中被拦截时就时常会发生这种情况。
在这一简单的范例中,我们展示了如何借助“简单”Java 线程编写并行版 π 程序:
public class PI1 {&&&&&&&&&&&&&&&& static long num_steps = 100000;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& static double sum = 0.0;&&&&&&&&&&&&&&&& static int part_&static class PITask extends Thread {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& int part_&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& double x = 0.0;&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& double sum = 0.0;&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& public PITask(int part_number) {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& this.part_number = part_&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& public void run() {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& for (int i = part_ i & num_ i += part_step) {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& x = (i + 0.5) *&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& sum += 4.0 / (1.0 + x * x);&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&& public static void main(String[] args) {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& step = 1.0 / (double) num_&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& part_step = Runtime.getRuntime().availableProcessors();&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& PITask[] part_sums = new PITask[part_step];&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& for (i = 0; i & part_ i++) {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& (part_sums[i] = new PITask(i)).start();&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& for (i = 0; i & part_ i++) {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& try {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& part_sums[i].join();&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& } catch (InterruptedException e) {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& sum += part_sums[i].&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& pi = step *&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& System.out.println(pi);&&&&&&&&&&&&&&&& }}
在 Java 中启动全新线程时,我们通常会将 Thread 类细分,并定义一个定制的 run() 方法,以保证主要工作能够并行完成。在我们的范例中,此项工作可在 PITask 类的 run() 方法中实施。出于性能原因,整个积分范围被分为 part_step 片断,这些片段的数量等于可用处理器的数量。PITask 对象通过 part_number(代表积分范围中的一个片断)实现参数化;这样,run() 的主体就在选定的积分附属范围内计算部分和。当调用 start() 方法时,实际的线程同时开始启动并并发执行。我们可在面向所有附属范围的循环中进行此项工作。然后,我们运行第二个循环,通过调用其 join() 方法,等待每条衍生线程 (spawned thread) 的完成,并接着对每条线程得出的结果进行总结。在本例中,每个积分范围都明确地映射到一条单独的 Java 线程中。
本例为我们明确创建了 Java 线程,因而,我们不得不将积分范围分成多个部分,以便在线程之间手动进行工作分区。也许人们认为这样做非常繁琐,但是如果不用这种方法,而去创建与积分范围中的步骤数量相同的线程,那么我们将会发现程序的表现实在让人无法接受。这是因为通常情况下,创建 Java 线程是一项非常昂贵的工程。
上文中所提到的“简单”Java 线程只是 Java 多线程支持的最低级别;还有很多更高级别的线程库,旨在增强 Java 多线程功能性的基本水平,并为一些常用任务增添解决方案。从 Java 标准 1.5起便开始提供的 java.util.concurrent 程序包是一个值得我们注意的范例。该程序包包括针对基本 Java 线程的诸多增强特性,如线程池支持、atomic 变量,以及复杂的同步原语等。但是,util.concurrent 程序包的一些片断不符合 J2SE 标准,因而它仍旧只能作为独立程序库使用(称为 EDU.oswego.cs.dl.util.concurrent)。在此之中,最重要的遗失部分就是 FJTask 框架,它针对Java采用了一种 fork-join 并行概念,旨在对定积分或矩阵乘法运算等计算密集型计算实现并行处理。FJTask 是对 Thread简单的直接模拟。它通常是指“基于任务的”并行,而不是“基于线程的”并行。FJTasks 往往在同一个 Java 线程池上执行。它还支持 Thread 等级中许多最为常见的方法,包括 start()、yield() 和 join()。
FJTask 不支持优先控制(priority control)等一些 Java 线程方法。因而,它的主要经济优势就在于它不支持任何类型的拦截操作。没有任何因素能够阻止 FJTask 中的拦截,并且极其短暂的等待/拦截也能执行得非常好。FJTask 并非设计用于支持任意同步,因为一旦开始执行,就没有任何方式能够暂挂和恢复独立任务的执行。在持续运行期间,FJTasks 也应该是有限的,而且不应该包含无限循环。FJTask 应顺利地完成运行,不应要求等待或运行阻拦式 IO。因而FJTask 和 Thread 之间存在着很大的成本差距。至少运行于 JVM 上时,FJTask可以在高性能垃圾回收(所有 FJTask 迅速成为垃圾)和良好的本地线程支持下,比 Thread 快 2 至 3 个数量级。
在以下范例中,我们展示了如何在 FJTask 框架的帮助下编写 PI 程序:
import EDU.oswego.cs.dl.util.concurrent.FJTimport EDU.oswego.cs.dl.util.concurrent.FJTaskRunnerG&public class PI2 {&&&&&&&&&&&&&&&& static int num_steps = 100000;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& static double sum = 0.0;&&&&&&&&&&&&&&&& static int part_&&&&&&&&&&&&&&&& static class PITask extends FJTask {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& int i = 0;&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& double sum = 0.0;&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& public PITask(int i) {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& this.i =&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& public void run() {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& double x = (i + 0.5) *&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& sum += 4.0 / (1.0 + x * x);&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&&& public static void main(String[] args) {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& step = 1.0 / (double) num_&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& try {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& FJTaskRunnerGroup g = new FJTaskRunnerGroup(Runtime.getRuntime()&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& .availableProcessors());&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& PITask[] tasks = new PITask[num_steps];&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& for (i = 0; i & num_ i++) {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& tasks[i] = new PITask(i);&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& g.invoke(new FJTask.Par(tasks));&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& for (i = 0; i & num_ i++) {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& sum += tasks[i].&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& pi = step *&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& System.out.println(pi);&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& System.out.println(Math.PI);&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& } catch (InterruptedException ie) {&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& }&&&&&&&&&&&&&&&& }}
首先,与上一范例中的做法类似,我们为 PITask 类设立一个 run() 方法。但是这样,PITask 仅会计算出对应 i-th 步骤的单一值 x,而不是积分附属范围的部分和。随后,我们创建了 PITask 对象阵列,使用 FJTask.Par 对象进行打包,并通过调用 FJTaskRunnerGroup 对象上的 invoke() 加以执行。使用 FJtask.Par 对象进行打包,可指导框架并行执行线程池上的基本任务阵列(我们已将该线程池的线程数量设置为处理器的数量)。此范例中的 invoke() 方法必须等到完成阵列中的所有任务时才可使用。这就使得我们能够通过每个具体任务中所得出的独立和数值,来立即计算出总和。
请注意:本 Java π 程序小修订版不会明确创建任何线程,也不会在线程与任务之间划分任何的工作分区。然而,大家可能已注意到,即使与上文我们在线程之间进行明确工作分区的范例相比,它的执行情况仍然非常好。这是因为所有全新 FJtask 的创建与异步执行几乎都能像调用一种方法那样快速。但是,为了获得最佳性能,我们仍然建议您为每个 FJTask 对象适当分配大量工作,以确保这些对象数量的可管理性。这将有助于减少 JVM 内垃圾收集器(garbage collector)的压力。
在本文中,我们已经谈及了众多并行编程的常用符号。文中使用的程序非常简单----甚至可能过于简单了。但是,我们希望您能从如此简单的举例中很好地了解所有的并行编程符号。
这些并行编程符号在复杂性、变更时需要的原始串行程序数量、以及共同使用时可能发生的错误等方面各不相同。鉴于您所倾向使用的并行算法类型的特性,建议您将所有这些因素都考虑在内。此外,您还需考虑:
可移植性:您需要支持哪些平台?MPI 如此流行的原因之一就是它在哪里都能够运行。但是如果您只计划支持具有共享地址空间的硬件,那么基于线程的符号也许更加合适。
性能:可管理的运行时和高级运行时环境大大减轻了程序员的负担。由于创建和维护软件需要较高成本,因此这些优势对您来说非常重要。但是这些优势也是需要成本的。由于低级 API (如 windows 线程、Pthreads 或 MPI)中的硬件直接面向程序员,所以需要更细致的优化。如果所有可用的内核都需要扩充,那么这样的优化就非常重要。
串行与并行产品发布:软件拥有较长的使用周期。成功的软件开发人员所支持的产品都会拥有较长的使用期限。因此,将软件的串行和并行版本保存在一个源代码树中就会显得十分重要。如果并行编程符号需要对软件进行大量改写以支持并行,那么这就很难做到。
熟悉程度:学习一门新的语言是非常困难的。此外,当多位开发人员学习一门不熟悉的语言时,成本也可能会非常昂贵。因此,如果并行符号是一种熟悉的串行语言的扩展,意义就会非常重大。
测试:软件产品必须进行广泛的测试。在专业开发环境中,测试的成本很容易在最初便超过创建软件的成本。这就是递增策略在并行编程(通常使用 OpenMP)中的优势所在。借助递增并行,每当添加一个结构时,开发人员便可测试结果,并确保结果仍然与最初的串行代码保持一致。
[omp] OpenMP 应用编程接口 2.5 版,&,2005 年
[Mattson05] Timothy G. Mattson、Beverly A. Sanders、Berna L. Massingill,《并行编程模式》,Addison Wesley 出版社,2005 年
[mpi] William Gropp、Ewing Lusk、Anthony Skjellum,“使用 MPI”,麻省理工学院出版社,1994 年
[JLS] Java 语言规范,&
[JVMS] Java 虚拟机规范,&
[JavaAPI] java.lang.Thread 规范,&
[JSR166] Doug Lea 的 util.concurrent Fork-Join 任务框架,&
Tim Mattson 是一位并行程序员。在过去 20 年中,他曾利用并行计算机来制造化学反应、重新组合蛋白质、寻找石油、分析基因,以及解决众多其它科学问题。Tim 还决心在当前应用编程人员大都编写并行软件之时,开发一种比较少见的串行软件。多年来,他始终坚信寻找正确的并行编程环境是解决问题的关键所在。他尝试了无数种并行编程环境,并自行创建了一些新的环境(包括 OpenMP)。当这种方法被证实并没有他所期望的那样有效时,他迅速转换方式,并决定在人们被语言和软件工具困扰之前,帮助我们意识到需要了解专业程序员对并行编程的思考方式。为了解决这个问题,Tim 花了五年多的时间,与他人合作,共同为并行编程开发设计模式语言(《并行编程模式》,Addison Wesley 出版社,2004 年)。Tim 目前效力于英特尔,并在英特尔企业技术事业部的应用研究实验室中继续进行着并行应用编程问题的研究。
Andrey Y Chernyshev 是英特尔公司企业解决方案软件部门的一名软件工程师。他目前在俄罗斯工作,联系方式为:。
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
#include &stdio .h&
#include &gsl /gsl_sf_bessel.h&
int main (void)
double x = 5.0;
double y = gsl_sf_bessel_J0(x);
printf ("J0(%g) = %.18en", x, y);
gcc sample.c -o sample -lgsl
运行输出:
J0(5) = e-01
有兴趣的可以详细阅读一下。
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
谈到数学,并不缺乏相关的计算机软件包。数学是程序员和分析人员的流行主题,从而导致了大量不同的工具可供选择。
数学是科学之王。数学工作站的商业使用群体非常庞大:从基础工程到设计创意,从基因治疗到天体导航,数学统治着整个世界。帮助人们解决他们所选领域的数学问题的计算机程序并不缺乏。本文的剩余部分将描述几个商业和开放源代码系统,它们在您所从事的领域可能证明是有价值的。所描述的所有系统都有专用于它们的网页,并包括在本文的部分。
UNIX 工作站中存在的最常用数学工具以计算器和更高级的电子表格程序(如 GNU Calc)的形式出现。算术规则非常简单,所有这些工具都忠实地遵守它们。几乎每个全功能的窗口管理工具包都至少突出体现了一个计算器,有时还有多个版本的计算器。
GNU 项目还提供了可供下载的计算工具。其中包括 GNU Calc、GNU Plot、ATLAS、Choose 和 4,000 多个其他面向数学的程序。请参见部分以获得指向完整列表的链接。
正如您在学校中级课程中所学习到的,数学所涉及的远不只是对数字的简单运算。数字占位符的符号表示形式——诸如 x、y 和 z 等变量、笛卡儿坐标、因素分解原理以及积分和微分世界——是一个丰富和多样性领域的一部分,该领域要求提供同样丰富和多样性的计算机资源。为这些任务而设计的程序领域的最常用名称是“计算机代数系统”(Computer Algebraic Systems,CAS)。同时存在商业和开放源代码 CAS 程序可供使用,下面将描述其中一些最常用程序。
但是,能够操作符号在大多数情况下还不足够。与原始坐标点值清单所能提供的信息相比,图形可以告诉我们更多信息。可用于计算机代数的大多数系统还突出体现了某种绘图机制,允许您可视化所标绘的数据。
可以使用许多计算机程序来帮助您解决数字和符号数学问题,但是还有些程序可以帮助您学习数学。MetaMath 系列程序就是一个理想的起点(请参见部分)。对于初学者或通常认为自己无意从事该领域的读者,本文描述的数学和程序也是引人入胜的。只需下载和试验其中一些程序就可能会激发您成为某方面的专家,从而比任何计算机游戏都更能充实您的生活。
下面是一些最常用商业 CAS 系统的简要概述。如果您对其中任何系统感兴趣,可以通过浏览它们的网站来找到更多信息,部分列出了所有这些网站。
Derive。Software Warehouse 是创建旨在运行于早期工作站计算机上的数学软件的最早先行者之一。该公司设立于 1979,当时 PC 革命刚起步,计算机代数系统还仅在使用分时终端的大型计算机系统上可用,该公司设计了一款名为 muMATH 的程序,以便 PC 用户能够超越简单的计算器,从而在有限的小型计算机硬件上处理符号数学问题。后来 muMATH 系统停止了使用,并被远远更高级的 Derive 系统所取代。Derive 是使用 Lisp 语言编写的,Lisp 是一种公共编程语言,尤其适合基于规则的处理,该处理是将一个抽象数学表达式变换为另一个表达式所必需的。
Fermat。这个专有共享软件计算机代数系统是为纪念已故最著名数学家之一 Pierre de Fermat 而命名的。它在各种各样的计算机系统上运行,尤其擅长于涉及任意长度整数和小数、图、矩阵和多项式代数的算术。Fermat 运行得非常快。据该公司的广告宣称,如果您需要计算 Q 上的 400 x 400 矩阵的特征多项式,那么您就需要 Fermat。
Maple。 Maple 数学软件包由 Waterloo Maple Inc. (Maplesoft) 开发并销售,最初由加拿大安大略省沃特卢的沃特卢大学的 Symbolic Computation Group 于 1981 年创建。Maple 是一款给人印象深刻的软件包,尤其擅长于三维绘图和以课本形式显示数学公式。它在一个动态工具中组合了界面和编程语言,可同时用于数字和符号问题的解答。许多大学已将 Maple 作为教授数学概念的标准工具;该公司提供此软件的学生版和专业版。
MathCAD。 PTC 是一家工程设施公司,于 2006 年收购了 MathCAD 的股权。MathCAD 在简单性方面与 Maple 类似,具有允许工程师在屏幕上输入和显示各种公式以及图形和文本的界面。MathCAD 组合了一个庞大的功能库,包括微积分、拉普拉斯变换、贝塞尔函数、统计和财务函数。
Mathematica。 Stephen Wolfram 的公司 Wolfram Research 于 20 世纪 80 年代晚期开发了 Mathematica,并重点强调了该工具的编程方面。它提供了过程式和函数式编程的灵活组合,并引入了自动重新编写公式的非确定性方法。它是一个强大的软件包,带有一个大型的多样性功能库,并提供了解决问题的独特多范例方法。
Reduce。 Reduce 是另一个通用计算机代数系统,是由许多科学家以协作方式开发完成的。该系统于 20 世纪 60 年代由 Anthony Hearn 创建。Reduce 现已成为科学界许多人的最爱。它以收取成本回收费用的方式进行分发,并且分发版中通常包括源代码。
本部分研究开放源代码的世界。下面是一些最常用的开放源代码 CAS 系统的简要概述,这些系统的源代码在常用开放源代码许可证之一下授予许可。如果您对其中任何系统感兴趣,可以通过浏览它们的网站来找到更多信息,部分列出了所有这些网站。
Axiom。这个称为 Axiom 的开放源代码计算机代数系统是在修改后的 Berkeley Software Distribution (BSD) 许可证下发布的,它对于探索不同的数学算法非常有用。其突出特性是强类型的数学对象层次结构和对对象进行分组和控制的常用编程数据结构。Axiom 是使用自定义 A# 编程语言来编写的,对于有意探索算法设计的人来说是值得的工具。
CoCoA。 Computations in Commutative Algebra (CoCoA) 是另一个免费计算机代数系统,用于处理超大型整数、有理数和多项式。它为自定义 C++ 程序提供了有用的数学功能库。
Dcas。计算机代数领域几乎为数学研究的每个方面提供了丰富多彩的方法。存在许多在程序中表示数学对象的方法,而 Martin Johansen 的 Dcas 系统则证明了这些方法可以如何的千差万别。Dcas 突出体现了一种使用标识作为规则来操作代数表达式的方法。Dcas 非常值得一试;您可能会发现它是处理您所在领域问题的理想方法。
DoCon。 称作 Haskell 的函数式编程语言展示了一个用于符号数学的程序,名为 DoCon。按照该公司网站(请参见部分)上的文档,DoCon 实现了线性代数、多项式最大公约数、因素分解、Grobner 基,并支持域上的结构(constructions on domains)——分数、多项式、留数环,等等。它是开放源代码的,可能就是用于您正打算要编写的应用程序的恰当工具。
Eigenmath。Eigenmath 是由 George 使用 C 语言来编写的,是一个简单易用的计算机代数系统。由于有源代码可用,它成了刚开始探索计算机代数系统的学生的有用工具。
GiNaC。 GiNaC 与大多数其他计算机代数系统不同,它没有提供用于输入表达式的图形用户界面 (GUI),而是选择让用户以本机 C++(其实现语言)来输入表达式。它使用运算符重载这种本机 C++ 面向对象技术来实现代数语法。在这个由许多陌生名称所主宰的领域,它还具有一个比较陌生的名称!
Jscience。Jscience 软件包是一个强大的基于 Java的物理和数学函数库,它通过提供单个用于所有开发的体系结构,旨在帮助跨不同科学领域构建协同作用。至少可以这样说,这是个非常崇高的目标,但是也不排除该公司能够实现该目标。
Macaulay。Macaulay 计算机代数系统对于多项式计算非常有用,并重点强调 Grobner 基计算。它旨在解决具有简单语法并且已描述为代数机器语言 (algebraic machine language) 的问题。
Magma. Magma 在成本回收许可证下进行分发,是一个旨在解决代数问题的高性能系统。它突出体现了用于群论的功能以及群数据库、用于整数和多项式算术的渐近快速算法和几个用于高级运算的前沿库。
Mathomatic。此程序没有内置的编程功能,旨在用作简单的符号数学计算器。它可以在任何系统上使用 C 编译器、标准 C 库和 UNIX make 实用程序进行编译。
Maxima。与大多数计算机代数系统一样,Maxima 是使用 Lisp 语言编写的。Maxima 基于商业软件包 Macsyma,并包括一种完整的 ALGOL 风格的编程语言,对于教授计算机代数方面的编程概念最有用。它提供了任意精度的算术,使得整数和有理数的大小仅受到系统可用内存的限制。
PARI/GP。PARI 是快速运行的符号函数 C 语言库,用于因素分解、代数数论、椭圆曲线、矩阵和超越函数。GP 是交互式的 Shell,用于提供对 PARI 函数的访问。通过使用 gp2c 编译器,可以为问题域创建快速运行的程序。
SAGE。Software for Algebra and Geometry Experimentation (SAGE) 是使用 Python 语言编写的,并使用交互式的 Python Shell 作为其用户界面。SAGE 的独特之处在于,它能够用作其他各种计算机代数系统的集成器,从而允许用户利用不同软件包的各自强项。
SINGULAR。对于交换代数、代数几何和奇点理论,SINGULAR 计算机代数系统在软件包内核以及共享库中提供了大量的算法。它还包括详尽的文档。SINGULAR 是个值得一试的系统,如果您对奇点理论感兴趣则尤其如此。
Yacas。Yet Another Computer Algebra System (Yacas) 具有漂亮的用户界面和开放源代码软件的所有其他强项。该系统的输入可以是 ASCII 或 OpenMath;该程序还具有批处理模式。
数学是科学之王,帮助解决您在特定领域所面对的数学问题的工具并不缺乏。无论您是处理保险单定价的保险精算师,还是确定飞向冥王星的宇宙飞船最优路线的天体导航员,都有相关程序可帮助回答您的问题。
学习您可以参阅本文在 developerWorks 全球站点上的
。检查以下数学资源:
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
Sun[tm] Studio:Fortran 预处理程序 fpp 自述文件
Fortran 编程人员经常需要维护一种以上的代码版本,或者在各种环境下运行代码。对于编程人员来说,最容易的解决办法是保留单个源文件,而所有代码变更均相互穿插在该文件中,这样就可以轻松地提取出任何版本。这样对应用到所有版本的修改只需执行一次即可。
源代码预处理程序很早以前就被用于提供这些功能。它们允许用户在源代码中插入指令语句来影响预处理程序的输出。
通常,这些特殊指令可能作为编译器的注释行出现。一般情况下,源代码预处理程序允许用户定义特殊变量和逻辑构造来有条件地控制文件中哪些源代码行将被传送给编译器,哪些行将被略过。此外,预处理程序的源代码行编辑功能允许用户根据所定义的字符串变量的值来指定如何更改源代码。
在历史上,曾经将标准 C 编译器的源代码预处理程序 cpp 用于向 Fortran 编程人员提供这些功能。然而,cpp 受到 C 语言语法和源代码行格式的紧密束缚,以致于没有详细审查就不能使用。建议的 Fortran 预处理程序 fpp 将提供特定于 Fortran 的源代码功能(C 编程人员曾经期望在 UNIX 环境下实现这些功能)。
fpp 的某些功能可能会重新应用到其他工具中。fpp 的一个实现必须解析和分析 Fortran 源代码、对 Fortran 表达式求值并生成 Fortran 输出。如果其他工具需要提供一个增强型 Fortran 程序开发环境,这些功能将是这些工具的构件。
以下部分更详细地描述了 fpp 的功能。有关完整的详细信息,请参见
fpp 输入源代码
fpp 输入源代码是散布着预处理程序语句的标准 Fortran 代码。所有预处理程序语句均以一个特殊字符 # 开头。它只能在一行的第一个字符位置出现。# 后必须紧跟预处理程序命令的文本。
预处理程序指令可能出现在 Fortran 注释内。预处理程序也会修改 Fortran 注释内的文本。
预处理程序能够对固定格式和自由格式的源代码进行操作。固定格式的源文件以 ".F" 为扩展名,而自由格式的源文件以 ".F90" 为扩展名。请注意,固定格式文件命名约定适用于 FORTRAN 77 和 Fortran 90。在调用适当的编译器之前,会首先预处理带有此类扩展名的文件。
如果宏展开或者字符串替换导致一行中列的宽度超过 72 列(固定格式)或者 132 列(自由格式),预处理程序将生成适当的续行。
预处理程序变量
预处理程序变量由指令定义并具有一个字符串值。fpp 将在源代码中任何适合替换的地方用变量的值来替换所出现的变量。变量也会出现在条件指令中,控制传送给输出的源代码行的选择。
#define name string
将变量 "name" 的值定义为 "string","name" 标记的每个实例 [超出 IMPLICIT 语句边界,FORMAT 语句和串文字中的文本] 的结果被 "string" 照字面意义替换。
注意:如果是 IMPLICIT 语句边界和 FORMAT 语句中的文本,则宏只有在与该范围内的有效文字相冲突的情况下才会扩展。
还允许使用参数执行类似于“宏”的内联替换:
#define name (arg1 [,arg2]...) string-with-args-inserted
#define SOLARIS_2 .TRUE.#define CONVERT(TO_FARENHEIT) ((TO_FARENHEIT*9)/5)+32
预处理程序变量在从定义点到编译单元结束的整个范围内有效。(即文件的全局范围)
可以使用以下内容通过显式方式取消预处理程序变量的定义
#undef name
Fortran 预处理程序接受 /* 与 */ 之间包含的 C 样式注释。
源代码的条件选择
这是在 cpp 条件代码构造基础上进行建模的。
#if condition1block1[#elif condition2block2 ]...[#elseblockn ]#endif
#ifdef nameblock#endif#ifndef nameblock#endif
这些条件是包含预处理程序变量(由 #define 语句指定)的 fpp 表达式。请注意,条件可在圆括号内指定。这些 fpp 表达式是 cpp 表达式的超集,它们可以计算 Fortran 逻辑型。因此,".TRUE." 在 fpp 表达式中是有效的。预处理程序会评估这些条件以获得一个真或者假的结果。这些表达式无法计算浮点值或者包含内在函数。
#define SOLARIS_2 .TRUE.#if (SOLARIS_2)CALL solaris_2 (X,Y,Z)#elseCALL solaris_1 (X,Y,Z)#endif
有时,在一个文件中收集预处理程序变量是很方便的。可使用以下语句来实现
#include filename
允许嵌套 #include。#include 语句不能出现在续行中(即包含文件中的第一个语句不能是续行)。
预处理程序内在函数
defined(name) 提供了一个名称的定义状态。根据该名称是否要定义来决定返回的是 TRUE 还是 FALSE。这通常在 #if 语句中使用:
#if defined(BIG_MODEL)
命令行选项
fpp 通常从 Fortran 编译器驱动程序中调用。fpp 也可以独立调用。以下是 fpp 接受的一些选项:
将 name 定义为 1。这与 fpp 当前正在处理的源文件中出现的 #define name1 这一行相同。
-Dname=def
这与 fpp 当前正在处理的源文件中出现的 #define name def 这一行相同。
-Idirectory&
对于名称不是以 "/" 开头的 #include 文件,将 directory 插入搜索路径中。directory 被插入在 "include" 目录标准列表的前面。因此,对于其名称用双引号 (") 括起来的 #include 文件,首先在包含 #include 行的文件的目录中搜索,然后在以 -I 选项命名的目录中搜索,最后在标准列表内的目录中搜索。
删除 name 的所有初始定义,其中 name 由预处理程序进行预定义。
-Ydirectory&
搜索 #include 文件时使用 directory 来替换目录的标准列表。
有关使用 fpp 的详细信息,请参见
手册页。fpp 源代码可以从 NetLib () 下载。
已知的限制
第 1-6 列中的宏:fpp 不会扩展出现在固定格式文件(例如,带 .F 扩展名的文件)的第 1 至第 6 列中的宏。fpp 会在固定格式中遵循 Fortran 规则,并且预计不会在某一行的前 6 列或第 72 列之后发现任何语句。唯一的解决方法是使用自由格式(.F90 或 .F95 文件)。如果无法实现这一点,请使用 cpp,它将扩展前 6 列中的宏。(4315099)
& 2007 Sun Microsystems, Inc. 保留所有权利。必须依据许可证条款使用。
[22:55] [发表评论]
[22:55] []
[22:55] [发表评论]
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
[22:55] []
&&& 1 2 3 4
5 6 7 8 91011
121415161718
19202122232425
2627282930&&

我要回帖

更多关于 怎么删除卸载残留 的文章

 

随机推荐