康威的生命游戏,每个细胞都是一根线
Conway's game of life with each cell being a thread
康威的生命游戏,每个细胞都是一个线程
大家好,
所以就像标题所说的那样,我必须制作一个程序来实现 Conway 的生命游戏中的线程,其中每个死细胞或活细胞都是一个线程。
我的第一个目标是简单地让游戏正常运行,我做到了(非常有趣的挑战)
所以我可以打印 20x20 网格,每个单元格都初始化为随机数 1 或 0,其中 1 是活的,0 是死的。
现在,我一直在观看关于如何使用线程的视频,但我仍然不确定我应该如何为每个单元实现它......
我有 3 个 classes,Main、Cell 和 CellRules
我的 Cell class 看起来像:
public class Cell implements Runnable
{
public static int myCount = 0;
private String name;
private int neighbors;
private int alive; // 0 is dead; 1 is alive.
Random rand = new Random();
public Cell (String nm)
{
name = nm;
myCount = rand.nextInt(999);
neighbors = 0;
// 2 because it is exclusive
this.setLife(rand.nextInt(2));
}
public void run()
{
while(Cell.myCount <= 10){
try
{
System.out.println("Expl Thread: " + (++Cell.myCount));
Thread.sleep(100);
} catch (InterruptedException iex)
{
System.out.println("Exception in thread:
"+iex.getMessage());
}
}
}
里面还有一些其他的东西,真的是为了简单起见,我认为没有必要显示它们,单元格规则也是如此 class。单元格规则如下:
/**
* This function simply gets the number of neighbors for each cell, and saves the future generation
* based on the number neighbors from arr to future array.
*
* @param arr Arr that will be checked for neighbors
* @param futureGen This array will keep track of the future generation
* @param columns numbers of columns
* @param rows numbers of rows
*/
public void checkN(Cell [][] arr, Cell [][] futureGen, int columns, int rows)
{
for (int x = 0; x < rows; x++)
{
for (int y = 0; y < columns; y++)
{
arr[x][y].setNeighbors(0);
// Upper left corner
if (x == 0 && y == 0)
for (int i = 0; i <= 1; i++)
for (int j = 0; j <= 1; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Upper margin checks
if ((x == 0) && (y != 0 && y <= columns - 2))
for(int i = 0; i <= 1; i++)
for(int j = -1; j <= 1; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Upper right corner
if ((x == 0) && (y == columns - 1))
for(int i = 0; i <= 1; i++)
for(int j = -1; j <= 0; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Left margin checks
if ((y == 0) && (x != 0 && x <= rows - 2))
for (int i = -1; i <= 1; i++)
for (int j = 0; j <= 1; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Lower left corner
if ((x == rows - 1) && y == 0)
for (int i = -1; i <= 0; i++)
for (int j = 0; j <= 1; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Bottom margin checks
if ((x == rows - 1) && (y != 0 && y <= columns - 2 ))
for (int i = -1; i <= 0; i++)
for (int j = -1; j <= 1; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Lower right corner
if ((x == rows - 1) && (y == columns - 1))
for (int i = -1; i <= 0; i++)
for (int j = -1; j <= 0; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Right margin checks
if ((y == columns - 1) && (x != 0 && x <= rows - 2))
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 0; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Middle area checks ( can check all around now )!
if ((x > 0) && (x < rows - 1) && (y > 0) && (y < columns - 1) )
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Do not add yourself!
if (arr[x][y].getLife() == 1)
arr[x][y].subNeighbor();
// Get the new generation through the neighbors
if ((arr[x][y].getLife() == 1) &&
(arr[x][y].getNeighbors() < 2))
futureGen[x][y].setLife(0);
else if ((arr[x][y].getLife() == 1) &&
(arr[x][y].getNeighbors() > 3))
futureGen[x][y].setLife(0);
else if ((arr[x][y].getLife() == 0) &&
(arr[x][y].getNeighbors() == 3))
futureGen[x][y].setLife(1);
else
futureGen[x][y].setLife(arr[x][y].getLife());
}
}
}
我不确定线程的实现到底去了哪里,非常感谢任何指导或解释!
祝你有美好的一天:)
首先,您的 checkN
函数设计过度了。让我们稍微简化一下。
/**
* This function simply gets the number of neighbors for each cell, and saves the future generation
* based on the number neighbors from arr to future array.
*
* @param arr Arr that will be checked for neighbors
* @param futureGen This array will keep track of the future generation
* @param columns numbers of columns
* @param rows numbers of rows
*/
public void checkN(Cell [][] arr, Cell [][] futureGen, int columns, int rows) {
for (int x = 0; x < rows; x++) {
for (int y = 0; y < columns; y++) {
arr[x][y].setNeighbors(0);
for(int i = -1; i <= 1; i++) {
for(int j = -1; j <= 1; j++) {
if(i == 0 && j == 0) continue; //don't check self
if(x + i < 0 || x + i >= rows) continue; //bounds checking
if(y + j < 0 || y + j >= columns) continue; //bounds checking
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
}
}
// Get the new generation through the neighbors
if(arr[x][y].getLife() == 1 &&
(arr[x][y].getNeighbors() == 2 || arr[x][y].getNeighbors() == 3))
futureGen[x][y].setLife(1);
else if(arr[x][y].getLife() == 0 && arr[x][y].getNeighbors() == 3)
futureGen[x][y].setLife(1);
else
futureGen[x][y].setLife(0);
}
}
}
然后我们可以将其重构为一些额外的函数:
private void countNeighbors(Cell[][] arr, int row, int column, int rows, int columns) {
Cell c = arr[row][column];
c.setNeighbors(0);
for(int i = -1; i <= 1; i++) {
for(int j = -1; j <= 1; j++) {
if(i == 0 && j == 0) continue; //don't check self
if(row + i < 0 || row + i >= rows) continue; //bounds checking
if(column + j < 0 || column + j >= columns) continue; //bounds checking
if (arr[row + i][column + j].getLife() == 1)
c.addNeighbor();
}
}
}
private void evaluateNeighbors(Cell oldCell, Cell newCell) {
if (oldCell.getLife() == 1 &&
(oldCell.getNeighbors() == 2 || oldCell.getNeighbors() == 3))
newCell.setLife(1);
else if(oldCell.getLife() == 0 && oldCell.getNeighbors() == 3)
newCell.setLife(1);
else
newCell.setLife(0);
}
public void checkN(Cell [][] arr, Cell [][] futureGen, int columns, int rows) {
for (int row = 0; row < rows; row++) {
for (int column = 0; column < columns; column++) {
countNeighbors(arr, row, column, rows, columns);
evaluateNeighbors(arr[row][column], futureGen[row][column]);
}
}
}
我们这样重构的原因马上就会明白。
那么回到最初的问题:我们在这个程序的什么地方插入线程?
您需要决定如何拆分线程。根据你的问题,听起来你想为每个被评估的单元格启动一个独立的线程。虽然理论上这种方法没有任何问题,但这并不能保证任何显着的加速,仅仅是因为在数千个单元格(即使是适度大小的网格,你也会很快命中)你将创建数千个线程,并且只有大型服务器才有足够的线程来利用它。
尽管如此,如果那是您想要做的,代码(Java 8 需要)如下所示:
public void checkN(Cell [][] arr, Cell [][] futureGen, int columns, int rows)
{
ArrayList<Thread> threads = new ArrayList<>();
for (int row = 0; row < rows; row++) {
for (int column = 0; column < columns; column++) {
Integer thread_local_row = row;
Integer thread_local_column = column;
Thread t = new Thread(() -> {
countNeighbors(arr, thread_local_row, thread_local_column, rows, columns);
evaluateNeighbors(arr[thread_local_row][thread_local_column], futureGen[thread_local_row][thread_local_column]);
});
t.start();
threads.add(t);
}
}
for(Thread t : threads)
t.join();
}
最终结果是每个单元格都将接收到自己的专用线程。请注意,一旦代码被重构,我们几乎不需要更改。
但是,正如我提到的,就我们正在创建的线程数而言,这是过大的杀伤力。因此,另一种方法是为每一行创建一个新线程。
public void checkN(Cell [][] arr, Cell [][] futureGen, int columns, int rows)
{
ArrayList<Thread> threads = new ArrayList<>();
for (int row = 0; row < rows; row++) {
Integer thread_local_row = row;
Thread t = new Thread(() -> {
for (int column = 0; column < columns; column++) {
countNeighbors(arr, thread_local_row, column, rows, columns);
evaluateNeighbors(arr[thread_local_row][column], futureGen[thread_local_row][column]);
}
});
t.start();
threads.add(t);
}
for(Thread t : threads)
t.join();
}
这样更好,但当然,如果你有一个不平衡的网格,其中有很多行但很少有列,它会再次变得过大。
因此第三种选择是使线程数保持不变,并扩展每个线程的工作负载以适应线程数。
public void checkN(Cell [][] arr, Cell [][] futureGen, int columns, int rows)
{
ArrayList<Thread> threads = new ArrayList<>();
final int NUM_OF_THREADS = 8; //Or can be passed as an argument
for(int tid = 0; tid < NUM_OF_THREADS; tid++) {
Integer thread_local_row_start = tid * rows / NUM_OF_THREADS;
Integer thread_local_row_end = (tid + 1) * rows / NUM_OF_THREADS;
Thread t = new Thread(() -> {
for (int row = thread_local_row_start; row < thread_local_row_end; row++) {
for (int column = 0; column < columns; column++) {
countNeighbors(arr, row, column, rows, columns);
evaluateNeighbors(arr[row][column], futureGen[row][column]);
}
}
});
t.start();
threads.add(t);
}
for(Thread t : threads)
t.join();
}
此选项往往是最高性能的,因为您可以将 NUM_OF_THREADS
设置为等于机器中处理器内核的数量,或者您在测试中找到的任何值以产生理想性能。
您可以使用这些技术中的任何一种,或者想出一种不同的技术来拆分线程(例如,也许一种算法可以完美地拆分工作负载,而不是四舍五入到最近的行数?) .重要的部分只是让您的代码组织得足够好,以促进您的工作负载拆分机制。
如果你限于Java7,所有写的代码仍然可以使用,但是lambda结构需要换成Anonymous Inner Class,任何变量需要在线程体中使用需要制作final
:
public void checkN(final Cell [][] arr, final Cell [][] futureGen, final int columns, final int rows)
{
ArrayList<Thread> threads = new ArrayList<>();
for (int row = 0; row < rows; row++) {
for (int column = 0; column < columns; column++) {
final Integer thread_local_row = row;
final Integer thread_local_column = column;
Thread t = new Thread(new Runnable() {
public void run() {
countNeighbors(arr, thread_local_row, thread_local_column, rows, columns);
evaluateNeighbors(arr[thread_local_row][thread_local_column], futureGen[thread_local_row][thread_local_column]);
}
});
t.start();
threads.add(t);
}
}
for(Thread t : threads)
t.join();
}
康威的生命游戏,每个细胞都是一个线程
大家好, 所以就像标题所说的那样,我必须制作一个程序来实现 Conway 的生命游戏中的线程,其中每个死细胞或活细胞都是一个线程。 我的第一个目标是简单地让游戏正常运行,我做到了(非常有趣的挑战) 所以我可以打印 20x20 网格,每个单元格都初始化为随机数 1 或 0,其中 1 是活的,0 是死的。 现在,我一直在观看关于如何使用线程的视频,但我仍然不确定我应该如何为每个单元实现它...... 我有 3 个 classes,Main、Cell 和 CellRules 我的 Cell class 看起来像:
public class Cell implements Runnable
{
public static int myCount = 0;
private String name;
private int neighbors;
private int alive; // 0 is dead; 1 is alive.
Random rand = new Random();
public Cell (String nm)
{
name = nm;
myCount = rand.nextInt(999);
neighbors = 0;
// 2 because it is exclusive
this.setLife(rand.nextInt(2));
}
public void run()
{
while(Cell.myCount <= 10){
try
{
System.out.println("Expl Thread: " + (++Cell.myCount));
Thread.sleep(100);
} catch (InterruptedException iex)
{
System.out.println("Exception in thread:
"+iex.getMessage());
}
}
}
里面还有一些其他的东西,真的是为了简单起见,我认为没有必要显示它们,单元格规则也是如此 class。单元格规则如下:
/**
* This function simply gets the number of neighbors for each cell, and saves the future generation
* based on the number neighbors from arr to future array.
*
* @param arr Arr that will be checked for neighbors
* @param futureGen This array will keep track of the future generation
* @param columns numbers of columns
* @param rows numbers of rows
*/
public void checkN(Cell [][] arr, Cell [][] futureGen, int columns, int rows)
{
for (int x = 0; x < rows; x++)
{
for (int y = 0; y < columns; y++)
{
arr[x][y].setNeighbors(0);
// Upper left corner
if (x == 0 && y == 0)
for (int i = 0; i <= 1; i++)
for (int j = 0; j <= 1; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Upper margin checks
if ((x == 0) && (y != 0 && y <= columns - 2))
for(int i = 0; i <= 1; i++)
for(int j = -1; j <= 1; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Upper right corner
if ((x == 0) && (y == columns - 1))
for(int i = 0; i <= 1; i++)
for(int j = -1; j <= 0; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Left margin checks
if ((y == 0) && (x != 0 && x <= rows - 2))
for (int i = -1; i <= 1; i++)
for (int j = 0; j <= 1; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Lower left corner
if ((x == rows - 1) && y == 0)
for (int i = -1; i <= 0; i++)
for (int j = 0; j <= 1; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Bottom margin checks
if ((x == rows - 1) && (y != 0 && y <= columns - 2 ))
for (int i = -1; i <= 0; i++)
for (int j = -1; j <= 1; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Lower right corner
if ((x == rows - 1) && (y == columns - 1))
for (int i = -1; i <= 0; i++)
for (int j = -1; j <= 0; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Right margin checks
if ((y == columns - 1) && (x != 0 && x <= rows - 2))
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 0; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Middle area checks ( can check all around now )!
if ((x > 0) && (x < rows - 1) && (y > 0) && (y < columns - 1) )
for (int i = -1; i <= 1; i++)
for (int j = -1; j <= 1; j++)
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
// Do not add yourself!
if (arr[x][y].getLife() == 1)
arr[x][y].subNeighbor();
// Get the new generation through the neighbors
if ((arr[x][y].getLife() == 1) &&
(arr[x][y].getNeighbors() < 2))
futureGen[x][y].setLife(0);
else if ((arr[x][y].getLife() == 1) &&
(arr[x][y].getNeighbors() > 3))
futureGen[x][y].setLife(0);
else if ((arr[x][y].getLife() == 0) &&
(arr[x][y].getNeighbors() == 3))
futureGen[x][y].setLife(1);
else
futureGen[x][y].setLife(arr[x][y].getLife());
}
}
}
我不确定线程的实现到底去了哪里,非常感谢任何指导或解释! 祝你有美好的一天:)
首先,您的 checkN
函数设计过度了。让我们稍微简化一下。
/**
* This function simply gets the number of neighbors for each cell, and saves the future generation
* based on the number neighbors from arr to future array.
*
* @param arr Arr that will be checked for neighbors
* @param futureGen This array will keep track of the future generation
* @param columns numbers of columns
* @param rows numbers of rows
*/
public void checkN(Cell [][] arr, Cell [][] futureGen, int columns, int rows) {
for (int x = 0; x < rows; x++) {
for (int y = 0; y < columns; y++) {
arr[x][y].setNeighbors(0);
for(int i = -1; i <= 1; i++) {
for(int j = -1; j <= 1; j++) {
if(i == 0 && j == 0) continue; //don't check self
if(x + i < 0 || x + i >= rows) continue; //bounds checking
if(y + j < 0 || y + j >= columns) continue; //bounds checking
if (arr[x + i][y + j].getLife() == 1)
arr[x][y].addNeighbor();
}
}
// Get the new generation through the neighbors
if(arr[x][y].getLife() == 1 &&
(arr[x][y].getNeighbors() == 2 || arr[x][y].getNeighbors() == 3))
futureGen[x][y].setLife(1);
else if(arr[x][y].getLife() == 0 && arr[x][y].getNeighbors() == 3)
futureGen[x][y].setLife(1);
else
futureGen[x][y].setLife(0);
}
}
}
然后我们可以将其重构为一些额外的函数:
private void countNeighbors(Cell[][] arr, int row, int column, int rows, int columns) {
Cell c = arr[row][column];
c.setNeighbors(0);
for(int i = -1; i <= 1; i++) {
for(int j = -1; j <= 1; j++) {
if(i == 0 && j == 0) continue; //don't check self
if(row + i < 0 || row + i >= rows) continue; //bounds checking
if(column + j < 0 || column + j >= columns) continue; //bounds checking
if (arr[row + i][column + j].getLife() == 1)
c.addNeighbor();
}
}
}
private void evaluateNeighbors(Cell oldCell, Cell newCell) {
if (oldCell.getLife() == 1 &&
(oldCell.getNeighbors() == 2 || oldCell.getNeighbors() == 3))
newCell.setLife(1);
else if(oldCell.getLife() == 0 && oldCell.getNeighbors() == 3)
newCell.setLife(1);
else
newCell.setLife(0);
}
public void checkN(Cell [][] arr, Cell [][] futureGen, int columns, int rows) {
for (int row = 0; row < rows; row++) {
for (int column = 0; column < columns; column++) {
countNeighbors(arr, row, column, rows, columns);
evaluateNeighbors(arr[row][column], futureGen[row][column]);
}
}
}
我们这样重构的原因马上就会明白。
那么回到最初的问题:我们在这个程序的什么地方插入线程?
您需要决定如何拆分线程。根据你的问题,听起来你想为每个被评估的单元格启动一个独立的线程。虽然理论上这种方法没有任何问题,但这并不能保证任何显着的加速,仅仅是因为在数千个单元格(即使是适度大小的网格,你也会很快命中)你将创建数千个线程,并且只有大型服务器才有足够的线程来利用它。
尽管如此,如果那是您想要做的,代码(Java 8 需要)如下所示:
public void checkN(Cell [][] arr, Cell [][] futureGen, int columns, int rows)
{
ArrayList<Thread> threads = new ArrayList<>();
for (int row = 0; row < rows; row++) {
for (int column = 0; column < columns; column++) {
Integer thread_local_row = row;
Integer thread_local_column = column;
Thread t = new Thread(() -> {
countNeighbors(arr, thread_local_row, thread_local_column, rows, columns);
evaluateNeighbors(arr[thread_local_row][thread_local_column], futureGen[thread_local_row][thread_local_column]);
});
t.start();
threads.add(t);
}
}
for(Thread t : threads)
t.join();
}
最终结果是每个单元格都将接收到自己的专用线程。请注意,一旦代码被重构,我们几乎不需要更改。
但是,正如我提到的,就我们正在创建的线程数而言,这是过大的杀伤力。因此,另一种方法是为每一行创建一个新线程。
public void checkN(Cell [][] arr, Cell [][] futureGen, int columns, int rows)
{
ArrayList<Thread> threads = new ArrayList<>();
for (int row = 0; row < rows; row++) {
Integer thread_local_row = row;
Thread t = new Thread(() -> {
for (int column = 0; column < columns; column++) {
countNeighbors(arr, thread_local_row, column, rows, columns);
evaluateNeighbors(arr[thread_local_row][column], futureGen[thread_local_row][column]);
}
});
t.start();
threads.add(t);
}
for(Thread t : threads)
t.join();
}
这样更好,但当然,如果你有一个不平衡的网格,其中有很多行但很少有列,它会再次变得过大。
因此第三种选择是使线程数保持不变,并扩展每个线程的工作负载以适应线程数。
public void checkN(Cell [][] arr, Cell [][] futureGen, int columns, int rows)
{
ArrayList<Thread> threads = new ArrayList<>();
final int NUM_OF_THREADS = 8; //Or can be passed as an argument
for(int tid = 0; tid < NUM_OF_THREADS; tid++) {
Integer thread_local_row_start = tid * rows / NUM_OF_THREADS;
Integer thread_local_row_end = (tid + 1) * rows / NUM_OF_THREADS;
Thread t = new Thread(() -> {
for (int row = thread_local_row_start; row < thread_local_row_end; row++) {
for (int column = 0; column < columns; column++) {
countNeighbors(arr, row, column, rows, columns);
evaluateNeighbors(arr[row][column], futureGen[row][column]);
}
}
});
t.start();
threads.add(t);
}
for(Thread t : threads)
t.join();
}
此选项往往是最高性能的,因为您可以将 NUM_OF_THREADS
设置为等于机器中处理器内核的数量,或者您在测试中找到的任何值以产生理想性能。
您可以使用这些技术中的任何一种,或者想出一种不同的技术来拆分线程(例如,也许一种算法可以完美地拆分工作负载,而不是四舍五入到最近的行数?) .重要的部分只是让您的代码组织得足够好,以促进您的工作负载拆分机制。
如果你限于Java7,所有写的代码仍然可以使用,但是lambda结构需要换成Anonymous Inner Class,任何变量需要在线程体中使用需要制作final
:
public void checkN(final Cell [][] arr, final Cell [][] futureGen, final int columns, final int rows)
{
ArrayList<Thread> threads = new ArrayList<>();
for (int row = 0; row < rows; row++) {
for (int column = 0; column < columns; column++) {
final Integer thread_local_row = row;
final Integer thread_local_column = column;
Thread t = new Thread(new Runnable() {
public void run() {
countNeighbors(arr, thread_local_row, thread_local_column, rows, columns);
evaluateNeighbors(arr[thread_local_row][thread_local_column], futureGen[thread_local_row][thread_local_column]);
}
});
t.start();
threads.add(t);
}
}
for(Thread t : threads)
t.join();
}