4

I make experiment with RunLoop. I was create simple Mac OS console application and call only one line of code.

RunLoop.current.run()

After that in Debug navigator appearance second thread. Why?

enter image description here enter image description here

Citrael
  • 542
  • 4
  • 17

1 Answers1

5

Grand Central Dispatch (GCD) provides a “main queue” (accessible in Swift using DispatchQueue.main). The main queue always runs its blocks on the main thread.

Since apps on Apple platforms are usually running RunLoop.main on the main thread, the run loop works with GCD to run the blocks added to the main queue.

So, when the main thread's run loop is created, it creates some GCD objects, and that makes GCD initialize itself. Part of that GCD initialization involves creating a “work queue” and a pool of threads for running jobs added to the work queue.

You can see that it is the creation of the run loop, not the running of it, that creates the threads. Here's a sample program:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    [NSRunLoop currentRunLoop]; // line 4
    return 0;                   // line 5
}

In a Terminal, I run lldb (the debugger). I tell it to debug the test program, set a breakpoint on line 4, and run. When it stops at the breakpoint (before the call to currentRunLoop, I list all threads:

:; lldb
"crashlog" and "save_crashlog" command installed, use the "--help" option for detailed help
(lldb) target create test
Current executable set to 'test' (x86_64).
(lldb) b 4
Breakpoint 1: where = test`main + 22 at main.m:4, address = 0x0000000100000f46
(lldb) r
Process 12087 launched: '/Users/mayoff/Library/Developer/Xcode/DerivedData/test-aegotyskrtnbeabaungzpkkbjvdz/Build/Products/Debug/test' (x86_64)
Process 12087 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: sp=0x00007fff5fbff240 fp=0x00007fff5fbff260 pc=0x0000000100000f46 test`main(argc=1, argv=0x00007fff5fbff280) + 22 at main.m:4
   1        #import <Foundation/Foundation.h>
   2    
   3        int main(int argc, const char * argv[]) {
-> 4            [NSRunLoop currentRunLoop]; // line 4
   5            return 0; // line 5
   6        }
Target 0: (test) stopped.
(lldb) thread list
Process 12087 stopped
* thread #1: tid = 0x1066d3, 0x0000000100000f46 test`main(argc=1, argv=0x00007fff5fbff280) at main.m:4, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1

There is only one thread. Next, I step over the call to currentRunLoop and list all threads again:

(lldb) n
Process 12087 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = step over
    frame #0: sp=0x00007fff5fbff240 fp=0x00007fff5fbff260 pc=0x0000000100000f69 test`main(argc=1, argv=0x00007fff5fbff280) + 57 at main.m:5
   2    
   3        int main(int argc, const char * argv[]) {
   4            [NSRunLoop currentRunLoop]; // line 4
-> 5            return 0; // line 5
   6        }
Target 0: (test) stopped.
(lldb) thread list
Process 12087 stopped
* thread #1: tid = 0x1066d3, 0x0000000100000f69 test`main(argc=1, argv=0x00007fff5fbff280) at main.m:5, queue = 'com.apple.main-thread', stop reason = step over
  thread #2: tid = 0x106ab3, 0x00007fffc942c070 libsystem_pthread.dylib`start_wqthread
  thread #3: tid = 0x106ab4, 0x00007fffc934244e libsystem_kernel.dylib`__workq_kernreturn + 10
  thread #4: tid = 0x106ab5, 0x00007fffc8923e85 libobjc.A.dylib`class_createInstance + 142, queue = 'com.apple.root.default-qos.overcommit'

Now there are four threads, and some of them are stopped in the middle of initializing.

But Rob,” you say, “when I run test in Xcode and stop before the call to currentRunLoop, it already has four threads!” as shown here:

debugging under Xcode

“Indeed it does,” I reply. If you run menu item Debug > Debug Workflow > Shared Libraries…, and type Xcode into the filter box, you can discover why:

shared libraries from Xcode

When you run the program under Xcode, Xcode injects some extra shared libraries into your process to provide extra debugging support. These shared libraries include initialization code that runs before your code runs, and that initialization code does something with GCD, so GCD gets initialized (creating its thread pool) before your first line of code runs.

The work queue adjusts the size of its thread pool based on workload. Since nothing is adding jobs to the queue, it immediately shrinks its pool to just one background thread. That's why when you look in Xcode's CPU Report, you only see two threads: the main thread running the run loop, and one worker thread waiting for a job to run.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • 1
    Thank you for your detailed explanation! How did you know that? I could not find the information on the apple ( – Citrael Jul 02 '17 at 08:43
  • 1
    I figured it out by debugging the test in Terminal and some educated guesses. [This page](http://newosxbook.com/articles/GCD.html) has lots of interesting information, but I didn't find it until after posting my answer. – rob mayoff Jul 02 '17 at 17:26