1つ:RunLoopとスレッドの関係

1つ:RunLoopとスレッドの関係

ここでのRunLoopのソースコードは2つのアドレスのソースコードを指します。1つはGitHubのgithub.com/apple/swift…にあるrunloopのソースコードで、もう1つはopensource.apple.com/にあるrunloopのソースコードです。 source / CF /、主に後者が優先されます。後者はiOSのバージョンに近いと推測され、前者は他のプラットフォームの実装バージョンである必要があるためです。

RunLoop

RunLoopは、スレッド関連のインフラストラクチャの一部です。一般的に、スレッドはタスクの実行後に終了しますが、クリックイベントなどを継続的に処理する必要があるメインスレッドなどのCocoaTouchでは機能しません。Runloopの目的は、タスクがあるときにスレッドが機能し、タスクがないときにスレッドが停止できるようにすることです。このモデルは一般に**イベントループと呼ばれ、**はイベントループであり、通常、ロジックは次のとおりです。

void CFRunLoopRun(void) {
    int result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(),
                                      KCFRunLoopRunLoopDefaultMode, ...);
    } while (KCFRunLoopRunStopped != result && KCFRunLoopRunFinished != result);
}
复制代码

イベントループは、プログラム内のイベントまたはメッセージを待機およびディスパッチするためのデザインパターンです。イベントループがプログラムの中央制御フローを形成する場合、通常はメインループと呼ばれます。イベントループには、Windowsプログラムのメッセージループ、MacOSのイベントループなど、多くのプログラムで実用的なアプリケーションがあります。

iOSでは、RunLoopは、処理する必要のあるイベントを管理し、それを処理するためのエントリ関数を提供するオブジェクトです。

let runloop = RunLoop.current()
runloop.add(Port.init(), forMode: .common)
runloop.run()
复制代码

スレッドがこの関数を実行した後、ループが終了して関数が戻るまで、「メッセージの受け入れ->待機->処理」のループになります。

在iOS中有两个类来管理RunLoop,一个是RunLoop类,一个是CFRunLoopRef。CFRunLoopRef是CoreFoundation框架封装了,提供了面向对象的API,所有这些API都是线程安全的。RunLoop是基于CFRunLoop的封装,提供了面向对象的API,但是这些API并不是线程安全的。

同时要注意RunLoop实例调用的**run()**方法并不是直接调用的该方法,而是封装的以下方法:

extension RunLoop {
	public func run() {
        while run(mode: .default, before: Date.distantFuture) { }
    }

    public func run(until limitDate: Date) {
        while run(mode: .default, before: limitDate) && limitDate.timeIntervalSinceReferenceDate > CFAbsoluteTimeGetCurrent() { }
    }

    ......
}
复制代码

RunLoop和线程

iOS中的线程,我们一般是使用Thread以及pthread_t 来管理的。通过开源的源码可以看到,**Thread是封装了pthread_t的,**而pthread_t是直接包装最底层的mach thread。同时Thread和pthread_t是一一相关的。

在Swift的Runloop中,Runloop对象是对CFRunLoop的封装,而CFRunLoop是基于pthread_t来进行管理的。

以下是RunLoop的部分源码:

typedef pthread_mutex_t CFLock_t;

/// 全局的dictionary:key是pthread_t, value是CFRunLoopRef
static CFMutableDictionaryRef loopsDic = NULL;
// 访问loopsDic的锁
static CFLock_t loopLock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;

// ** 这个方法只能通过Foundation来调用
// 比如Swift的Runloop封装CFRunloop,调用了_CFRunLoopGet2这个API,这个方法内部调用了_CFRunloopGet0
// 获取一个pthread对应的Runloop
CFRunLoopRef _CFRunloopGet0(pthread_t thread) {
    // 1、首先添加互斥锁
    pthread_mutex_lock(&loopLock);

    //2、第一次进入,初始化全局Dic,并先为主线程创建一个RunLoop
    if (!loopDic) {
        loopsDic = CFDictionaryCreateMutable();
        CFRunloopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_up());
        CFDictionarySetValue(loopsDic, pthread_main_thread_up(), mainLoop);
    }

    //3、直接从全局Dic中获取
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(loopsDic, thread);

    //4、如果取不到,那就创建一个
    if (!loop) {
        loop = __CFRunLoopCreate(thread);
        CFDictionarySetValue(loopDic, thread, loop);
     }

    //5、保证线程安全
    // 注册回调关联:当thread被销毁时,这个runloop也会被销毁!
    if (pthread_equal(t, thread_self())) {
        //TSD:Thread-share data 线程共享数据
        //这里当前thread会持有tsdTable,同时tsdtable->data[__CFTSDKeyRunLoop]的值设为loop
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);

        if (0 == _CFGetTSD(__CFTSDKeyRunloopCntr)) {
            _CFSetTSD(_CFTSDKeyRunLoopCntr, __CFFinalizeRunLoop);
        }
    }
    return loop;
}
复制代码

线程和RunLoop之间是一一对应的,保存在了一个全局的Dictionary中。线程创建的时候是没有RunLoop的,如果不主动获取,那它一直都不会有。RunLoop的创建是发生在第一次获取时,RunLoop的销毁是发生线程结束时。线程结束时会调用 __CFFinalizeRunLoop 方法,这个方法中会销毁全局的Dictionary中的Key-Value对。

除了全局的Dictionary之外,两者之间还有互相持有的关系,其一是CFRunLoop的结构体中持有pthread_t, 其二是pthread_t的TSD数据中也持有runloop。

// runLoop中持有pthread
struct __CFRunLoop {
    ...
    _CFThreadRef _pthread;
    CFMutableSetRef _modes;
    ...
}

// pthread中持有runloop
static void __CFSDSetSpecific(void *arg) {
	pthread_setspecific(_CFTSDIndexKey, arg);
}

static __CFTSDTable *__CFTSDGetTable(const Boolean create) {
    ...
    __CFTSDTable *table = (__CFTSDTable *)__CFTSDGetSpecific();
    __CFTSDSetSpecific(table);
    ...
	return table;
}

_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
复制代码

TSD

什么是pthread_t的TSD数据呢?

T SDのフルネーム:スレッド固有のデータはスレッド固有のデータです。TSDは、特定の各スレッドによって維持されます。TSDはワンキーマルチバリューテクノロジーを採用しています。つまり、1つのキーが複数の異なる値に対応し、各スレッドはデータにアクセスするときにキーにアクセスすることで対応するデータを取得します。

スレッド固有のデータは、キーを行インデックス、スレッドIDを列インデックスとして2次元配列として表示できます。スレッド固有のデータのキーは不透明(OPAQUE)型のpthread_key_tデータ型です。プロセス内のすべてのスレッドがこのキーを使用できます。すべてのスレッドが同じキーを使用している場合でも、このキーを介してアクセスまたは変更するスレッド固有のデータは異なります。

キー T1スレッド T2スレッド T3スレッド T4スレッド
K1(__ CFTSDKeyRunLoop) 6 56 4 3
K2 87 21 0 9
K3 23 12 61 2
K4 11 76 47 88

上記の表を例にとると、スレッドT2で使用されるK3に対応するデータは12であり、スレッドT3で使用されるK3に対応するデータは61です。

コード内の上記のコードの最後の行に対応します_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);。keyは_CFFTSDKeyRunLoopであり、valueは現在のループです。つまり、スレッドは現在のループを対応するスレッド固有のデータに格納します。

要約する

上記は、RunLoopとスレッドの関係について説明しています。厳密に言えば、相互に保持するだけでなく、対応する関係を格納するためのグローバルハッシュテーブルもあります。このハッシュテーブルのキーはスレッドであり、値はRunLoopオブジェクトです。 。

おすすめ

転載: juejin.im/post/7119400023195910151