惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Tenable Blog
博客园_首页
Vercel News
Vercel News
WordPress大学
WordPress大学
美团技术团队
G
Google Developers Blog
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
Y
Y Combinator Blog
博客园 - 【当耐特】
量子位
酷 壳 – CoolShell
酷 壳 – CoolShell
The Cloudflare Blog
T
The Blog of Author Tim Ferriss
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google DeepMind News
Google DeepMind News
云风的 BLOG
云风的 BLOG
腾讯CDC
M
MIT News - Artificial intelligence
爱范儿
爱范儿
Recent Announcements
Recent Announcements
雷峰网
雷峰网
Last Week in AI
Last Week in AI
宝玉的分享
宝玉的分享
The Register - Security
The Register - Security
Jina AI
Jina AI
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Hugging Face - Blog
Hugging Face - Blog
P
Privacy & Cybersecurity Law Blog
Recorded Future
Recorded Future
Help Net Security
Help Net Security
N
News and Events Feed by Topic
博客园 - Franky
P
Proofpoint News Feed
L
LINUX DO - 热门话题
S
SegmentFault 最新的问题
The GitHub Blog
The GitHub Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
月光博客
月光博客
D
Docker
Google DeepMind News
Google DeepMind News
有赞技术团队
有赞技术团队
IT之家
IT之家
Security Latest
Security Latest
L
LangChain Blog
V
V2EX
阮一峰的网络日志
阮一峰的网络日志
J
Java Code Geeks

Sophie Alpert

There are no lossless transformations of natural-language text Deconstructing “services” I don’t want AI agents controlling my laptop Materialized views are obviously useful TODOs aren’t for doing Everyone is wrong about that Slack flowchart Hire me to empower and upskill your eng team How React Changed the Web Forever: A Documentary Fast and maintainable patterns for fetching from a database React Conf: “Building a Custom React Renderer” Why review code? Metrics by proxy Yak shaving and fixing Voice React Conf: “React Today and Tomorrow” Why we host conference talk dry runs React Podcast: Inside React Type errors with inference need stacks Observable programming React 16: an API-compatible rewrite Hi, I’m trans. A near-perfect oninput shim for IE 8 and 9 Using React to speed up the Khan Academy question editor What I did at Khan Academy, 2012 edition Preventing XSS attacks when embedding JSON in HTML Rolling back to an old revision in Mercurial (like git reset)
Initializing on the main thread using dispatch_once
Sophie Alpert · 2014-04-02 · via Sophie Alpert

We recently refactored the Khan Academy iOS app to use Core Data instead of the haphazard system of JSON files which we were previously using. In this post I’ll explain how to run initialization code that has to happen on the main thread without worrying about race conditions.

When anything in the app needs to access the database, it needs a reference to a Core Data context. Our app shares one main thread context used by all of the UI code, which runs on the main thread. The first time this context is accessed, we need to initialize the persistent store coordinator and the context. Our core data stack class looks something like this:

// KACoreDataStack.m

@implementation KACoreDataStack

+ (instancetype)defaultStack {
  static KACoreDataStack *stack;
  static dispatch_once_t onceToken;

  dispatch_once(&onceToken, ^{
    stack = [[KACoreDataStack alloc] init];
  });

  return stack;
}

- (instancetype)init {
  if (!(self = [super init])) {
    return nil;
  }

  NSAssert([NSThread isMainThread], @"init must happen on main thread");
  _mainThreadContext = ...;
  return self;
}

@end

This is the standard way to initialize an object that may be accessed on multiple threads to ensure that the initialization code runs only once; if two threads reach the dispatch_once call at the same time, the initialization will happen only once and the second thread will block until the initialization completes.

However, initialization for a Core Data main-queue context has to happen on the main thread or else deadlock can occur. Here, if [KADefaultStack defaultStack] is called on a background thread before it’s called on the main thread, the initialization will happen on the wrong thread and consequently fail.

First attempt

My first attempt to fix this was to check within the dispatch block whether we’re on the main thread and if not, dispatch to the main thread to do the initialization:

+ (instancetype)defaultStack {
  static KACoreDataStack *stack;
  static dispatch_once_t onceToken;

  dispatch_once(&onceToken, ^{
    if ([NSThread isMainThread]) {
      stack = [[KACoreDataStack alloc] init];
    } else {
      dispatch_sync(dispatch_get_main_queue(), ^{
        stack = [[KACoreDataStack alloc] init];
      });
    }
  });

  return stack;
}

Unfortunately, there’s a race condition here. If + defaultStack is called on a background thread then is called on the main thread immediately after, the main thread’s dispatch_once will wait for the background thread’s dispatch_once to finish, but the dispatch_sync call will wait for the main thread to finish its current run loop, causing deadlock.

Second attempt

I then tried refactoring so that dispatch_once is always called from the main thread:

+ (instancetype)defaultStack {
  static KACoreDataStack *stack;
  static dispatch_once_t onceToken;

  dispatch_sync(dispatch_get_main_queue(), ^{
    dispatch_once(&onceToken, ^{
      stack = [[KACoreDataStack alloc] init];
    });
  });

  return stack;
}

Unfortunately, this deadlocks consistently if ever called from the main thread since dispatch_sync again waits for the main thread to finish, not realizing that we’re already on the main thread.

A working solution

Our last solution was completely broken but gives us the pieces we need to build a working one. When we’re on the main thread, we want to perform the initialization immediately; when we’re on a background thread we want to dispatch to the main thread then initialize. We can write this logic as follows:

+ (instancetype)defaultStack {
  static KACoreDataStack *stack;
  static dispatch_once_t onceToken;

  if ([NSThread isMainThread]) {
    dispatch_once(&onceToken, ^{
      stack = [[KACoreDataStack alloc] init];
    });
  } else {
    dispatch_sync(dispatch_get_main_queue(), ^{
      dispatch_once(&onceToken, ^{
        stack = [[KACoreDataStack alloc] init];
      });
    });
  }

  return stack;
}

This works, though is inefficient in the common case that we’re fetching the already-initialized stack from a background thread; we’ll end up dispatching to the main thread even if it’s completely unnecessary.

We’d prefer to skip the dispatch_sync call if the initialization has happened already. The static variable onceToken is set by default to 0 and if we look at the libdispatch source, we can see that it is set to ~0 once the block has run. (Note that DISPATCH_EXPECT expands to __builtin_expect, a compiler primitive provided to help with branch prediction.)

A reusable helper

We can take advantage of this and write a reusable helper for solving this problem:

void dispatch_once_on_main_thread(dispatch_once_t *predicate,
                                  dispatch_block_t block) {
  if ([NSThread isMainThread]) {
    dispatch_once(predicate, block);
  } else {
    if (DISPATCH_EXPECT(*predicate == 0L, NO)) {
      dispatch_sync(dispatch_get_main_queue(), ^{
        dispatch_once(predicate, block);
      });
    }
  }
}

With this helper, we can simply call dispatch_once_on_main_thread instead of dispatch_once in + defaultStack, and the initialization will always happen on the main thread.

Arguably, setting up Core Data shouldn’t require being on the main thread, but it does as of iOS 7. This helper is useful for this case and others where thread-unsafe initializers need to be run.


Update (2018/02/27): Stephan Tolksdorf warns that this technique is risky because it can deadlock if the main thread is already blocked on the thread running this code and notes that newer versions of libdispatch have slightly different implementations which may be more correct.