How to set CPU Affinity in Golang? [SOLVED]


Written By - Deepak Prasad

Understanding the basics of CPU Affinity

Let's visualize a Linux system with four CPU cores and, for simplicity, one ready-to-run thread. On which CPU core will this thread run? The kernel will decide this; the key thing to realize is that it could run upon any of the four available CPUs!

Can the CPU(s) it could possibly be run upon be specified by the programmer? Yes, indeed; just this feature alone is called CPU affinity. On Linux, it is a per-thread attribute (within the OS). The CPU affinity can be changed on a per-thread basis by changing the thread's CPU affinity mask; this is achieved, of course, via a system call.

I have already written some articles around this area which can help you understand the basics:

Check thread count per process in Linux
CPU, processors, core, threads - Explained in layman's terms

Now that we are clear what is CPU Affinity, let's understand how it can be set using GO code. We will be using Linux environment for all our examples which rely on underlying kernel modules so it may or may not work in other environments.

 

Can we set CPU Affinity in GO using GOMAXPROCS?

Unfortunately NO, the runtime.GOMAXPROCS(n) function in Golang does not directly set the CPU affinity of a process in Linux. Instead, it sets the maximum number of OS threads that the Go runtime will use to execute Go code concurrently. It does not set the number of cores a process will be limited to.

You can read more at Understanding GOMAXPROCS with Example

 

Method-1: Set CPU Affinity using syscall.SchedSetaffinity()

In Golang, you can set CPU affinity by using the syscall package and the SchedSetaffinity function. The SchedSetaffinity function takes a process ID (PID) and a bitmask that specifies which CPUs the process is allowed to run on.

package main

import (
	"fmt"
	"syscall"
	"time"
	"unsafe"
)

func main() {
	var mask uintptr
	
	// Get the current CPU affinity of the process
	if _, _, err := syscall.RawSyscall(syscall.SYS_SCHED_GETAFFINITY, 0, uintptr(unsafe.Sizeof(mask)), uintptr(unsafe.Pointer(&mask))); err != 0 {
		fmt.Println("Failed to get CPU affinity:", err)
		return
	}
	fmt.Println("Current CPU affinity:", mask)

	// Set the new CPU affinity
	mask = 3
	if _, _, err := syscall.RawSyscall(syscall.SYS_SCHED_SETAFFINITY, 0, uintptr(unsafe.Sizeof(mask)), uintptr(unsafe.Pointer(&mask))); err != 0 {
		fmt.Println("Failed to set CPU affinity:", err)
		return
	}
	fmt.Println("New CPU affinity:", mask)

	// some code
	for {
		println("Hello, World!")
		time.Sleep(1 * time.Second)
	}
}

Output:

]# go run main.go 
Current CPU affinity: 15
New CPU affinity: 3
Hello, World!
Hello, World!
Hello, World!

 

Method-2: Set CPU Affinity using taskset

taskset is used to set or retrieve the CPU affinity of a running process given its PID or to launch a new COMMAND with a given CPU affinity.  Here's an example using the os/exec package to set the CPU affinity for the current process to use CPU 0 and CPU 1:

package main

import (
	"fmt"
	"os"
	"os/exec"
)

func main() {
	pid := os.Getpid()

	// Get the current CPU affinity of the process
	cmd := exec.Command("taskset", "-p", fmt.Sprintf("%d", pid))
	out, err := cmd.Output()
	if err != nil {
		fmt.Println(err)
	}

	// Set the CPU affinity for the current process to use CPU 0 and CPU 1
	cmd = exec.Command("taskset", "-p", "0,1", fmt.Sprintf("%d", pid))
	out, err = cmd.Output()
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("%s", out)
}

Output:

# go run main.go 
pid 871612's current affinity mask: f
pid 871612's new affinity mask: 1

This code will get the current process ID using os.Getpid() and then it creates an instance of the exec.Command struct, passing the taskset command, the -p flag (to change the affinity mask of a process) and a string representing the desired CPU mask (in this case "0,1") and the pid of the process to be modified.

 

Still failing to set CPU Affinity?

Are you still unable to assign CPU to your process? There can be various reasons for the same:

  • The runtime.GOMAXPROCS() function sets the maximum number of CPUs that can execute Go code simultaneously, but it does not set the CPU affinity. If you want to set the CPU affinity of a process, you'll need to use a different method, such as the taskset command as I mentioned earlier.
  • It's possible that your system does not support the ability to set the CPU affinity for a process. This is a feature provided by the kernel and not all kernels support it.
  • If you're running the code on a multi-node environment like Kubernetes or other container orchestration systems, you will need to set the CPU affinity via the orchestration system.

 

Summary

There are multiple methods to set CPU Affinity in golang such as using taskset, syscall package or you can use C library inside the GO code to use SYS_SCHED_SETAFFINITY API. It is important to note that setting CPU affinity is a feature provided by the operating system and not all operating systems support it. So, you should also check if your OS supports this functionality.

Moreover changing the affinity can also lead to performance issues as when kernel allots a CPU to any process, so it automatically checks the availability of free CPU and then assigns the process to that CPU but when we are forcing a certain CPU to the process then there is no guarantee that the CPU in question is free and can cause issues to the application.

 

References

How to set CPU affinity in Go

 

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

Leave a Comment