并发问题在协调两个线程时。

huangapple go评论52阅读模式
英文:

Concurrency issue when coordinating two threads

问题

我试图将循环的工作分成两个线程。

我正在使用_ExecutorService_创建第二个线程,同时使用主线程作为另一个线程。

我使用一个计数器在达到某个值时停止循环,但我无法在这两个线程之间同步计数器,导致循环多运行了一次。

此外,我在主线程中使用一个while循环来检查所需计数是否已达到,并在达到时打印结束时间,但这也没有起作用。

ExecutorService executorService = Executors.newFixedThreadPool(1);

Clock clock = new Clock();

int count = 5;
AtomicInteger c = new AtomicInteger(1); 
clock.start();

executorService.execute(() -> {
    while (c.get() <= count) {
        System.out.println("LOOP ONE" + "----PRINT_ME");
        c.incrementAndGet();
    }
});

while (c.get() <= count) {
    System.out.println("LOOP TWO" + "----PRINT_ME");
    c.incrementAndGet();
}

while(true) {
    if(c.get() == count) {
        System.out.println("STOPPED");
        clock.stop();
        break;
    }
}

输出 -

Clock started at -- 2020-07-25T12:32:59.267
LOOP TWO----PRINT_ME
LOOP TWO----PRINT_ME
LOOP TWO----PRINT_ME
LOOP TWO----PRINT_ME
LOOP TWO----PRINT_ME
LOOP ONE----PRINT_ME

在上述代码中,PRINT_ME 应该被打印 5 次(int count = 5;),但实际上被打印了 6 次(多了一次)。

我不确定发生了什么,以及如何在两个线程之间共享计数器。

附注:如果在两个循环中都添加 Thread.sleep(2000);

executorService.execute(() -> {
    while (c.get() <= count) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("LOOP ONE" + "----PRINT_ME");
        c.incrementAndGet();
    }
});

while (c.get() <= count) {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("LOOP TWO" + "----PRINT_ME");
    c.incrementAndGet();
}

输出是:

Clock started at -- 2020-07-25T12:54:15.596
LOOP TWO----PRINT_ME
LOOP ONE----PRINT_ME
LOOP TWO----PRINT_ME
LOOP ONE----PRINT_ME
LOOP TWO----PRINT_ME
LOOP ONE----PRINT_ME
英文:

I am trying to divide the work of a loop into two threads.

I am using an ExecutorService to create the 2nd thread while using the main thread as the other one.

I am using a counter to stop the loop when it reaches a certain value but I'm not able to sync the counter among these two threads and loop is running one extra time.

Also, I am using a while loop in main thread to know when the desired count has reached print the end time, which is also not working.

ExecutorService executorService = Executors.newFixedThreadPool(1);
	
	Clock clock = new Clock();

	int count = 5;
	AtomicInteger c = new AtomicInteger(1); 
	clock.start();
	
	executorService.execute(()-&gt;{
		while (c.get() &lt;= count) {
			
			System.out.println(&quot;LOOP ONE&quot; + &quot;----PRINT_ME&quot;);
			c.incrementAndGet();
		}
	});
	
	while (c.get() &lt;= count) {
		
		System.out.println(&quot;LOOP TWO&quot; + &quot;----PRINT_ME&quot;);
		c.incrementAndGet();
	}
	
	while(true) {
		if(c.get() == count) {
            System.out.println(&quot;STOPPED&quot;);
			clock.stop();
			break;
		}
	}

Output -

Clock started at -- 2020-07-25T12:32:59.267
LOOP TWO----PRINT_ME
LOOP TWO----PRINT_ME
LOOP TWO----PRINT_ME
LOOP TWO----PRINT_ME
LOOP TWO----PRINT_ME
LOOP ONE----PRINT_ME

In the code above PRINT_ME should get printed 5 times(int count = 5;) only but is is getting printed 6 times (One time extra).

I am not sure what is going on here and how we can share a counted among two threads.

PSI: If I add Thread.sleep(2000); in both loops

executorService.execute(()-&gt;{
		while (c.get() &lt;= count) {
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(&quot;LOOP ONE&quot; + &quot;----PRINT_ME&quot;);
			c.incrementAndGet();
		}
	});
	
	while (c.get() &lt;= count) {
		
		try {
			Thread.sleep(2000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(&quot;LOOP TWO&quot; + &quot;----PRINT_ME&quot;);
		c.incrementAndGet();
	}

the output is:

Clock started at -- 2020-07-25T12:54:15.596
LOOP TWO----PRINT_ME
LOOP ONE----PRINT_ME
LOOP TWO----PRINT_ME
LOOP ONE----PRINT_ME
LOOP TWO----PRINT_ME
LOOP ONE----PRINT_ME

答案1

得分: 3

问题在于您首先轮询计数器,执行操作,然后增加计数器,表示作业已完成。

这会创建一个窗口,使得两个线程可以同时进行工作,然后在完成工作后执行递增操作,这样做太晚了。

为了更容易可视化,让我们想象一下,期望的计数为1,有两个线程争夺要执行的任务。在您的实现中,两个线程都会从检查 c.get() <= count 开始,对于两者都是真的,然后执行任务,这导致了您描述的问题。

为了防止这种情况发生,线程首先需要声明一个“空闲插槽”,然后才执行任务。这可以通过在作业开始之前递增计数器,然后在每个作业开始之前验证最大 count 条件来完成:

int count = 5;
AtomicInteger c = new AtomicInteger(0);

executorService.execute(() -> {
    while (c.incrementAndGet() <= count) {
        // ...
    }
});

while (c.incrementAndGet() <= count) {
    // ...
}
英文:

The problem is that you poll the counter first, perform an action, and then increment the counter signaling that the job has been done.

This creates a window where both threads can be doing the work at the same time and performing the incrementation after they are finished which is just too late.

To make it easier to visualize, let's imagine that there the desired count is 1 and that there are two threads fighting for a task to execute. In your implementation, both threads would start by checking c.get() &lt;= count which would be true for both, and then execute the task which results in the problem you described.

In order to prevent this, threads need to claim a "free slot" first, and only then perform the task. This can be performed by incrementing the counter before the job starts, and then verifying the max count condition before starting each job:

    int count = 5;
    AtomicInteger c = new AtomicInteger(0);

    executorService.execute(()-&gt;{
        while (c.incrementAndGet() &lt;= count) {
            // ...
    });

    while (c.incrementAndGet() &lt;= count) {
            // ..
    }

huangapple
  • 本文由 发表于 2020年7月25日 15:04:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/63085401.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定