Update: Newer versions of taskset have a -a
/--all-tasks
option that "operates on all the tasks (threads) for a given pid" and should solve the behavior I show below.
I wrote a Python script that simply spins up some threads and burns CPU cycles. The idea is to test taskset against it, as it's quite simple.
#!/usr/bin/env python
import threading
def cycle_burner():
while True:
meh = 84908230489 % 323422
for i in range(3):
thread = threading.Thread(target=cycle_burner)
print "Starting a thread"
thread.start()
Just running the Python script eats up about 150% CPU usage.
[~/cbench]$ ./burn_cycles.py
Starting a thread
Starting a thread
Starting a thread
Launching my Python script with taskset works as expected. Watching top shows the Python process pegged at 100% usage.
[~/cbench]$ taskset -c 0 ./burn_cycles.py
Starting a thread
Starting a thread
Starting a thread
Interestingly, launching the Python script and then immediately using taskset to set the just-started process' affinity caps the process at 100%. Note from the output that the Linux scheduler finished executing the Bash commands before spawning the Python threads. So, the Python process was started, then it was set to run on CPU 0, then it spawned its threads, which inherited the proper affinity.
[~/cbench]$ ./burn_cycles.py &; taskset -pc 0 `pgrep python`
[1] 8561
pid 8561's current affinity list: 0-3
pid 8561's new affinity list: 0
Starting a thread
[~/cbench]$ Starting a thread
Starting a thread
That result contrasts with this method, which is exactly the same but allows the Python threads to spawn before setting the affinity of the Python process. This replicates the "taskset does nothing" results I described above.
[~/cbench]$ ./burn_cycles.py &
[1] 8996
[~/cbench]$ Starting a thread
Starting a thread
Starting a thread
[~/cbench]$ taskset -pc 0 `pgrep python`
pid 8996's current affinity list: 0-3
pid 8996's new affinity list: 0
What's going wrong here?
Apparently threads spawned before the parent process' affinity is changed don't inherit the affinity of their parent. If someone could edit in a link to documentation that explains this, that would be helpful.
Ah, it's not the systemd-logind feature where each user gets it's own cgroup. I think the change responsible here is older; they're just confusingly similar. (I searched "process group fair scheduling", thinking it might be something based on unix's "process groups" that I never really understand). Wikipedia:
The Linux kernel received a patch for CFS in November 2010 for the 2.6.38 kernel that has made the scheduler fairer for use on desktops and workstations.
When a task calls __proc_set_tty(), the process wide reference to the default group is dropped, a new task group is created, and the process is moved into the new task group. Children thereafter inherit this task group, and increase its refcount. On exit, a reference to the current task group is dropped when the last reference to each signal struct is dropped. The task group is destroyed when the last signal struct referencing it is freed. At runqueue selection time, IFF a task has no cgroup assignment, its current autogroup is used.
The feature is enabled from boot by default if CONFIG_SCHED_AUTOGROUP is selected, but can be disabled via the boot option noautogroup, and can be also be turned on/off on the fly [via /proc/sys/kernel/sched_autogroup_enabled
: Writing 0
there disables it for newly created tasks, writing 1
enables it.]
The primary issues solved by this are for multi-core as well as multi-cpu (SMP) systems experiencing increased interactive response times while performing other tasks that use many threads in those tasks. A simple explanation is that one will be able to still watch a video, read email and perform other typical desktop activities without glitches or choppiness while compiling the Linux kernel or a similar process such as encoding video. However, there are objections to this statement.
Best Answer
taskset
uses a mask to specify which CPUs a process can run on. Each bit maps to one CPU; if a bit is set to 1, the process can run on that CPU, if it’s set to 0, it can’t. Thus a mask of FF means any CPU from 0 to 7 (not one specific CPU), and a mask of 2 means only CPU 1.