简单说明
除了“数据挖掘与机器学习”,学院还开了“分布与并行计算”这门课,同样也是要求我们计算机1、2班的同学都选修这门课。
采用并行的方式计算π,是这门课的第二个实验内容,看起来很简单,实际上也很简单,目的是分别记录单线程与多线程计算出π的时间,由时间的差别体验一下并行计算的威力。
描述
求π有个公式:
π/4 ≈ 1 - 1/3 + 1/5 - 1/7 + 1/9 - 1/11 + ….
如果是单线程计算,当然是逐项加上去,而采用多线程来计算,就要考虑并行计算的并发性,这里应是数据分解。
假设采用4线程计算,那么有
Thread-0计算第1,5,9。。。项,
Thread-1计算第2,6,10。。。项,
Thread-2计算第3,7,11。。。项,
Thread-3计算第4,8,12。。。项,
直到各自计算达到相应的精度停止,最后将多个线程计算得的结果相加算出最终结果。
Java代码如下:
//CalcThread.java
/**
* 独立的计算线程
*/
public class CalcThread extends Thread {
private double result; //记录当前线程计算的结果
private double from; //开始计算的项
private double interval; //项数间隔
private double accuracy; //计算的精度
private double sign; //符号
public CalcThread(double from, double interval, double accuracy) {
this.from = from;
this.interval = interval * 2;
this.accuracy = accuracy;
result = 0;
sign = (from - 1) / 2 % 2 == 0 ? 1 : -1;
}
@Override
public void run() {
double i = sign / from;
while(Math.abs(i) >= accuracy){ //精度控制
result += i;
from += interval;
sign = (from - 1) / 2 % 2 == 0 ? 1 : -1;
i = sign / from;
}
}
/**
* 取出该线程计算的结果
* @return
*/
public double getResult() {
return result;
}
}
//Main.java
public class Main {
public static void main(String[] args) {
double PI;
long t1;
long t2;
System.out.println("单线程");
t1 = System.currentTimeMillis(); //记录开始时间
PI = createThread(1, 1e-9);
System.out.println("PI = " + PI);
t2 = System.currentTimeMillis(); //记录结束时间
System.out.println("单线程时间: " + (1.0 * (t2 - t1) / 1000 + "秒"));
System.out.println();
System.out.println("2线程");
t1 = System.currentTimeMillis();
PI = createThread(2, 1e-9);
System.out.println("PI = " + PI);
t2 = System.currentTimeMillis();
System.out.println("2线程时间: " + (1.0 * (t2 - t1) / 1000 + "秒"));
System.out.println();
System.out.println("4线程");
t1 = System.currentTimeMillis();
PI = createThread(4, 1e-9);
System.out.println("PI = " + PI);
t2 = System.currentTimeMillis();
System.out.println("4线程时间: " + (1.0 * (t2 - t1) / 1000) + "秒");
}
private static double createThread(int threadCount, double accuracy) {
double PI = 0;
CalcThread[] threads = new CalcThread[threadCount];
//准备线程
for (int i = 0; i < threadCount; i++) {
threads[i] = new CalcThread(2 * i + 1, threadCount, accuracy);
}
//启动线程
for (int i = 0; i < threadCount; i++) {
threads[i].start();
}
try {
for (int i = 0; i < threadCount; i++) {
//等待多线程计算,获取计算结果
threads[i].join();
PI += threads[i].getResult();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
PI *= 4;
return PI;
}
}
计算结果
精度 = 1e-6 :
单线程
PI = 3.141590653589692
单线程时间: 0.028秒2线程
PI = 3.1415906535890485
2线程时间: 0.017秒4线程
PI = 3.141590653590204
4线程时间: 0.008秒
精度 = 1e-7 :
单线程
PI = 3.1415924535897797
单线程时间: 0.215秒2线程
PI = 3.141592453587034
2线程时间: 0.128秒4线程
PI = 3.14159245359098
4线程时间: 0.082秒
精度 = 1e-8 :
单线程
PI = 3.1415926335902506
单线程时间: 2.268秒2线程
PI = 3.1415926335840005
2线程时间: 1.377秒4线程
PI = 3.141592633596291
4线程时间: 0.838秒
精度 = 1e-9 :
单线程
PI = 3.141592651589258
单线程时间: 22.815秒2线程
PI = 3.1415926515851744
2线程时间: 13.034秒4线程
PI = 3.1415926515922354
4线程时间: 8.086秒
相关问题
计算出来的π的结果不一样
由于各个线程停止计算的控制都由同一精度来控制,所以会由线程数不同,导致所相加的公式中的总项数不同。
用不同线程数计算所消耗的时间差别不明显,甚至多线程耗时更多了
同学之间讨论的大致有两种情况:
一是在主线程用循环来等待各个线程计算结束。这种方式会在主线程白白消耗CPU资源来做判断,总耗时显然会更多。
二是在线程之间用共享的变量来计算结果,然后加线程锁。这种方式相当于让多线程串行执行了,这样可能反而增加了系统在调度线程的消耗,也可能让耗时增多。
这一问题还有一种情况是,老CPU只支持单线程执行,或者在单核单线程的CPU的虚拟机上跑,这样就跟上述第二中情况一样,相当于多线程串行执行,系统在调度线程上消耗了更多资源,总耗时增多。