I recently started sampling applications that freeze on my machine. I have an iMac with 8 cores and 32GB of RAM running Catalina 10.15.1 fully patched. It's far from resource constrained yet certain applications freeze regularly. I see threads marked as unknown often in the traces. Are these normal threads? If so what would make a thread show up as completely unknown in a sample. It seems strange that the library a thread is linked to could be known in some cases but unknown in others.
I think you're confused about what parts of the output actually represents threads:
Yes, the threads you see in the output from "Sample Process" in Activity Monitor are definitely "normal threads".
However, each of the lines in your screenshot do not represent a thread each.
Activity Monitor clearly labels each seperate thread with the word "Thread" - the whole line is in lightly blue color (compared to the rest that is black).
The screenshot you have does not indicate that a thread is "unknown", nor does it indicate that "the library a thread is linked to" is unknown.
I'll explain how to read the output from "Sample Process":
Each blue line indicates a thread.
Each black line below the blue line is a function that has been executed by that thread.
The percentage indicates how much time was spent inside the function. You can choose when viewing if you want it displayed as a percent of the thread or the parent.
The number in the brackets (such as [0x1100007ffd]) is the memory address of the function.
The field right next to the percentage is the name of the function, and in parentheses, the binary file from which the original contents on the memory address was loaded.
Here's why unknowns can appear:
Note that the name of the function is only available if the function was loaded in from a binary that contained what's known as a "symbol map". This makes it possible for the sample program to known that the code at a given memory address was generated from a function with that specific name in the original source code. If you haven't got that information, it is listed as ???.
Typically you'll see that Objective-C programs will show you these names, while C programs typically won't. The developer of a C program chooses whether or not to expose the names, but for performance/size reasons, these are typically left out in programs meant for "the public" (as opposed to internal testing).
Similarly the name of the binary file is known only if code in question was loaded in via the actual program binary itself, or by the dynamic linker from a .dylib or similar shared library. If the code at the memory address came to be there for any other reason, the sample program cannot know the name and it is displayed as unknown.
In addition you'll often see that programs that internally employ dynamic languages, JIT compilers, interpreters, etc. will show up as "
??? (in unknown binary)" in these listings. This is because the code at that specific location wasn't there from the start - it was generated dynamically by the program as it executed.
If you do not know the meaning of the word thread, an rough, high-level explanation is as follows:
Think of a CPU as a machine that executes instructions in a sequence. The current state of the CPU itself is the contents of the registers (internal memory locations inside the CPU that are seperate from RAM) as well as the program counter (the address of the instruction currently being executed). Each time an instruction is executed, the registers can be changed by the computation, and the PC is set to a new address. Typically the PC is set to address of the next instruction in memory, but it can change arbitrarily. In this view, the CPU executes a single program.
In order to (seemingly) run multiple programs at the same time, the system is setup so that CPU executes a single program as described above, but at various times "switches program" by saving its state (registers and PC) to RAM, and loading in another state. Thereafter it is now executing at a different place in memory. By "switching" back and forth between these programs, you get the illusion of running multiple programs at the same time.
Each of these "programs" are actually what is known as a thread. So a thread is essentially just a saved state consisting of the values of registers and the program counter, which the operating system continually saves and restores.
In comparison, a "process" is what you observe as a running instance of an application - i.e. the whole thing you're sampling in Activity Monitor). The concept is very similar to a thread in that the operating system allows for running multiple processes at a time by continually saving/restoring state, whereby switching between processen. A process can have one or more threads, so part of switching between process is actually switching between threads as describe above. However more state is being saved/restored, as for example memory mapping are also saved/restored. This is the reason that threads within the same process can (in general) access the same memory, whereas different processes (in generally) cannot access each other's memory.
Note that in real life things are much more complicated. A system can contain multiple cores and multiple CPUs, meaning that you have multiple sets of registers and program counters at the same time. Threads will also have various attributes that are not described here.