Concurrent & Parallel Programming With CPU-Affinity
Concurrent & Parallel Programming
1
White

Joe viết ngày 10/02/2018

I. OVERVIEW

High-level IT Developers often face the most basic problem that demands them to make some crucial decision which could severely impact their development: Concurrency or Parallelism. And it's clear that Concurrency is NOT Parallelism.

  • To CONCUR means to work together, to cooperate, to pay attention to each other.
  • To PARALLEL is working independently, side-by-side and alone among each other.

Working-together demands more harmonization, more synchronization among each other than working-alone-side-by-side.

Modern Object-Oriented Programming Languages (OOPL) do offer some mechanism to cope with the demand for concurrency and parallelism programming. However, only C/C++ or C# gives developers genuine and direct accesses to the underlaying Operating System (OS): Thread and CPU-Affinity. Please don't start to protest loudly. Java or Python does let developers work with threads. But CPU-Affinity? First, what is it, CPU-Affinity? Affinity is the proneness to something. If you love "eating steak" you have "Steak-affinity." Java or Python or Ruby doesn't give developers any possibility to access directly to a certain core (CPU) of a multicore processor.

First, we have to distinguish Thread from Task. A Task is a process which has its own working environment (e.g. memory, stack, etc.), and Thread is a "reduced" task, or a simplified subtask, which usually shares its environment with the main task or other subtasks. A Task or process can run multiple Threads (e.g. event-listeners) which should "ideally" run along and parallel to parent process.

alt text

Every IT developer has soon or later to work with thread. A Thread is a simple subtask that runs along and inside the main task. So far, so good with a thread. What's about multiple threads? As long as each thread won't interfere the main task, or other threads, or they don't have to compete or to concur with the main task or other threads everything works fine. What happens when threads work on a same area (objects)? They compete against each other for the use of this common area. If two or more threads concur at the same time (simultaneously or concurrently) and try to work on the very same object the result could be unpredictable and becomes unreliable. Or in other words: the result is falsified. I am talking now about Concurrency Programming.

C/C++ and C# are more or less OS-dependent and less standardized than Java or Python. Because Java and Python are popular, more standardized and OS-independent I decide to focus on JAVA which is in my opinion more versatile than PYTHON and, especially, PYTHON's threading mechanism is not as efficient as JAVA. Hence Concurrent or Parallel Programming in PYTHON is quite a tricky work.

First, let scratch a bit deeper under the skin of PYTHON. As we know, PYTHON is an interpreting OOPL. But PYTHON uses Global Interpreter Lock (GIL) to manage the concurrent threads and to make them "thread-safe", though, at the cost of real Multithreading and Parallelism. In essence and from this perspective Python is not truly multithreaded. So, now what? Python has to look for "supplier" who helps it to overcome the multitreaded problem. The solution is found in Greenlet (a spin-off of CPython's Stackless). Greenlet package (click HERE for more details) allows Python developers to work with multiple "Micro-Threads" called tasklets which run pseudo-concurrently (typically in a single or a few OS-level threads) and are synchronized with data exchanges on “channels” (quote). Now let compare serial-running tasks versus multiple threads and parallel-running tasks. We define 4 tasks (or processes) and 4 threads.

import os
import time
import threading
import multiprocessing

NUM_WORKERS = 10

def doSomething():
    print("PID: %s, Process Name: %s, Thread Name %s" % (
        os.getpid(),
        multiprocessing.current_process().name,
        threading.current_thread().name)
    )
    x = 0
    while x < 10000000:
        x += 1


## run tasks serially
start_time = time.time();
for _ in range(NUM_WORKERS):
    doSomething()

end_time = time.time()
print("Serial Running:", end_time - start_time)

## run tasks using threads
start_time = time.time();
threads = [threading.Thread(target = doSomething()) for _ in range(NUM_WORKERS)]
[thread.start() for thread in threads]
[thread.join() for thread in threads]

end_time = time.time()
print("MultiThreading", end_time - start_time)

## run tasks using processes
start_time = time.time();
processes = [multiprocessing.Process(target = doSomething()) for _ in range(NUM_WORKERS)]
[process.start() for process in processes]
[process.join() for process in processes]

end_time = time.time()
print("Parallel Running", end_time - start_time)

alt text

You can split the Python codes into 3 different scripts, for example, SerialTask.py, MultiThread.py and ParallelTask.py and check for yourself the results. If you have more specific interest only on Python Concurrent & Parallel Programming you can consult this SITE toptal or simply surf to Python.org.

Let try in Java. Different to Python Java sources must be compiled before they could be run.

  1. SerialTask.java
// Joe Nartca (C)
import java.util.*;
import java.util.concurrent.*;
import java.lang.management.*;
public class SerialTask implements Callable<Integer> {
    private String tName;
    public SerialTask(String name){
        tName = name;
    }
    public Integer call() {
        System.out.println("PID: "+Thread.currentThread().getId()+
                           " Process Name: "+ManagementFactory.getRuntimeMXBean().getName()+
                           " Task Name: "+tName);
        for (int i = 0; i < 10000000; ++i) ; // just counting to count
        return 0;
    }
    public static void main(String... a) throws Exception {
        int num = 4;
        if (a.length > 0) try {
            num = Integer.parseInt(a[0]);
        } catch (Exception e) {
            num = 4;
        }
        // Pool for only 1 task (or thread)
        ExecutorService pool = Executors.newFixedThreadPool(1);
        ArrayList<Future<Integer>> list = new ArrayList<Future<Integer>>();
        long beg = System.currentTimeMillis();
        for (int i = 0; i < num; ++i) {
            list.add(pool.submit(new SerialTask("No."+i)));
        }
        for (int i = 0, m = list.size(); i < m; ++i) list.get(i).get();
        System.out.println("SerialTask:"+(System.currentTimeMillis()-beg)+" milliSec.");
        pool.shutdownNow();
    }
}
  1. ParallelTask.java
// Joe Nartca (C)
import java.util.*;
import java.util.concurrent.*;
import java.lang.management.*;
public class ParallelTask implements Callable<Integer> {
    private String tName;
    public ParallelTask(String name){
        tName = name;
    }
    public Integer call() {
        System.out.println("PID: "+Thread.currentThread().getId()+
                           " Process Name: "+ManagementFactory.getRuntimeMXBean().getName()+
                           " Task Name: "+tName);
        for (int i = 0; i < 10000000; ++i) ; // just counting to count
        return 0;
    }
    public static void main(String... a) throws Exception {
        int num = 4;
        if (a.length > 0) try {
            num = Integer.parseInt(a[0]);
        } catch (Exception e) {
            num = 4;
        }
        ExecutorService pool = Executors.newWorkStealingPool(Runtime.getRuntime().availableProcessors());
        ArrayList<Future<Integer>> list = new ArrayList<Future<Integer>>();
        long beg = System.currentTimeMillis();
        for (int i = 0; i < num; ++i) {
            list.add(pool.submit(new ParallelTask("No."+i)));
        }
        for (int i = 0, m = list.size(); i < m; ++i) list.get(i).get();
        System.out.println("ParallelTask:"+(System.currentTimeMillis()-beg)+" milliSec.");
        pool.shutdownNow();
    }
}
  1. ForkTask.java
// Joe Nartca (C)
import java.util.*;
import java.util.concurrent.*;
import java.lang.management.*;
public class ForkTask extends RecursiveTask<Integer> {
    private String tName;
    public ForkTask(String name, int no){
        tName = name;
        this.no = no;
    }
    private int no;
    public Integer compute() {
        ForkTask tt[] = null;
        if (no > 0) {
            tt = new ForkTask[--no];
            for (int i = 0; i < no; ++i) {
                tt[i] = new ForkTask("No."+(i+1), 0);
                tt[i].fork();
            }
        }
        System.out.println("PID: "+Thread.currentThread().getId()+
                           " Process Name: "+ManagementFactory.getRuntimeMXBean().getName()+
                           " Task Name: "+tName);
        for (int i = 0; i < 10000000; ++i) ; // just counting to count
        if (tt != null) for (ForkTask ft : tt) ft.join();
        return 0;
    }
    private static int num = 4;
    public static void main(String... a) throws Exception {
        if (a.length > 0) try {
            num = Integer.parseInt(a[0]);
        } catch (Exception e) {
            num = 4;
        }
        ForkJoinPool pool = new ForkJoinPool(Runtime.getRuntime().availableProcessors());
        ArrayList<Future<Integer>> list = new ArrayList<Future<Integer>>();
        ForkTask tt = new ForkTask("No."+num, num);
        long beg = System.currentTimeMillis();
        pool.execute(tt);
        tt.join();
        System.out.println("ForkTask:"+(System.currentTimeMillis()-beg)+" milliSec.");
        pool.shutdownNow();
    }
}
  1. MultiThread.java
// Joe Nartca (C)
import java.lang.management.*;
public class MultiThread extends Thread {
    private String tName;
    public MultiThread(String name){
        tName = name;
    }
    public void run() {
        System.out.println("PID: "+getId()+
                           " Process Name: "+ManagementFactory.getRuntimeMXBean().getName()+
                           " Thread Name: "+tName);
        for (int i = 0; i < 10000000; ++i) ; // just counting to count
    }
    public static void main(String... a) throws Exception {
        int num = 4;
        if (a.length > 0) try {
            num = Integer.parseInt(a[0]);
        } catch (Exception e) {
            num = 4;
        }
        MultiThread[] tt = new MultiThread[num];
        long beg = System.currentTimeMillis();
        for (int i = 0; i < num; ++i) {
            tt[i] = new MultiThread("No."+i);
            tt[i].start();
        }
        for (int i = 0; i < num; ++i) tt[i].join();
        System.out.println("MultiThread:"+(System.currentTimeMillis()-beg)+" milliSec.");
    }
}

  1. PoolThread.java
// Joe Nartca (C)
import java.util.*;
import java.util.concurrent.*;
import java.lang.management.*;
public class PoolThread implements Runnable {
    private String tName;
    public PoolThread(String name){
        tName = name;
    }
    public void run() {
        System.out.println("PID: "+Thread.currentThread().getId()+
                           " Process Name: "+ManagementFactory.getRuntimeMXBean().getName()+
                           " Thread Name: "+tName);
        for (int i = 0; i < 10000000; ++i) ; // just counting to count
    }
    public static void main(String... a) throws Exception {
        int num = 4;
        if (a.length > 0) try {
            num = Integer.parseInt(a[0]);
        } catch (Exception e) {
            num = 4;
        }
        ExecutorService pool = Executors.newFixedThreadPool(num);
        ArrayList<Future<?>> list = new ArrayList<Future<?>>();
        long beg = System.currentTimeMillis();
        for (int i = 0; i < num; ++i) {
            list.add(pool.submit(new PoolThread("No."+i)));
        }
        for (int i = 0, m = list.size(); i < m; ++i) list.get(i).get();
        System.out.println("PoolThread:"+(System.currentTimeMillis()-beg)+" milliSec.");
        pool.shutdownNow();
    }
}

alt text

If you use Lambda Expression instead of Runnable-Implementation the result would stun you. Let modify the PoolThread.java and name it as PoolThreadLambda.java

// Joe Nartca (C)
import java.util.*;
import java.util.concurrent.*;
import java.lang.management.*;
public class PoolThreadLambda {
    private static int i;
    public static void main(String... a) throws Exception {
        int num = 4;
        if (a.length > 0) try {
            num = Integer.parseInt(a[0]);
        } catch (Exception e) {
            num = 4;
        }
        ExecutorService pool = Executors.newFixedThreadPool(num);
        ArrayList<Future<?>> list = new ArrayList<Future<?>>();
        long beg = System.currentTimeMillis();
        for (i = 0; i < num; ++i) {
            list.add(pool.submit(() -> {
                System.out.println("PID: "+Thread.currentThread().getId()+
                           " Process Name: "+ManagementFactory.getRuntimeMXBean().getName()+
                           " Thread Name: No."+i);
                for (int l = 0; l < 10000000; ++l) ; // just counting to count
            }));
        }
        for (int i = 0, m = list.size(); i < m; ++i) list.get(i).get();
        System.out.println("PoolThread:"+(System.currentTimeMillis()-beg)+" milliSec.");
        pool.shutdownNow();
    }
}

alt text

Again, the gain is insignificant. Why that? The answer lay back in the past: JVM up version 5 supports Concurrency to support multicore Processor and JVM's smart and efficient enough to exploit the available cores and to balance the last between the cores. How? A brief explanation: Instead of each JVM per core Java runs a single GLOBAL JVM and uses threads to to bind the cores and tries to distribute the last equally among the cores. Or in other words: "JVM uses a global scheduling technique, assuring at all times that the N highest priority threads in the Java application are running on the N available processors." (More: Multi-Core in JAVA/JVM or THIS).

The reason why Python and Java delivered insignificant results is due to two reasons:

  • Python Engine (PE) and Java Virtual Machine (JVM) do the load-balancing among the cores
  • the running tasks or threads are fully independent on each other

If objects are shared by threads they have to be synchronized in order to keep their state consistent and reliable. In this case the results of concurrency and parallelism will be significantly different.

From this point of view Java Concurrent & Parallel Programming is very "verbose" compared to Python, but the process is much faster (more than 60x faster) and more authentic. Yes, Python is an easy-to-learn OOPL thanks to its simplicity, but at the expense of Error-Handling (very confusing for newbies), "pseudo" concurrency, incompatibilities between versions, 3rd party API-package dependency and Performance (slow). Further: the purpose of Multiple Threads and Parallelism is to enhance the performance and to reduce the processing time. What we see within Python's multiprocessing and threading package is not very encouraging. The gain is negligible. If you so will, Python is best for single processing (which is usually the case for a quick-and-ready-to-run solution, for example, in Machine Learning). However, business applications are more complex, more demanding than "simplicity" or "easy-to-learn". For example, Banking DB-transactions rely heavily on quick response which is only achievable with MultiThreading and Parallelism.

Because Software development is usually not a research or an experimental work (e.g. Machine Learning), or quick-and-ready solution (e.g. playing a MP3-piece of music) we have to phase Python out and focus on Java Concurrent & Parallel Programming (note: C# is quite Java-alike so that this tutorial is also appliable to C#.)

II. SOME BASICS ABOUT THREAD, TASK, SUB-THREAD AND SUB-TASK

Thread is a simplified task (or process) and run by the main process. Meaning: a process can spawn some threads to do some work aside. For example: to download a file while the main process continues to do other things. In Java it's the Thread API. User's thread must derived form this API.

public Download extends Thread {
    // constructor
    public Download(String url) {
       ...
    }
    ...
    public void run() {
      ...// your codes
    }
}
...
(new Download("http:myhome.com/start.html")).start();

Each thread is an unique object, its instance is unsharable and thread itself cannot be an extension of another object (single inheritance). And that is very costly if more threads are needed. Even thread consumes less resources than a task. If an instance is sharable thread should be replaced by a "subthread". An implementation of Interface Runnable (subthread)

public Download implements Runnable {
    // constructor
    public Download(String url) {
       ...
    }
    ...
    public void run() {
      ...// your codes
    }
}
...
(new Threas(new Download("http:myhome.com/start.html"))).start(); 

The only difference is the instance Download. As a Thread each "download" produces an own instance. As a Runnable-Implementation the instance "download" can be shared and also an extension of an object other than object Thread itself.

Thread and Runnable Implementation (RI) won't return any result. If a thread or RI needs to return a result it must be implemented as a Task or a Callable. Example:

As a Task:

public class ForkTask extends RecursiveTask<Integer> {
    public ForkTask(...){
        ...// initialization
    }
    public Integer compute() {
        ...// your codes
        if (wrong) return -1;  // return value
        return 0;
    }
}

As an Implementation of Callable (subtask)

public class CallTask implements Callable<Integer> {
    public CallTask(...){
        ...// initialization
    }
    public Integer call() {
        ...// your codes
        if (wrong) return -1;  // return value
        return 0;
    }
}

Of course the returned value can be anything: Object (e.g. String, StringBuffer, etc.) or primitive (as Integer, Long, Double, etc.)

III. SYNCHRONIZATION

The most basic protecting mechanism of shared objects is Synchronization between contenders. Synchronized objects are monopolized by allowing only one access to be work with. In Java it's the keyword synchronized. A single object (or variable) or a group of objects or a method can be synchronized. Without the keyword synchronized any modification could end up in unpredictable state.

Synchronized Method

    int count = 0;
    StringBuilder sb = new StringBuilder();
    ...
    private synchronized void counter() {
        count = count + 1;
        sb.append(count+" ");
    }

Synchronized Group

    int count = 0;
    StringBuilder sb = new StringBuilder();
    ...
    private void counter() {
        synchronized(this) {
           count = count + 1;
           sb.append(count+" ");
        }
    }

Synchronized object (not with primitive type like int, long, float, etc.)

    int count = 0;
    StringBuilder sb = new StringBuilder();
    ...
    private void counter() {
        synchronized(this) {   // primitive -> this
           count = count + 1;
        }
        synchronized(sb) {     // object StringBuffer
           sb.append(count+" ");
        }
    }

Now let check the functionality of synchronized and learn how it works.

Without Synchonization

public class NoSyncThread {
    private class NoSync implements Runnable {
        public Sync() { } // just for Performance
        public void run() {
            counter();
        }
    }
    private void counter() {
        count = count + 1;
    }
    public NoSync getThread( ) {
        return new NoSync( );
    }
    private int count = 0;
    public static void main(String... a) throws Exception {
        Thread[] t = new Thread[10000];
        NoSyncThread ns = new NoSyncThread();
        long beg = System.currentTimeMillis();
        for (int i = 0; i < 10000; ++i) {
            t[i] = new Thread(ns.getThread( ));
            t[i].start();
        }
        for (int i = 0; i < 10000; ++i) t[i].join();
        System.out.println("Counter = "+ns.count+
                           "\nTime:"+(System.currentTimeMillis()-beg)+" milliSec.");
    }
}

With Synchonization

public class SyncThread {
    private class Sync implements Runnable {
        public Sync() { } // just for Performance
        public void run() {
            counter();
        }
    }
    private synchronized void counter() {
        count = count + 1;
    }
    public Sync getThread() {
        return new Sync();
    }
    private int count = 0;
    public static void main(String... a) throws Exception {
        Thread[] t = new Thread[10000];
        SyncThread st = new SyncThread();
        long beg = System.currentTimeMillis();
        for (int i = 0; i < 10000; ++i) {
            t[i] = new Thread(st.getThread());
            t[i].start();
        }
        for (int i = 0; i < 10000; ++i) t[i].join();
        System.out.println("Counter = "+st.count+
                           "\nTime:"+(System.currentTimeMillis()-beg)+" milliSec.");
    }
}

alt text

You may note that the elapsed time for 10000 synchronized threads is always more than 10000 unsynchronized. The reason is obvious: the counter is synchronized and that means: only ONE can access, the others have to wait while unsynchronized threads accessed wildly the counter without having to wait for the others.

IV. CONCURRENCY

As we see, Synchronization is more or less very "coarse". Threads have to wait when the object was held by a thread. And that is not very funny when business application poses unpredictable long response time. Java concurrency package java.util.concurrent relieves the waiting problem with ThreadPool. ThreadPool (or ExecutorService) cares about the processing and reuses the resources (saving resource excess). There are different kinds of pools. The most relevant Pools are:

  • FixedThreadPool(int NoOfThreads): it is useful if Tasks or Threads are small and have a long life. The NoOfThreads defines the number of threads that can be active and could ve simultaneously (or concurrently) executed. If the number of arrival threads exceeds NoOfThreads the newly arrivals have to wait until some active threads terminate. If there're more cores JVM cares about a balancing distribution of threads to each core (Parallelism.) How? JVM links Java Threads up with OS threads, but it is not very clear how JVM distributes a task or a thread to a certain core.
  • CachedThreadPool(): it's FixedThreadPool with NoOfThreads = infinite. CachedThreadPool is useful when the number of threads is excessive and the threads are short-lived (how short is your "rule of thumb"?).
  • WorkStealingPool(int NoOfCores): it's a variant of ForkJoinPool. Its target is parallelism. JVM does more work than just linking to OS-Core-Scheduling by checking the queue of each core. If, for example, core 1 has an empty queue and core 3 has too many waiting tasks JVM "steals" (hence "Stealing") some waiting tasks of core 3 and puts them into core 1 queue for execution.

The mentioned ForkJoinPool is used to split a task (e.g. recursive task) into several subtasks which can be executed parallel. The "Stealing" act won't happen here.

Working with Concurrency and Parallelism are usually tricky. The most unspoken problem is when the state of a thread or a task must be observed or followed. Especially when the threads or tasks run inside a pool and because the result can be only delivered in the "future" -meaning: after a period of processing time. Java Concurrent Package provides developers an API named Future< V >. The term V stands for Value (or Variable) and it could be any Java object. If a primitive int is an expected return value object Integer is used instead of int. Example

public class SyncTaskPoolLambda {
    private static synchronized void counter() {
        count = count + 1;
    }
    private static int count = 0;
    public static void main(String... a) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(100);
        ArrayList<Future<Integer>> list = new ArrayList<Future<Integer>>();
        long beg = System.currentTimeMillis();
        for (i = 0; i < 10; ++i) {
            list.add(pool.submit(()-> {
                counter();
                return count; // return an int
            }));
        }
        for (int i = 0; i < 10; ++i) // print the result 
             System.out.println("Task_"+i+":"+list.get(i).get());
        System.out.println("Counter = "+count+
                           "\nTime:"+(System.currentTimeMillis()-beg)+" milliSec.");
        pool.shutdownNow();
    }
}

The output could be

C:\JoeApp\cpuAffinity>java SyncTaskPoolLambda
Task_0:1
Task_1:3
Task_2:2
Task_3:4
Task_4:5
Task_5:8
Task_6:6
Task_7:7
Task_8:9
Task_9:10
Counter = 10
Time:63 milliSec.

What do we need if we want to follow a thread or subthread? In such a case the "V" is replaced by a question mark ?. Example

public class SyncThreadPoolLambda {
    private static synchronized void counter() {
        count = count + 1;
    }
    private static int count = 0;
    public static void main(String... a) throws Exception {
        ExecutorService pool = Executors.newFixedThreadPool(100);
        ArrayList<Future<?>> list = new ArrayList<Future<?>>();
        long beg = System.currentTimeMillis();
        for (i = 0; i < 10; ++i) {
            list.add(pool.submit(()-> {
                counter();
            }));
        }
        for (int i = 0; i < 10; ++i) {
            list.get(i).get(); // wait for the "future"
            System.out.println("End of Runnable:"+i);
        }
        System.out.println("Counter = "+count+
                           "\nTime:"+(System.currentTimeMillis()-beg)+" milliSec.");
        pool.shutdownNow();
    }
}

and the output:

C:\JoeApp\cpuAffinity>java SyncThreadPoolLambda
End of Runnable:0
End of Runnable:1
End of Runnable:2
End of Runnable:3
End of Runnable:4
End of Runnable:5
End of Runnable:6
End of Runnable:7
End of Runnable:8
End of Runnable:9
Counter = 10
Time:62 milliSec.

Now let rewrite the SyncThread.java using FixedThreadPool and see how "good" Concurrency works with JVM.

SyncThreadPool.java (counterpart of SyncThread.java) with a queue of 100

import java.util.*;
import java.util.concurrent.*;
public class SyncThreadPool {
    private class Sync implements Runnable {
        public Sync() { } // just for Performance
        public void run() {
            counter();
        }
    }
    private synchronized void counter() {
        count = count + 1;
    }
    public Sync getThread( ) {
        return new Sync( );
    }
    private int count = 0;
    public static void main(String... a) throws Exception {
        SyncThreadPool st = new SyncThreadPool();
        ExecutorService pool = Executors.newFixedThreadPool(100);
        ArrayList<Future<?>> list = new ArrayList<Future<?>>();
        long beg = System.currentTimeMillis();
        for (int i = 0; i < 10000; ++i) {
            list.add(pool.submit(st.getThread( )));
        }
        for (int i = 0; i < 10000; ++i) list.get(i).get();
        System.out.println("Counter = "+st.count+
                           "\nTime:"+(System.currentTimeMillis()-beg)+" milliSec.");
        pool.shutdownNow();
    }
}

alt text

Note: SyncStealingPool.java: replace the line "ExecutorService pool = Executors.newFixedThreadPool(100);" with this "ExecutorService pool = Executors.newWorkStealingPool(Runtime.getRuntime().availableProcessors());"

Here you can see the significance of Concurrency: 46 mSec. (or 31 mSec. with StealingPool) versus 1742 milliSec. by 10000 concurrent threads. A factor of 37 or in other word: 37 times faster with Concurrency.

You may ask what kind of application is best for concurrency, am I right? Pool is best for Webbrowser or Webproxy or DB-Server or any desktop application that requires a myriad threads or tasks to work concurrently or parallel. Example, a webpage is usually full of links which must be accessed. If a browser works conventionally link after link a full webpage could take minutes or hours and that is impossible for a user. With ThreadPool the accesses can be done concurrently and the waiting time for a full webpage is cutting down to seconds.

V. LOCK & UNLOCK MECHANICSM

Instead of using "synchronized" implicitly to lock and unlock object you can use the Lock Interface or implemented lock objects (e.g. ReentrantLock, StampedLock, etc.) of the package java.concurrent to do the explicit lock. Or you can develop your own locking/unlocking mechanism. For more details you'd study the API you work with. The beauty of Java is its single Inheritance and that means all Java objects are children of class Object which is also the foundation of concurrency. With the method wait() and notify() you are in a favorable position to build your own Lock & Unlock mechanism. An example of your own Lock/Unlock object.

package joe;
public class MyLock {
  private boolean locked = false;
  public synchronized void lock() throws Exception{
    while(locked) {
      wait();
    }
    System.out.println("It's locked NOW!");
    locked = true;
  }
  public synchronized void unlock(){
    locked = false;
    System.out.println("It's unlocked NOW!");
    notify();
  }
}

You may note that MyLock has to use synchronized implicitly to lock the implemented methods lock() and unlock(). Now let rewrite SyncThreadPool.java and name it as ThreadPool_MyLock.java

// Joe Nartca (C)
import java.util.*;
import java.util.concurrent.*;
// import my self-developed Lock/Unlock mechanism
import joe.MyLock;
public class ThreadPool_MyLock {
    private class Sync implements Runnable {
        public Sync() { } // just for Performance
        public void run() {
            counter();
        }
    }
    MyLock myLock = new MyLock();
    private void counter() {
        try {
            myLock.lock();
            count = count + 1;
            myLock.unlock();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    public Sync getThread( ) {
        return new Sync( );
    }
    private int count = 0;
    public static void main(String... a) throws Exception {
        ThreadPool_MyLock st = new ThreadPool_MyLock();
        ExecutorService pool = Executors.newFixedThreadPool(100);
        ArrayList<Future<?>> list = new ArrayList<Future<?>>();
        long beg = System.currentTimeMillis();
        for (int i = 0; i < 10000; ++i) {
            list.add(pool.submit(st.getThread( )));
        }
        for (int i = 0; i < 10000; ++i) list.get(i).get();
        System.out.println("Counter = "+st.count+
                           "\nTime:"+(System.currentTimeMillis()-beg)+" milliSec.");
        pool.shutdownNow();
    }
}

the output is

C:\JoeApp\cpuAffinity>java ThreadPool_MyLock
It's locked NOW!
It's unlocked NOW!
It's locked NOW!
...
It's locked NOW!
It's unlocked NOW!
Counter = 10000
Time:46354 milliSec.

The advantage of self-developed Lock/Unlock mechanism is the versatility and its simplicity. Now let modify ThreadPool_MyLock,java to ThreadPool_APILock.java using the Concurrent API ReentrantLock. What is "Reentrant"? In plain English: another entry variant (from to re-enter.) In Computer Science it's a safe continuation of execution in CPU exactly after a "long" interrupt.

// Joe Nartca (C)
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
//
public class ThreadPool_APILock {
    private class Sync implements Runnable {
        public Sync() { } // just for Performance
        public void run() {
            counter();
        }
    }
    // Using Concurrent API
    private Lock myLock = new ReentrantLock();
    private void counter() {
        myLock.lock();
        count = count + 1;
        myLock.unlock();
    }
    public Sync getThread() {
        return new Sync();
    }
    private int count = 0;
    public static void main(String... a) throws Exception {
        ThreadPool_APILock st = new ThreadPool_APILock();
        ExecutorService pool = Executors.newFixedThreadPool(100);
        ArrayList<Future<?>> list = new ArrayList<Future<?>>();
        long beg = System.currentTimeMillis();
        for (int i = 0; i < 10000; ++i) {
            list.add(pool.submit(st.getThread()));
        }
        for (int i = 0; i < 10000; ++i) list.get(i).get();
        System.out.println("Counter = "+st.count+
                           "\nTime:"+(System.currentTimeMillis()-beg)+" milliSec.");
        pool.shutdownNow();
    }
}

We remove the printing messages out of MyLock and recompile it and compare the result with Concurrent ReentrantLock (by 10000 concurrent threads.)

C:\JoeApp\cpuAffinity>java ThreadPool_MyLock
Counter = 10000
Time:47 milliSec.

C:\JoeApp\cpuAffinity>java ThreadPool_APILock
Counter = 10000
Time:47 milliSec.

C:\JoeApp\cpuAffinity>

As said, self-developed can be very bad if the developer is inexperienced, or excellent if the devloper's a Guru (due to simplicity) compared to API ReentrantLock. To make sure that a locked object always unlocked after use (even in case of exception) it's better to use try-finally to solve the Come-back problem. Example

    private void counter() {
        try {
            myLock.lock();
            count = count + 1;
        } finally {
            myLock.unlock(); // coerce to come back.
        }
    }
    ...

Further, if you have to work with mutable object (e.g. ArrayList, StringBuffer, etc.) and want to keep strict consistency of the object you can implement the ReadWriteLock Interface or use its implemented API ReentrantReadWriteLock. Example

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
// Joe Nartca (C)
public class ThreadPool_RWLock {
    private class Sync implements Runnable {
        public Sync() { } // just for Performance
        public void run() {
            doWrite(doRead()+1);
        }
    }
    private ReadWriteLock myLock = new ReentrantReadWriteLock();
    private Lock rLock =  myLock.readLock();
    private Lock wLock =  myLock.writeLock();
    private int doRead() {
        int result = -1;
        rLock.lock();
        try {
            result = count++;
        } finally {
            rLock.unlock();
        }
        return result;
    }
    private void doWrite(int n) {
        wLock.lock();
        try {
            sb.append("/"+n);
        } finally {
            wLock.unlock();
        }
     }
    public Sync getThread( ) {
        return new Sync( );
    }
    private int count = 0;
    private StringBuilder sb = new StringBuilder();
    public static void main(String... a) throws Exception {
        ThreadPool_RWLock st = new ThreadPool_RWLock();
        ExecutorService pool = Executors.newFixedThreadPool(100);
        ArrayList<Future<?>> list = new ArrayList<Future<?>>();
        long beg = System.nanoTime();
        for (int i = 0; i < 10; ++i) {
            list.add(pool.submit(st.getThread( )));
        }
        for (int i = 0; i < 10; ++i) list.get(i).get();
        System.out.println("sb = "+st.sb.toString()+
                           "\nTime:"+(System.nanoTime()-beg)+" nanoSec.");
        pool.shutdownNow();
    }
}

and the output could be

C:\JoeApp\cpuAffinity>javac -g:none -d ./classes ThreadPool_RWLock.java

C:\JoeApp\cpuAffinity>java ThreadPool_RWLock
sb = /2/1/3/4/5/6/7/8/9/10
Time:2895653 nanoSec.

C:\JoeApp\cpuAffinity>

If you want to know when a lock is raised you can use the API StampedLock that works similarly like ReentrantReadWriteLock, however you always get an unique "stamp" (a long value) for your lock-action and it can be used to unlock or to verify precisely a certain lock. Different to ReentrantReadWriteLock StampedLock provides the unlockRead() and unlockWrite() so that you don't have to derive the lock and unlock from Lock Interface. Example

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
// Joe Nartca (C)
public class ThreadPool_StampedLock {
    private class Sync implements Runnable {
        public Sync() { } // just for Performance
        public void run() {
            doWrite(doRead()+1);
        }
    }
    private StampedLock myLock = new StampedLock();
    private int doRead() {
        int result = -1;
        long rl = myLock.readLock();
        try {
            result = count++;
        } finally {
            myLock.unlockRead(rl);
        }
        return result;
    }
    private void doWrite(int n) {
        long wl = myLock.writeLock();
        try {
            sb.append("/"+n);
        } finally {
            myLock.unlockWrite(wl);
        }
     }
    public Sync getThread( ) {
        return new Sync( );
    }
    private int count = 0;
    public StringBuilder sb = new StringBuilder();
    public static void main(String... a) throws Exception {
        ThreadPool_StampedLock st = new ThreadPool_StampedLock();
        ExecutorService pool = Executors.newFixedThreadPool(100);
        ArrayList<Future<?>> list = new ArrayList<Future<?>>();
        long beg = System.nanoTime();
        for (int i = 0; i < 10; ++i) {
            list.add(pool.submit(st.getThread( )));
        }
        for (int i = 0; i < 10; ++i) list.get(i).get();
        System.out.println("sb = "+st.sb.toString()+
                           "\nTime:"+(System.nanoTime()-beg)+" nanoSec.");
        pool.shutdownNow();
    }
}

and the output

C:\JoeApp\cpuAffinity>javac -g:none -d ./classes ThreadPool_StampedLock.java

C:\JoeApp\cpuAffinity>java ThreadPool_StampedLock
sb = /1/2/3/4/5/6/7/8/9/10
Time:3054312 nanoSec.

C:\JoeApp\cpuAffinity>

The advantage of StampedLock is the possibilities of "Optimistic Trying". Instead of waiting for an (un)locked object you can "tryOptimisticRead()" or "tryReadLock()" or "tryWriteLock()", etc. So, when working with "Optimistic" you should validate the lock every time after accessing any shared object to make sure the read was still valid. Example: let modify the doRead() method of ThreadPool_StampedLock.java

    private int doRead() {
        int result = -1;
        long stamp = myLock.readLock(); // to lock count
        try {  // convert to writeLock
            stamp = myLock.tryConvertToWriteLock(stamp);
            // if failed! must lock count for write
            if (stamp == 0) stamp = myLock.writeLock();
            result = count++;
        } finally {
            myLock.unlockRead(stamp);
        }
        return result;
    }
    ...

Of course such an "optimistic" lock requires some more time to manage the locking/unlocking mechanism (2556522 nanoSec. versus 4182039 nanoSec.)

C:\JoeApp\cpuAffinity>java ThreadPool_StampedLock
sb = /1/2/3/4/5/6/7/8/9/10
Time:2556522 nanoSec.

C:\JoeApp\cpuAffinity>java ThreadPool_StampedOptLock
sb = /1/2/3/4/5/6/7/8/10/9
Time:4182039 nanoSec

For more information about other methods of ReentrantReadWriteLock and StampedLock you should take time and tinker with them so far that you get some feeling how they work.

VI. SEMAPHORE

Additionally to ReentrantReadWriteLock and StampedLock you can also work with java.util.concurrent.Semaphore. Semaphore allows you to "acquire" or "permit" an entire block until the number of "permissions" is reached. Example

import java.util.*;
import java.util.stream.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
// Joe Nartca (C)
public class ThreadPool_Semaphore {
    public static void main(String... a) throws Exception {
        Semaphore sem = new Semaphore(4); // max 4 permissions
        Runnable ra = () ->{
            boolean permit = false;
            try {
                permit = sem.tryAcquire(1, TimeUnit.SECONDS);
                if (permit) {
                    System.out.println("Got Permit!");
                    Thread.sleep(1);
                }
            } catch (Exception e) {
                System.out.println("Semaphore is exhausted!");
            } finally {
                if (permit) sem.release();
            }
        };
        //  max 10 active threads
        ExecutorService pool = Executors.newFixedThreadPool(10);
        IntStream.range(1, 10).forEach(i -> pool.submit(ra));
        pool.shutdownNow();
    }
}

the output

C:\JoeApp\cpuAffinity>javac -g:none -d ./classes ThreadPool_Semaphore.java

C:\JoeApp\cpuAffinity>java ThreadPool_Semaphore
Got Permit!
Got Permit!
Got Permit!
Semaphore is exhausted!
Semaphore is exhausted!
Got Permit!
Semaphore is exhausted!
Semaphore is exhausted!
Semaphore is exhausted!
Semaphore is exhausted!
Semaphore is exhausted!
Semaphore is exhausted!
Semaphore is exhausted!
Semaphore is exhausted!

C:\JoeApp\cpuAffinity>

VI. FINE-GRAINED CONCURRENCY AND PARALLELISM

With the given Lock & Unlock Mechanism (imlicit with synchronized or explicit with Concurrency APIs or your own) the fine-grained package java.concurrent.atomic also provides you an intrinsic Lock & Unlock possibility: Atomic Variables and ConcurrentMap. It's a lock-unlock-free and threadsafe programming. The package description heralds:

A small toolkit of classes that support lock-free thread-safe programming on single variables. In essence, the classes in this package extend the notion of volatile values, fields, and array elements to those that also provide an atomic conditional update operation of the form: boolean compareAndSet(expectedValue, updateValue);...

The atomic variables represent the primitives like int, long, double, etc. and are preceded by Atomic, for example, AtomicInteger, AtomicBoolean, AtomicLong, etc. Also: A lock-free thread-safe programming on single variables. Example: we rewrite ThreadPool_APILock.java (with ReentrantLock) using "AtomicInteger count" instead of "int count".

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
// Joe Nartca (C)
public class ThreadPool_AtomicInteger {
    private class Sync implements Runnable {
        public Sync() { } // just for Performance
        public void run() {
            counter();
        }
    }
    // No lock/unlock, No synchronized...
    // just as usual
    private void counter() {
        count.incrementAndGet();
    }
    public Sync getThread( ) {
        return new Sync( );
    }
    //private int count = 0;   
    private AtomicInteger count = new AtomicInteger(0);
    public static void main(String... a) throws Exception {
        ThreadPool_AtomicInteger st = new ThreadPool_AtomicInteger();
        ExecutorService pool = Executors.newFixedThreadPool(100);
        ArrayList<Future<?>> list = new ArrayList<Future<?>>();
        long beg = System.currentTimeMillis();
        for (int i = 0; i < 10000; ++i) {
            list.add(pool.submit(st.getThread( )));
        }
        for (int i = 0; i < 10000; ++i) list.get(i).get();
        System.out.println("Counter = "+st.count.get()+
                           "\nTime:"+(System.currentTimeMillis()-beg)+" milliSec.");
        pool.shutdownNow();
    }
}

the output is correct: 10000 !

C:\JoeApp\cpuAffinity>javac -g:none -d ./classes ThreadPool_AtomicInteger.java

C:\JoeApp\cpuAffinity>java ThreadPool_AtomicInteger
Counter = 10000
Time:47 milliSec.

C:\JoeApp\cpuAffinity>

It's strongly recommended that Concurrency Developer should pay more attention to this "fine-grained" concurrency package. The gain is not the response time, but more expressed in clarity and less bug-prone (e.g. missing lock or unlock, etc.)

VII. PARALLELISM WITH CPU-AFFINITY

Modern Operation Systems exploit fully the multicore processor by distributing processes equally among different available core. For example: on Linux using C/C++

#define _GNU_SOURCE
#include <sched.h>
long sched_setaffinity(pid_t pid, unsigned int len,
                  unsigned long *user_mask_ptr);
long ched_getaffinity(pid_t pid, unsigned int len,
                  unsigned long *user_mask_ptr); 

With the function sched_setaffinity() developers can "pin" a certain process (or task/thread) on a certain core (or CPU) so that some prioritized app can be secured to be always executed. It's could quicken a certain app, but might worsen the performance of other apps because OS is usually optimized to balance the load equally on the cores.

On Windows with C/C++ (for C# see HERE)

BOOL WINAPI SetProcessAffinityMask(
  _In_ HANDLE    hProcess,
  _In_ DWORD_PTR dwProcessAffinityMask
);

If your codes (i.e. tasks and threads) can be separated and run independently you should start to think about parallelism. Either you let the work being done by OS, or you distribute the work for yourself using CPU-Affinity technique. It's more complicated with PYTHON. How about JAVA? It's relatively easy and simple (of course, if you're experienced developer). I show you here two possible options:

  • Pure JAVA using Java Thread
  • Via JNI to (Windows) Kernel32

The JNI option requires the package com.sun.jna (download HERE). This package includes other OSs, too:

  • freebsd-amd64
  • freebsd-i386
  • linux-amd64
  • linux-i386
  • linux-ia64
  • sunos-amd64
  • sunos-sparc
  • sunos-sparcv9
  • sunos-x86
  • Win32
  • Win32-amd64
  • Win32-x86

PURE JAVA

package joe;
// Joe Nartca (C)
public class JavaThreadCore extends Thread{
    private Thread t;
    private int cores, coreNo;
    private static String name;
    /** Constructor
     * @param name Strimg, name of JavaThreadCore
    */
    public JavaThreadCore(String name){
        this.name = name;
        cores = Runtime.getRuntime().availableProcessors();
    }
    /** Constructor with selected core
     * @param name Strimg, name of JavaThreadCore
     * @param coreNo int, the selected core (between 1 to N cores)
    */
    public JavaThreadCore(String name, int coreNo){
        this.name = name;
        this.coreNo = coreNo;
        cores = Runtime.getRuntime().availableProcessors();
        // create a thread for this coreNo
        setAffinity(coreNo);
    }
    /** synchronized method to select the processor
     * @param coreNo int, the selected core between 1 to N
    */
    public synchronized void setAffinity(int coreNo){
        this.coreNo = coreNo;
        if (coreNo >= cores)throw new IllegalArgumentException("CPU "+coreNo+" isn't available.");
        for(int i = 0; i < cores; i++) if (i == coreNo) t = new Thread(name);
    }
    /** get the affinity of the thread
    * @return int the core of the running thread
    */
    public int getAffinity(){
        return coreNo;
    }
    /** get the number of cores
    * @return int the number of cores
    */
    public int getCores(){
        return cores;
    }
}

Example

// Joe Nartca (C)
import joe.JavaThreadCore;
public class JavaTest extends JavaThreadCore{
    private static String name;
    private static int wait;
    private static int pri,afi;
    //
    public JavaTest(String name,int wait,int pri,int afi){
        super(name);
        this.name = name;
        this.wait = wait;
        this.pri = pri;
        this.afi = afi;
        setPriority(pri);
        setAffinity(afi);
        start();
    }
    public void run(){
        try{
            for(int i = 0; i < 5; i++) {
                System.out.println(i+"\t"+ getId()+"\t\tCPU "+ getAffinity()+
                                   "\t\t"+ currentThread()); 
                sleep(wait);
            }
        }catch(InterruptedException e){
            System.out.println(e);
        }
    }
    public static void main(String s[]){ 
        long startTime, stopTime, elapsedTime;
        try{
            System.out.println("Available Processors:"+Runtime.getRuntime().availableProcessors()+
                               "\nValue\tThread Id\tCPU #\t\t\tThread"+
                               "\n----------------------------------------------------------");
            startTime = System.currentTimeMillis();
            JavaTest a1= new JavaTest("one",5,1,0);
            JavaTest a2= new JavaTest("two",5,1,1);
            JavaTest a3= new JavaTest("three",5,10,2);
            JavaTest a4= new JavaTest("four",5,10,3);
            a1.join();
            a2.join();
            a3.join();
            a4.join();
            stopTime = System.currentTimeMillis();
            elapsedTime = (stopTime - startTime)/wait;
            System.out.println("Used exection time in ms: " +elapsedTime);
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

Output

C:\JoeApp\cpuAffinity>javac -g:none -d ./classes JavaTest.java

C:\JoeApp\cpuAffinity>java JavaTest
Available Processors:4
Value   Thread Id       CPU #                   Thread
----------------------------------------------------------
0       10              CPU 0           Thread[Thread-0,1,main]
0       12              CPU 1           Thread[Thread-1,1,main]
0       16              CPU 3           Thread[Thread-3,10,main]
0       14              CPU 2           Thread[Thread-2,10,main]
1       12              CPU 1           Thread[Thread-1,1,main]
1       10              CPU 0           Thread[Thread-0,1,main]
1       14              CPU 2           Thread[Thread-2,10,main]
1       16              CPU 3           Thread[Thread-3,10,main]
2       10              CPU 0           Thread[Thread-0,1,main]
2       14              CPU 2           Thread[Thread-2,10,main]
2       12              CPU 1           Thread[Thread-1,1,main]
2       16              CPU 3           Thread[Thread-3,10,main]
3       10              CPU 0           Thread[Thread-0,1,main]
3       12              CPU 1           Thread[Thread-1,1,main]
3       14              CPU 2           Thread[Thread-2,10,main]
3       16              CPU 3           Thread[Thread-3,10,main]
4       10              CPU 0           Thread[Thread-0,1,main]
4       12              CPU 1           Thread[Thread-1,1,main]
4       14              CPU 2           Thread[Thread-2,10,main]
4       16              CPU 3           Thread[Thread-3,10,main]
Used exection time in ms: 8

C:\JoeApp\cpuAffinity>

VIA JNI

package joe;
import com.sun.jna.Native;
// download jna package HERE: http://www.java2s.com/Code/Jar/j/Downloadjna325jar.htm
// Joe Nartca (C)
public class KernelThreadCore extends Thread {
    private Kernel32 kernel32 = (Kernel32)Native.loadLibrary("kernel32", Kernel32.class);
    private int mask, cores;
    /** Constructor
    */
    public KernelThreadCore( ){
        cores = Runtime.getRuntime().availableProcessors();
    }
    /** Constructor
    * @param tName String, name of KernelThreadCore
    */
    public KernelThreadCore(String tName){
        super(tName);
        cores = Runtime.getRuntime().availableProcessors();
    }
    /** setAffinity
    * @param tid  int, Thread ID
    * @param mask int, mask (hex number) for a selected core
    * @return int 0: successful, -1: failed
    */
    public int setAffinity(int tid, int mask) {
        if (mask > cores) throw new IllegalArgumentException("The CPU between 1.."+cores);
        this.mask = mask;
        return kernel32.SetThreadAffinityMask(tid, mask);
    }
    /** getAffinity
    * @return int the setting mask
    */
    public int getAffinity(){
        return mask;
    }
    /** CurrentThreadId
    * @return int the current-running Thread ID 
    */
    public int getCurrentThreadId(){
        return kernel32.GetCurrentThreadId();
    }
    /** idle
    */
    public void idle(int duration){
        kernel32.Sleep(duration);
    }
    /** get the number of cores
    * @return int the number of cores
    */
    public int getCores(){
        return cores;
    }
}

Example

// Joe Nartca (C)
import joe.KernelThreadCore;
public class KernelTest extends KernelThreadCore{
    private String name;
    private static int wait,affinity;
    private int pri;
    //
    public KernelTest(String name,int wait,int pri,int affinity) throws Exception{
        super(name);
        this.name = name;
        this.wait = wait;
        this.pri = pri;
        this.affinity = affinity;
        setPriority(pri);
        setAffinity(getCurrentThreadId(), affinity);
        start();
    }
    public synchronized void run(){
        try{
            for(int i = 0; i < 5; i++){
                System.out.println(i+"\t"+getCurrentThreadId()+"\t\tCPU " +getAffinity()+
                                   "\t\t" +currentThread());
                idle(wait);
            }
        } catch(Exception e){
            System.out.println("Error :"+e);
        }
    }
    public static void main(String aa[]){
        long startTime,stopTime,elapsedTime;
        try{
            System.out.println("Available Processors:"+Runtime.getRuntime().availableProcessors()+
                               "\nValue\tThread Id\tCPU #\t\t\tThread"+
                               "\n----------------------------------------------------------");
            startTime = System.currentTimeMillis();
            //(thread name, waiting time, priority, affinity)
            KernelTest a1 = new KernelTest("ONE",5,1,0); 
            KernelTest a2 = new KernelTest("TWO",5,1,1);
            KernelTest a3 = new KernelTest("THREE",5,10,2);
            KernelTest a4 = new KernelTest("FOUR",5,10,3);
            // joins waiting thread through Thread class method
            a1.join();
            a2.join();
            a3.join();
            a4.join();
            stopTime = System.currentTimeMillis();
            elapsedTime = (stopTime - startTime)/wait;
            System.out.println("Used exection time in ms: " +elapsedTime);
        } catch(Exception e){
            e.printStackTrace();
        }
    }
}

Output

C:\JoeApp\cpuAffinity>java KernelTest
Available Processors:4
Value   Thread Id       CPU #                   Thread
----------------------------------------------------------
0       1668            CPU 0           Thread[ONE,1,main]
0       1220            CPU 1           Thread[TWO,1,main]
0       22340           CPU 2           Thread[THREE,10,main]
0       18524           CPU 3           Thread[FOUR,10,main]
1       22340           CPU 2           Thread[THREE,10,main]
1       1668            CPU 0           Thread[ONE,1,main]
1       1220            CPU 1           Thread[TWO,1,main]
1       18524           CPU 3           Thread[FOUR,10,main]
2       22340           CPU 2           Thread[THREE,10,main]
2       1668            CPU 0           Thread[ONE,1,main]
2       1220            CPU 1           Thread[TWO,1,main]
2       18524           CPU 3           Thread[FOUR,10,main]
3       22340           CPU 2           Thread[THREE,10,main]
3       1668            CPU 0           Thread[ONE,1,main]
3       1220            CPU 1           Thread[TWO,1,main]
3       18524           CPU 3           Thread[FOUR,10,main]
4       18524           CPU 3           Thread[FOUR,10,main]
4       1220            CPU 1           Thread[TWO,1,main]
4       1668            CPU 0           Thread[ONE,1,main]
4       22340           CPU 2           Thread[THREE,10,main]
Used exection time in ms: 116

C:\JoeApp\cpuAffinity>

JavaThreadCore runs faster than KernelThreadCore because the last one has to load the Windows Kernel32 before it could access its services. Btw, with JNI it's more precise than PURE JAVA and looks "sophisticated", but with the tradeoff of less performance and OS-dependency.

Joe

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Joe

20 bài viết.
121 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
27 11
Fuzzy Logic and Machine Learning Hi First of all: I apologize everyone for my writing in English. I come to this site because someone of Daynhauh...
Joe viết 11 tháng trước
27 11
White
21 8
Thu Nhat: Toi muon bien bai blog nay bang tieng Viet, nhung toi khong co du chu chu dong...Eh uh then in English. Noboby wants to be beholden as a ...
Joe viết 3 tháng trước
21 8
White
18 9
You're a fresh graduate and work for more than 12 months in an IT company with some boring coding tasks... The tasks are unchallenging. Day in, day...
Joe viết 11 ngày trước
18 9
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
20 bài viết.
121 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!