# Question about the renice command and threads



## GrandAdmiralThrawn (May 11, 2021)

I have a quick question about the use of the renice(8) command on FreeBSD when working with multi-threaded applications. I've looked through the man pages and the web, but couldn't really find an answer anywhere.

On Linux, renicing a process will not automatically renice all threads spawned by that same process, so you have to get all the TID's and renice them too. For example, on Linux, to renice a process and all of its threads, you'd need to do something like this: `$ renice -n 10 -p $(ps --no-heading -Lo tid <PID-goes-here>)`

Question: Does using renice(8) on a process on FreeBSD apply the new nice level to all threads of the process as well? This is assuming the process does not change its own thread's nice levels programmatically.

Similarly, when renicing an entire process group, will that alter the nice levels of all threads belonging to all processes in that process group?

Chapter "4.4 Thread Scheduling" in the book "The design and implementation of the FreeBSD operating system" seems to suggest that the process nice level directly affects the behavior of that process' threads (as far as I can understand it), but I wanted to ask to make sure.

Thank you.​


----------



## Alain De Vos (May 11, 2021)

There is a thread-ID and a proces-ID. As I don't see a tool to manipulate thread-ID's, I "suppose" all threads belonging to the same process inherit the same nice value when created. When modified afterwards I don't know.


----------



## mark_j (May 11, 2021)

Yes.


----------



## mark_j (May 11, 2021)

GrandAdmiralThrawn said:


> I have a quick question about the use of the renice(8) command on FreeBSD when working with multi-threaded applications. I've looked through the man pages and the web, but couldn't really find an answer anywhere.
> 
> On Linux, renicing a process will not automatically renice all threads spawned by that same process, so you have to get all the TID's and renice them too. For example, on Linux, to renice a process and all of its threads, you'd need to do something like this: `$ renice -n 10 -p $(ps --no-heading -Lo tid <PID-goes-here>)`
> 
> ...


FreeBSD follows posix which restricts process priority to encapsulating threads.
Now, I believe linux used to not do this and treat threads as processes but not now.  Incidentally, Solaris, for example, does quirky things with their lightweight processes for classes like realtime, fair-share scheduling and so on with forkall(2) and priocntl(2).


----------



## GrandAdmiralThrawn (May 11, 2021)

Thanks for your reply, but I fear I do not understand it. What do you mean by "which restricts process priority to encapsulating threads". I have to admit that I don't know what "encapsulating threads" are, so I'm not sure what this means in regards to my question.

Maybe you can clarify this further?

Thank you!


----------



## mark_j (May 11, 2021)

Sure. As I understand it Linux is not posix.1 on processes, and so while they've "lightened" the load of threads from being a total process, they're still considered processes in the kernel & get individual process ids and thread ids. Hence you can renice them?
I guess you would see evidence of this on large thread processes like web browsers?

Look at this from a debian-based linux:
`ps -efL`

```
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
root      1568     1  1568  0   10 May05 ?        00:00:00 /usr/sbin/rsyslogd
root      1568     1  1569  0   10 May05 ?        00:00:07 /usr/sbin/rsyslogd
root      1568     1  1570  0   10 May05 ?        00:00:00 /usr/sbin/rsyslogd
root      1568     1  1571  0   10 May05 ?        00:00:00 /usr/sbin/rsyslogd
root      1568     1  1572  0   10 May05 ?        00:00:00 /usr/sbin/rsyslogd
root      1568     1  1573  0   10 May05 ?        00:00:00 /usr/sbin/rsyslogd
root      1568     1  1574  0   10 May05 ?        00:00:00 /usr/sbin/rsyslogd
root      1568     1  1575  0   10 May05 ?        00:00:00 /usr/sbin/rsyslogd
root      1568     1  1576  0   10 May05 ?        00:00:00 /usr/sbin/rsyslogd
root      1568     1  1577  0   10 May05 ?        00:00:05 /usr/sbin/rsyslogd
```

Note how the main rsyslogd has a PID (1568) and then subsequent threads also do (1569-1577).

(I programmed a long time ago on a linux [opensuse?]which had this horrible implementation of threading based solely in userland; linux-threads was its name. It was quicker to fork processes. It was based on, I think SunOS 2 which also had that horrible threading model).

In FreeBSD, however, they follow posix which determines that nice values are process scope/process wide. Therefore all threads inside it after a fork(2) get the same nice because they are the same process.

For example, take mysql on FreeBSD:
`ps -Haux -o nlwp,tdname,tdaddr`

```
USER      PID  %CPU %MEM    VSZ    RSS TT  STAT STARTED       TIME COMMAND          NLWP TDNAME                TDADDR
mysql   15074   0.0 27.8 521100 265632  -  I    Sun20      0:01.89 /usr/local/libex   34 mysqld              dd234780
mysql   15074   0.0 27.8 521100 265632  -  I    Sun20      0:00.00 /usr/local/libex   34 mysqld              df294000
mysql   15074   0.0 27.8 521100 265632  -  I    Sun20      0:00.00 /usr/local/libex   34 mysqld              df7cb3c0
mysql   15074   0.0 27.8 521100 265632  -  S    Sun20      0:18.52 /usr/local/libex   34 mysqld              df008b40
mysql   15074   0.0 27.8 521100 265632  -  I    Sun20      0:00.44 /usr/local/libex   34 mysqld              dd0493c0
mysql   15074   0.0 27.8 521100 265632  -  I    Sun20      0:00.47 /usr/local/libex   34 mysqld              dcf67b40
mysql   15074   0.0 27.8 521100 265632  -  I    Sun20      0:00.41 /usr/local/libex   34 mysqld              dfdf8780
mysql   15074   0.0 27.8 521100 265632  -  I    Sun20      0:00.44 /usr/local/libex   34 mysqld              dcdc93c0
```
Note, there is no distinction or PID for the threads. While there is a count of them and their addresses, there is no attempt by FreeBSD to maintain threads like processes. Ergo, no nice!

Edit: I should add, there are thread ids associated with a thread, but these cannot be renice(8) or setpriority(2)


----------



## GrandAdmiralThrawn (May 12, 2021)

Thank you very much, that is more clear to me. I am mostly used to older RedHat-based Enterprise Linux systems, but I just now examined the Raspberry Pi I got yesterday (It's running a modern Linux, because PiHole), and I can indeed still renice individual threads.

Also, my apologies, for my question was actually a bit stupid. I could have just looked at the manpage of ps(1) and its output, as you suggest, or like e.g. `$ ps -Hp <PID goes here> -o nice`, which displays nice values for all threads of that PID on FreeBSD.

Alternatively, I could have also gotten the information from htop(1), which I've got running on a tmux(1) session already anyway. Once I renice a CPU-intensive multithreaded process, htop shows lots of green (instead of blue) color all over the place, indicating that multiple threads have just been reniced.

Guess I should (still) read more man pages before asking questions... ​


----------



## mark_j (May 12, 2021)

I forgot I didn't address your question specifically on process groups, but in short yes, it does. Process groups are rather limited, mainly to terminals/shells and honestly, I don't see much benefit in modifying nice for them. YMMV. Anyway, for further reading, see getpgrp(2), Description section.

I must admit, the Linux approach confuses me. I guess because a thread is treated like a process they deem it needs to be able to be reniced.  Oh well, it works for them but makes it confusing for people coming from a Linux-based OS.

I'm glad I could help (and only stupid people don't ask questions).


----------



## ralphbsz (May 12, 2021)

mark_j said:


> I must admit, the Linux approach confuses me. I guess because a thread is treated like a process they deem it needs to be able to be reniced.  Oh well, it works for them but makes it confusing for people coming from a Linux-based OS.


There is a lot of history there. I was using Linux when it first grew threads, and I was already using threads on other OSes (HP-UX, VMS). Threads on Linux started with using a special version of fork() which created a new process, just without the memory mapping (in the same address space as the parent thread). That threading model has worked surprisingly well for Linux: Sure, processes are heavy-weight, but with enough optimization their weight can be handled. With some major challenges along the way, for example when async IO had to be faked with threads, and having tens of thousands of threads didn't work well (I remember doing that on a 2.2 kernel, and it was a disaster). But for normal multi-threaded programs, this is the reason why every thread walks and talks like a process. The fact that it can be (re-)niced is probably an unintended benefit or side effect. Another side-effect is that because fork() is used extremely heavily in multi-threaded processes, it has been carefully polished and optimized, so Linux' fork call is highly efficient.

Most other OSes have a very different history of their thread implementations, so they can be quite different. That's particularly true of ones inspired by Sun's fiber-based threads.


----------



## GrandAdmiralThrawn (May 12, 2021)

To reply to mark_j: Process groups do make sense to me because I use pipes a lot. And processes connected by pipes are members of the same process group. Sometimes, multiple processes which are members of the same group may be multi-threaded by themselves and may consume lots of CPU resources simultaneously. This is the case at least for me.

To be more precise, this is about video transcoding. E.g. I may have a multi-threaded decoder piped to a multi-threaded AI-based deinterlacer, again piped to a multi-threaded scaler, then piped to a multi-threaded encoder.

So the idea is this: I have multiple processes, joined together by pipes. They form a process group. Now I overcommit my CPU with multiple such process groups (I call them "tasks"), because I kinda want to "fire & forget" them for days or even weeks. I am aware that there are penalties involved here (like context switching), but I'll accept those. It appears that scaling is still fine for my tasks for an overcommit ratio of 2-4x.

Alright, but sometimes, I want a specific task (consisting of multiple processes and many threads) to complete faster, so I would like to re-nice the entire group, including all of its processes and their threads in one go. That was my intention.

And it seems that that is super-easy on FreeBSD actually. ​


----------

