Can two threads be assigned to different processors and execute at the same time?

Viewed 573

I have a code like this, just trying to test it out.

import java.util.ArrayList;

public class Main {
    public static class MyThread extends Thread {
        public void run(){
            while(true);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ArrayList<MyThread> l = new ArrayList<>(100);
        int i;
        for (i = 0; i < 100; i++) {
            l.add(new MyThread());
        }
        for (i = 0; i < 100; i++) {
            l.get(i).start();
        }
        for (i = 0; i < 100; i++) {
            l.get(i).join();
        }
    }
}

I have read a lot of posts about the differences between parallelism and multithreading. However, I would like to ask this question. Could the OS choose to execute two or more threads created by the same process (not the same thread, but different ones, like t1 and t2) at the same time? If that's the case, can we ensure that it's happening? I think I'm having some troubles understanding the scheduling that is being done, because I thought that if a process creates a thread, the only processor that could be executing that task is the one that has the process being executed, but now I'm not that sure.

2 Answers

You asked:

Can two threads be assigned to different processors and execute at the same time?

Yes.

A couple of threads may be sharing a processor core, with one running while the other waits. Modern CPUs often have hyper-threading technology to make this switching back and forth quite cheap (little overhead, fast performance). But at any one moment only a single thread can be running on a core.

A machine with a multi-core processor can be simultaneously running a different thread on each core. Two cores can be running two threads total, at any one moment. Three cores, three threads, and so on. The cores operate separately, each executing instructions on their own at the very same moment. As mentioned above, there may be additional threads assigned to a core, waiting its turn to execute.

You asked:

could the OS choose to execute two or more threads created by the same process (not the same thread, but different ones, like t1 and t2) at the same time?

Yes certainly. The host OS can choose to run any thread of any process at any time. For a process running Java, the JVM is also involved in scheduling the threads within its jurisdiction.

For the threads to be executing literally simultaneously, they would be assigned to separate CPU cores.

If two threads share a core, then they must wait to take turns as mentioned above. One thread is executing on the core while the other is not, back and forth. This rapid switching creates the illusion of running together at the same time, but that is not literally true. Hyper-threading technology makes this illusion so convincing that modern OSes such as macOS report hyper-threaded cores as multiple cores. For example, a 4-core Intel CPU with Hyper-Threading technology will be reported in the macOS Activity Monitor app (and top console command) as 8 cores even though technically only 4 threads are ever executing at any one moment. Indeed, there is some small overhead involved in switching threads even with hyper-threading, so some sysadmins may choose to disable hyper-threading on a machine that is dedicated to doing CPU-bound tasks. The example 8 virtual cores will be reduced to being only 4 real cores after disabling hyper-threading.

You asked:

If that's the case, can we ensure that it's happening?

No. Scheduling execution of threads is the business of the JVM and host OS, not us programmers writing Java code.

You asked:

I think I'm having some troubles understanding the scheduling that is being done,

We cannot understand the scheduling, because CPU core scheduling is controlled by the OS and JVM as mentioned above. So scheduling decisions are up to them. Those decisions will vary depending on the capabilities of the hardware, and on the current runtime conditions. The scheduling behavior may change with version updates to the host OS or the implementation of Java.

You asked:

because I thought that if a process creates a thread, the only processor that could be executing that task is the one that has the process being executed, but now I'm not that sure.

You may be thinking of processor affinity, also known as "CPU pinning". To quote Wikipedia:

Processor affinity … enables the binding and unbinding of a process or a thread to a central processing unit (CPU) or a range of CPUs, so that the process or thread will execute only on the designated CPU or CPUs rather than any CPU.

As far as I know, programmers writing Java code have no control over any processor affinity features your particular CPU architecture may offer. We do not know if our Java thread is staying with one core or is being moved around to different cores at different times. Not our business, except we need know the implications mentioned below.

The upshot is that we Java programmers do not control any aspect of the scheduling of threads’ execution.

If doing any threading, be sure to read the classic book Java Concurrency in Practice by Brian Goetz et al. And learn about the Java Memory Model and “visibility” issues, the volatile keyword, the Atomic… classes, and the executor framework. And keep your eyes peeled for advancements with Project Loom under development now.

Caveat: I am over-simplifying for clarity. If you care about the gory details, start with the Wikipedia pages I linked.

Code

Regarding your example code, understand that in modern Java we rarely need to address the Thread class directly. The executor framework was added to Java 5 to relieve us Java programmers from the chore of juggling thread management.

Write your task as either a class implementing Runnable, or as a lambda with a run method.

Here is some example code of running 100 tasks with 3 threads. Beware: The System.out console output is not in chronological order. If you care, study the timestamps.

System.out.println( "INFO - demo starting.  " + Instant.now() );

// Submit tasks to run on background threads.
ExecutorService executorService = Executors.newFixedThreadPool( 3 );
for ( int i = 0 ; i < 100 ; i++ )
{
    executorService.submit( ( ) -> System.out.println( "Thread id " + Thread.currentThread().getId() + " is executing its task at " + Instant.now() ) );
}

// Wait for tasks to complete.
executorService.shutdown();
try { executorService.awaitTermination( 1 , TimeUnit.HOURS ); } catch ( InterruptedException e ) { e.printStackTrace(); }

System.out.println( "INFO - demo ending.  " + Instant.now() );

When run.

INFO - demo starting.  2021-03-30T03:07:31.695531Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.719603Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.718036Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.718036Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.731698Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.731745Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.731772Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.731762Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.731809Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.731836Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.731867Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.731888Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.731910Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.731934Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.731955Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.731978Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732001Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732028Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732046Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732065Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732088Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732109Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732127Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732141Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732160Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732185Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732207Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732229Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732250Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732271Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732293Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732320Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732340Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732359Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732374Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732391Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732408Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732424Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732440Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732464Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732484Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732501Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732516Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732535Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732556Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732579Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732598Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732617Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732632Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732648Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732669Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732684Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732698Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732718Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732735Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732753Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732770Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732785Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732805Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732819Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732834Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732858Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732878Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732896Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732910Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732936Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.732955Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.732970Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.732985Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.733011Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.733056Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.733034Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.733168Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.733106Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.733123Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.733279Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.733311Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.733335Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.733371Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.733386Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.733425Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.733469Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.733221Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.733535Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.733485Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.733582Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.733605Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.733643Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.733659Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.733700Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.733680Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.733736Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.733754Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.733779Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.733798Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.733818Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.733848Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.733833Z
Thread id 15 is executing its task at 2021-03-30T03:07:31.733880Z
Thread id 16 is executing its task at 2021-03-30T03:07:31.733900Z
Thread id 14 is executing its task at 2021-03-30T03:07:31.733918Z
INFO - demo ending.  2021-03-30T03:07:31.734044Z

If you want to collect results from your tasks, make them Callable rather than Runnable. And capture the Future object returned by the executor service on each call submitting your task. When all the tasks are done, loop the Future objects to ask for the result contained in each.

A fundamental design principle of "parallelism" is that: you do not know, and cannot know, *what the operating system will do."

At any given instant in time, the operating system perceives that there are "runnable threads, within runnable processes." Every CPU is competing, so to speak, to get a chance to run one. While hardware interrupts and other things are happening all the time. It is therefore a "chaotic" state of affairs, in the mathematical sense of the term.

You therefore must design software which uses the "serialization" and "mutual exclusion" features of the language – whatever language it may be – to the extent necessary for your application.

Related