因此,当处理线程中的任务是微不足道的时候,创建线程的成本会比分配任务创造更多的开销.这是一个单线程比多线程快的情况.
问题
>有更多的情况下单线程将比多线程快吗?
>我们什么时候决定放弃多线程,只使用一个线程来完成我们的目标?
解决方法
>如果您的cpu有多个核心可用,多线程可能会让您并行执行此操作.所以在一个理想的世界,例如如果您的cpu有4个核心可用,并且您的算法工作真正并行,那么使用4个线程可能会增加一些素数的4倍.
>如果您启动更多的线程作为核心可用,您的操作系统的线程管理将花费越来越多的时间在线程交换机,并以您的效率,使用您的cpu变得更糟.
>如果编译器,cpu缓存和/或运行时实现了您运行多个线程,则访问内存中的相同数据区域将以不同的优化模式运行:只要编译/运行时才确定只有一个线程访问数据,可以避免将数据经常写入外部RAM,并可能有效地使用cpu的L1缓存.如果没有:必须激活信号量,并且更频繁地将缓存的数据从L1 / L2缓存刷新到RAM.
所以我从高度parrallel多线程中学到的经验是:
>如果可能,使用单线程,无共享进程来提高效率
>如果需要线程,尽可能地解耦共享数据访问
>如果可能,不要尝试分配比可用核心更多的加载工作线程
这里有一个小程序(javafx)来玩.它:
>分配一个100.000.000大小的字节数组,填充随机字节
>提供一个方法,计数在这个数组中设置的位数
>该方法允许计数每个第n个字节位
count(0,1)将计数所有字节位
> count(0,4)将计数0′,4′,8’字节位,允许并行交错计数
使用MacPro(4核)导致:
>运行一个线程,count(0,1)需要1326ms计数所有399993625位
>运行两个线程,并行计数(0,2)和计数(1,2)需要920ms
>运行四个线程,需要618ms
>运行八个线程,需要631ms
改变计数方式,例如增加一个共同的整数(AtomicInteger或synchronized)会大大改变许多线程的性能.
public class MulithreadingEffects extends Application { static class ParallelProgressBar extends ProgressBar { AtomicInteger myDoneCount = new AtomicInteger(); int myTotalCount; Timeline myWhatcher = new Timeline(new KeyFrame(Duration.millis(10),e -> update())); BooleanProperty running = new SimpleBooleanProperty(false); public void update() { setProgress(1.0*myDoneCount.get()/myTotalCount); if (myDoneCount.get() >= myTotalCount) { myWhatcher.stop(); myTotalCount = 0; running.set(false); } } public boolean isRunning() { return myTotalCount > 0; } public BooleanProperty runningProperty() { return running; } public void start(int totalCount) { myDoneCount.set(0); myTotalCount = totalCount; setProgress(0.0); myWhatcher.setCycleCount(Timeline.INDEFINITE); myWhatcher.play(); running.set(true); } public void add(int n) { myDoneCount.addAndGet(n); } } int mySize = 100000000; byte[] inData = new byte[mySize]; ParallelProgressBar globalProgressBar = new ParallelProgressBar(); BooleanProperty iamReady = new SimpleBooleanProperty(false); AtomicInteger myCounter = new AtomicInteger(0); void count(int start,int step) { new Thread(""+start){ public void run() { int count = 0; int loops = 0; for (int i = start; i < mySize; i+=step) { for (int m = 0x80; m > 0; m >>=1) { if ((inData[i] & m) > 0) count++; } if (loops++ > 99) { globalProgressBar.add(loops); loops = 0; } } myCounter.addAndGet(count); globalProgressBar.add(loops); } }.start(); } void pcount(Label result,int n) { result.setText("("+n+")"); globalProgressBar.start(mySize); long start = System.currentTimeMillis(); myCounter.set(0); globalProgressBar.runningProperty().addListener((p,o,v) -> { if (!v) { long ms = System.currentTimeMillis()-start; result.setText(""+ms+" ms ("+myCounter.get()+")"); } }); for (int t = 0; t < n; t++) count(t,n); } void testParallel(VBox Box) { HBox hBox = new HBox(); Label result = new Label("-"); for (int i : new int[]{1,2,4,8}) { Button run = new Button(""+i); run.setOnAction( e -> { if (globalProgressBar.isRunning()) return; pcount(result,i); }); hBox.getChildren().add(run); } hBox.getChildren().addAll(result); Box.getChildren().addAll(globalProgressBar,hBox); } @Override public void start(Stage primaryStage) throws Exception { primaryStage.setTitle("ProgressBar's"); globalProgressBar.start(mySize); new Thread("Prepare"){ public void run() { iamReady.set(false); Random random = new Random(); random.setSeed(4711); for (int i = 0; i < mySize; i++) { inData[i] = (byte)random.nextInt(256); globalProgressBar.add(1); } iamReady.set(true); } }.start(); VBox Box = new VBox(); Scene scene = new Scene(Box,400,80,Color.WHITE); primaryStage.setScene(scene); testParallel(Box); GUIHelper.allowImageDrag(Box); primaryStage.show(); } public static void main(String[] args) { launch(args); } }