从源码视角彻底搞懂Linux线程实现原理

1.Linux线程接口

  • pthread_create : 创建线程
  • pthread_join : 等待线程执行完毕,获取线程执行结果,释放线程资源(线程栈内存等)
  • pthread_detach: 设置线程为分离状态,线程处于unjoinable状态,如果调用pthread_join返回错误;线程执行完比自动释放资源。
  • pthread_exit : 退出当前线程。

2.pthread_create

可以参照如下文章

从线程的一生看系统调用实现原理(bionic c库为例)_nginux的博客-CSDN博客。

要点:

1. 如果不特定指定PTHREAD_ATTR_FLAG_DETACHED,线程创建完状态时:THREAD_NOT_JOINED;如果指定上面FLAG,创建完状态是:THREAD_DETACHED。

2.线程执行完成的时候,默认都会调用pthread_exit(即使程序中不显示调用pthread_exit)。

源码分析:

__BIONIC_WEAK_FOR_NATIVE_BRIDGE
int pthread_create(pthread_t* thread_out, pthread_attr_t const* attr,
                   void* (*start_routine)(void*), void* arg) {
  ErrnoRestorer errno_restorer;

  pthread_attr_t thread_attr;
  if (attr == NULL) {
    pthread_attr_init(&thread_attr);
  } else {
    thread_attr = *attr;
    attr = NULL; // Prevent misuse below.
  }

  pthread_internal_t* thread = NULL;
  void* child_stack = NULL;
  //分配线程栈内存和创建代表线程的数据结构
  int result = __allocate_thread(&thread_attr, &thread, &child_stack);
  if (result != 0) {
    return result;
  }

  // Create a lock for the thread to wait on once it starts so we can keep
  // it from doing anything until after we notify the debugger about it
  //
  // This also provides the memory barrier we need to ensure that all
  // memory accesses previously performed by this thread are visible to
  // the new thread.
  thread->startup_handshake_lock.init(false);
  thread->startup_handshake_lock.lock();

  //用户传入的线程入口函数
  thread->start_routine = start_routine;
  thread->start_routine_arg = arg;

  thread->set_cached_pid(getpid());

  int flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM |
      CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID;
  void* tls = reinterpret_cast(thread->tls);
#if defined(__i386__)
  // On x86 (but not x86-64), CLONE_SETTLS takes a pointer to a struct user_desc rather than
  // a pointer to the TLS itself.
  user_desc tls_descriptor;
  __init_user_desc(&tls_descriptor, false, tls);
  tls = &tls_descriptor;
#endif
  //内部通过linux clone系统调用实现
  int rc = clone(__pthread_start, child_stack, flags, thread, &(thread->tid), tls, &(thread->tid));
  if (rc == -1) {
    int clone_errno = errno;
    // We don't have to unlock the mutex at all because clone(2) failed so there's no child waiting to
    // be unblocked, but we're about to unmap the memory the mutex is stored in, so this serves as a
    // reminder that you can't rewrite this function to use a ScopedPthreadMutexLocker.
    thread->startup_handshake_lock.unlock();
    if (thread->mmap_size != 0) {
      munmap(thread->attr.stack_base, thread->mmap_size);
    }
    async_safe_format_log(ANDROID_LOG_WARN, "libc", "pthread_create failed: clone failed: %s",
                          strerror(clone_errno));
    return clone_errno;
  }

  int init_errno = __init_thread(thread);
  if (init_errno != 0) {
    // Mark the thread detached and replace its start_routine with a no-op.
    // Letting the thread run is the easiest way to clean up its resources.
    atomic_store(&thread->join_state, THREAD_DETACHED);
    __pthread_internal_add(thread);
    thread->start_routine = __do_nothing;
    thread->startup_handshake_lock.unlock();
    return init_errno;
  }

  // Publish the pthread_t and unlock the mutex to let the new thread start running.
  *thread_out = __pthread_internal_add(thread);
  thread->startup_handshake_lock.unlock();

  return 0;
}  

// Called from the __bionic_clone assembler to call the thread function then exit.
extern "C" __LIBC_HIDDEN__ void __start_thread(int (*fn)(void*), void* arg) {
  BIONIC_STOP_UNWIND;

  pthread_internal_t* self = __get_thread();
  if (self && self->tid == -1) {
    self->tid = syscall(__NR_gettid);
  }

  //相当于执行pthread_start函数
  int status = (*fn)(arg);
  __exit(status);
}

static int __pthread_start(void* arg) {                                                                                                                    
  pthread_internal_t* thread = reinterpret_cast(arg);

  // Wait for our creating thread to release us. This lets it have time to
  // notify gdb about this thread before we start doing anything.
  // This also provides the memory barrier needed to ensure that all memory
  // accesses previously made by the creating thread are visible to us.
  thread->startup_handshake_lock.lock();

  __init_alternate_signal_stack(thread);

  //调用到用户调用pthread_create传入的线程入口函数
  void* result = thread->start_routine(thread->start_routine_arg);

  //线程执行完都会调用pthread_exit
  pthread_exit(result);

  return 0;
}

 内核执行完clone系统调用,创建完线程之后会跳转到__start_thread执行,最终会调用到用户pthread_create传入的线程执行函数体中。

3.pthread_join

man pthread_join如下:

PTHREAD_JOIN(3)                                                Linux Programmer's Manual                                                PTHREAD_JOIN(3)

NAME
       pthread_join - join with a terminated thread

SYNOPSIS
       #include 

       int pthread_join(pthread_t thread, void **retval);

       Compile and link with -pthread.

DESCRIPTION
       The  pthread_join()  function waits for the thread specified by thread to terminate.  If that thread has already terminated, then pthread_join()
       returns immediately.  The thread specified by thread must be joinable.

       If retval is not NULL, then pthread_join() copies the exit status of the target thread (i.e., the value  that  the  target  thread  supplied  to
       pthread_exit(3)) into the location pointed to by *retval.  If the target thread was canceled, then PTHREAD_CANCELED is placed in *retval.

       If  multiple  threads  simultaneously try to join with the same thread, the results are undefined.  If the thread calling pthread_join() is can‐
       celed, then the target thread will remain joinable (i.e., it will not be detached).

要点:

1. 等待指定的线程结束,且等待的线程必须是joinable(不然报错)。

2. 多个线程pthread_join一个线程结果未知。

3. pthread_join会释放等待线程的资源。

4.pthread_join执行成功,线程状态:THREAD_JOINED。

使用场景:

线程A需要请线程B帮忙干活,且要等待线程B的结果才继续执行,属于串行执行,A负责释放线程B的资源。

pthread_join的实现源码分析:


__BIONIC_WEAK_FOR_NATIVE_BRIDGE
int pthread_join(pthread_t t, void** return_value) {
  if (t == pthread_self()) {
    return EDEADLK;
  }

  pthread_internal_t* thread = __pthread_internal_find(t);
  if (thread == NULL) {
    return ESRCH;
  }

  //将线程状态修改为:THREAD_JOINED
  ThreadJoinState old_state = THREAD_NOT_JOINED;
  while ((old_state == THREAD_NOT_JOINED || old_state == THREAD_EXITED_NOT_JOINED) &&
         !atomic_compare_exchange_weak(&thread->join_state, &old_state, THREAD_JOINED)) {
  }

  //如果等待的线程是detached(调用过pthread_detach)或者已经被pthread_join过,返回错误。
  if (old_state == THREAD_DETACHED || old_state == THREAD_JOINED) {
    return EINVAL;
  }

  pid_t tid = thread->tid;
  volatile int* tid_ptr = &thread->tid;

  // We set thread->join_state to THREAD_JOINED with atomic operation,
  // so no one is going to remove this thread except us.

  // Wait for the thread to actually exit, if it hasn't already.
  //等待线程结束
  while (*tid_ptr != 0) {
    __futex_wait(tid_ptr, tid, NULL);
  }

  //拿到等待线程的返回值
  if (return_value) {
    *return_value = thread->return_value;
  }

  //释放等待线程的资源,删除线程的数据结构和unmap栈内存
  __pthread_internal_remove_and_free(thread);
  return 0;
} 

4.pthread_detach

man pthread_detach:


NAME
       pthread_detach - detach a thread

SYNOPSIS
       #include 

       int pthread_detach(pthread_t thread);

       Compile and link with -pthread.

DESCRIPTION
       The pthread_detach() function marks the thread identified by thread as detached.  When a detached thread terminates, its resources are automati‐
       cally released back to the system without the need for another thread to join with the terminated thread.

       Attempting to detach an already detached thread results in unspecified behavior.

RETURN VALUE
       On success, pthread_detach() returns 0; on error, it returns an error number.

使用场景:

与pthread_join使用的场景相反,如果线程A请线程B帮忙做事,线程A和线程B可以并行执行,不需要等待线程B的结果反馈,此时由于可能B线程完毕了,那么B要负责自己的资源释放(比如栈内存释放)

要点:

1.detach的线程自己负责释放资源(数据结构和栈内存)

2.重复detach同一个线程是个未定义行为。

3.pthread_detach执行成功,线程状态为:THREAD_DETACHED

源码分析:

__BIONIC_WEAK_FOR_NATIVE_BRIDGE
int pthread_detach(pthread_t t) {
  pthread_internal_t* thread = __pthread_internal_find(t);
  if (thread == NULL) {
    return ESRCH;
  }
  //线程状态修改为:THREAD_DETACHED
  ThreadJoinState old_state = THREAD_NOT_JOINED;
  while (old_state == THREAD_NOT_JOINED &&
         !atomic_compare_exchange_weak(&thread->join_state, &old_state, THREAD_DETACHED)) {
  }

  if (old_state == THREAD_NOT_JOINED) {
    return 0;
  } else if (old_state == THREAD_EXITED_NOT_JOINED) {
    // Use pthread_join to clean it up.
    return pthread_join(t, NULL);
  }
  return EINVAL;
}  

上面代码看完是否产生一个疑问,man手册中说pthread_detach的线程要自己释放资源,而上面源码中并未看到释放资源的代码,线程执行完比之后是怎么实现自己释放资源的?答案是在pthread_exit接口中。 

5.pthread_exit

通过pthread_create小节的分析,每个线程创建完成后,会先执行__start_thread,该函数进而执行pthread_start函数,这里执行线程函数主题,线程退出的时候调用了pthread_exit,也就说每个线程退出的时候都会调用下pthread_exit,而这里正式detached线程释放自己释放资源的地方:

extern "C" __noreturn void __exit(int);
                                                                                                                                         1,1           Top
    memset(&ss, 0, sizeof(ss));
    ss.ss_flags = SS_DISABLE;
    sigaltstack(&ss, NULL);

    // Free it.
    munmap(thread->alternate_signal_stack, SIGNAL_STACK_SIZE);
    thread->alternate_signal_stack = NULL;
  }

  ThreadJoinState old_state = THREAD_NOT_JOINED;
  while (old_state == THREAD_NOT_JOINED &&
         !atomic_compare_exchange_weak(&thread->join_state, &old_state, THREAD_EXITED_NOT_JOINED)) {
  }

  //如果线程是DETACHED状态,释放自己的资源(栈内存和数据结构)
  if (old_state == THREAD_DETACHED) {
    // The thread is detached, no one will use pthread_internal_t after pthread_exit.
    // So we can free mapped space, which includes pthread_internal_t and thread stack.
    // First make sure that the kernel does not try to clear the tid field
    // because we'll have freed the memory before the thread actually exits.
    __set_tid_address(NULL);

    // pthread_internal_t is freed below with stack, not here.
    __pthread_internal_remove(thread);

    if (thread->mmap_size != 0) {
      // We need to free mapped space for detached threads when they exit.
      // That's not something we can do in C.

      // We don't want to take a signal after we've unmapped the stack.
      // That's one last thing we can do before dropping to assembler.
      ScopedSignalBlocker ssb;
      __pthread_unmap_tls(thread);
      _exit_with_stack_teardown(thread->attr.stack_base, thread->mmap_size);
    }
  }

  // No need to free mapped space. Either there was no space mapped, or it is left for
  // the pthread_join caller to clean up.
  __pthread_unmap_tls(thread);
  __exit(0);
} 

你可能感兴趣的:(线程,操作系统,多线程编程)