简单线程不能完全并行执行
Simple threads don't execute exactly parallely
我正在启动十个线程来更新十个进度条。
完成这些条应该需要五秒钟。
当只启动一个线程时,它会在五秒内完成。
当启动所有 10 个线程时,它们会异步更新并在超过 5 秒的时间内完成。
是否有可能以更有效的方式完成此任务,以便所有十个进度条在五秒钟内完成?
这里是 MainActivity.java
package com.google.example;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
ArrayList<ProgressBar> bars = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for(int i = 0; i < 10; i++){
int id = getResources().getIdentifier("b" + (i+1), "id", getPackageName());
bars.add((ProgressBar) findViewById(id));
}
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int num = 10;
for(int i = 0; i < num; i++){
MyRunnable runnable = new MyRunnable(bars.get(i));
Thread thread = new Thread(runnable);
thread.start();
}
}
});
}
class MyRunnable implements Runnable{
ProgressBar bar;
MyRunnable(ProgressBar b){
bar = b;
}
@Override
public void run() {
int progress = 0;
long previous = 0;
while(progress < 100) {
long time = System.currentTimeMillis();
if (time != previous && time % 50 == 0) {
progress++;
bar.setProgress(progress);
previous = time;
}
}
}
}
}
这里是activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start"/>
<ProgressBar
android:id="@+id/b1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b5"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b7"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b8"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b9"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b10"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
</LinearLayout>
这是仅启动一个线程时的输出
这是一起开始的时候
编辑:按照@greeble31 的建议,我通过将 MyRunnable 更改为
解决了这个问题
class MyRunnable implements Runnable{
ProgressBar bar;
MyRunnable(ProgressBar b){
bar = b;
}
@Override
public void run() {
int progress = 0;
long hack = System.currentTimeMillis();
while(progress < 100) {
progress = (int) ((System.currentTimeMillis() - hack) / 50);
bar.setProgress(progress);
}
}
}
这是现在的样子。
由于 1 个线程需要 5 秒,而且我怀疑您的设备是否有 10 个内核(可能是 4 个中的 2 个,因为这看起来是平板电脑),您无法在 5 秒内完成所有 10 个。如果你做的线程/条数与你的内核数一样多,它应该会在大约 5 秒内完成。如果您使用的线程多于内核,那么线程肯定必须共享内核才能完成工作,并且会花费更长的时间(但仍然比同步少得多)。即使将您的线程数与内核相匹配,也不能保证您可以不间断地访问您的应用程序的所有内核——仍然有一个完整的 OS 和正在进行的后台任务需要 运行。
由于 1 个线程需要 5 秒,因此 10 个同步线程需要 50 秒。 10 个线程 6-7 秒已经很不错了。
正如@DavisHerring 在评论中提到的,这(几乎完全)是由于 MyRunnable.run()
中错过了毫秒。您的代码假定循环将从 System.currentTimeMillis()
.
"see" 完美顺序 return 值
您可能已经推断,由于循环全速运行,它无疑能够每毫秒调用 System.currentTimeMillis()
多次,因此不太可能错过任何一次。但是,您拥有的线程多于核心,因此显然某些线程必须花费部分时间进行抢占。
此外,在任何抢占式多任务操作系统上,无法保证任何特定线程将在任何特定时间执行。
因此,如果给定线程暂时停止执行,它很容易错过 50 的倍数。例如,其中一个线程可能 "see" 以下 return 来自 System.currentTimeMillis()
:
1546215544594
1546215544594
1546215544594
1546215544594 <-- thread is unscheduled here, causing a 23ms gap
1546215544617
1546215544617
1546215544617
1546215544617
建议修复
与其试图抓住每一毫,不如在开始时花点时间。然后让 progress
等于 (elapsed time) / 50:
progress = (int) ((System.currentTimeMillis() - hack) / 50);
bar.setProgress(progress);
顺便说一句:System.currentTimeMillis()
不能保证 return 单调递增的结果(参见 System.uptimeTimeMillis()
替代方案),但对于您的特定问题,这可能没有那么大一个问题。
我正在启动十个线程来更新十个进度条。
完成这些条应该需要五秒钟。
当只启动一个线程时,它会在五秒内完成。
当启动所有 10 个线程时,它们会异步更新并在超过 5 秒的时间内完成。
是否有可能以更有效的方式完成此任务,以便所有十个进度条在五秒钟内完成?
这里是 MainActivity.java
package com.google.example;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
ArrayList<ProgressBar> bars = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for(int i = 0; i < 10; i++){
int id = getResources().getIdentifier("b" + (i+1), "id", getPackageName());
bars.add((ProgressBar) findViewById(id));
}
Button button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
int num = 10;
for(int i = 0; i < num; i++){
MyRunnable runnable = new MyRunnable(bars.get(i));
Thread thread = new Thread(runnable);
thread.start();
}
}
});
}
class MyRunnable implements Runnable{
ProgressBar bar;
MyRunnable(ProgressBar b){
bar = b;
}
@Override
public void run() {
int progress = 0;
long previous = 0;
while(progress < 100) {
long time = System.currentTimeMillis();
if (time != previous && time % 50 == 0) {
progress++;
bar.setProgress(progress);
previous = time;
}
}
}
}
}
这里是activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Start"/>
<ProgressBar
android:id="@+id/b1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b5"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b6"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b7"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b8"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b9"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
<ProgressBar
android:id="@+id/b10"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleHorizontal"/>
</LinearLayout>
这是仅启动一个线程时的输出
这是一起开始的时候
编辑:按照@greeble31 的建议,我通过将 MyRunnable 更改为
解决了这个问题class MyRunnable implements Runnable{
ProgressBar bar;
MyRunnable(ProgressBar b){
bar = b;
}
@Override
public void run() {
int progress = 0;
long hack = System.currentTimeMillis();
while(progress < 100) {
progress = (int) ((System.currentTimeMillis() - hack) / 50);
bar.setProgress(progress);
}
}
}
这是现在的样子。
由于 1 个线程需要 5 秒,而且我怀疑您的设备是否有 10 个内核(可能是 4 个中的 2 个,因为这看起来是平板电脑),您无法在 5 秒内完成所有 10 个。如果你做的线程/条数与你的内核数一样多,它应该会在大约 5 秒内完成。如果您使用的线程多于内核,那么线程肯定必须共享内核才能完成工作,并且会花费更长的时间(但仍然比同步少得多)。即使将您的线程数与内核相匹配,也不能保证您可以不间断地访问您的应用程序的所有内核——仍然有一个完整的 OS 和正在进行的后台任务需要 运行。
由于 1 个线程需要 5 秒,因此 10 个同步线程需要 50 秒。 10 个线程 6-7 秒已经很不错了。
正如@DavisHerring 在评论中提到的,这(几乎完全)是由于 MyRunnable.run()
中错过了毫秒。您的代码假定循环将从 System.currentTimeMillis()
.
您可能已经推断,由于循环全速运行,它无疑能够每毫秒调用 System.currentTimeMillis()
多次,因此不太可能错过任何一次。但是,您拥有的线程多于核心,因此显然某些线程必须花费部分时间进行抢占。
此外,在任何抢占式多任务操作系统上,无法保证任何特定线程将在任何特定时间执行。
因此,如果给定线程暂时停止执行,它很容易错过 50 的倍数。例如,其中一个线程可能 "see" 以下 return 来自 System.currentTimeMillis()
:
1546215544594
1546215544594
1546215544594
1546215544594 <-- thread is unscheduled here, causing a 23ms gap
1546215544617
1546215544617
1546215544617
1546215544617
建议修复
与其试图抓住每一毫,不如在开始时花点时间。然后让 progress
等于 (elapsed time) / 50:
progress = (int) ((System.currentTimeMillis() - hack) / 50);
bar.setProgress(progress);
顺便说一句:System.currentTimeMillis()
不能保证 return 单调递增的结果(参见 System.uptimeTimeMillis()
替代方案),但对于您的特定问题,这可能没有那么大一个问题。