【libuv高效编程】libuv学习超详细教程7——libuv thread 线程句柄解读

libuv系列文章

linux线程

从很多Linux的书籍我们都可以这样子描述进程和线程的:进程是资源管理的最小单位,线程是程序执行的最小单位。

libuv是的底层其实有使用线程池对多个线程进行管理,而且它也提供了用户创建线程的功能,今天我们来学习一下libuv线程相关的知识。

线程是操作系统能够调度和执行的基本单位,在Linux中也被称之为轻量级进程。在Linux系统中,一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执行。一个进程可以拥有多个线程,它还可以同时使用多个cpu来执行各个线程,以达到最大程度的并行,提高工作的效率;同时,即使是在单cpu的机器上,也依然可以采用多线程模型来设计程序,使设计更简洁、功能更完备,程序的执行效率也更高。

从上面的这些概念我们不难得出一个非常重要的结论:线程的本质是一个进程内部的一个控制序列,它是进程里面的东西,一个进程可以拥有一个线程或者多个线程。

POSIX

我们可以先了解一个标准:可移植操作系统接口(英语:Portable Operating System Interface,缩写为POSIX),POSIX是IEEE为要在各种UNIX操作系统上运行软件,而定义API的一系列互相关联的标准的总称,其正式称呼为IEEE Std 1003,而国际标准名称为ISO/IEC 9945。此标准源于一个大约开始于1985年的项目。POSIX这个名称是由理查德·斯托曼(RMS)应IEEE的要求而提议的一个易于记忆的名称。它基本上是Portable Operating System Interface(可移植操作系统接口)的缩写,而X则表明其对Unix API的传承。

注:以上介绍来自维基百科:https://zh.wikipedia.org/wiki/POSIX

在Linux系统下的多线程遵循POSIX标准,而其中的一套常用的线程库是 pthread ,它是一套通用的线程库,是由 POSIX 提出的,因此具有很好的可移植性,我们学习多线程编程,就是使用它,必须包含以下头文件:

#include <pthread.h>

除此之外在链接时需要使用库libpthread.a。因为pthread的库不是Linux系统的库,所以在编译时要加上 -lpthread 选项。

创建线程

pthread_create()函数是用于创建一个线程的,创建线程实际上就是确定调用该线程函数的入口点,在线程创建后,就开始运行相关的线程函数。若线程创建成功,则返回0。若线程创建失败,则返回对应的错误代码。

函数原型:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                    void *(*start_routine) (void *), void *arg);

参数:

  • thread:指向线程标识符的指针。
  • attr:设置线程属性。
  • start_routine:start_routine是一个函数指针,指向要运行的线程入口。
  • arg:运行线程时传入的参数。

线程的分离状态

什么是线程的分离状态呢?在任何一个时间点上,线程是可结合的(joinable),或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。

总而言之:线程的分离状态决定一个线程以什么样的方式来终止自己。

进程中的线程可以调用以下函数来等待某个线程的终止,获得该线程的终止状态,并收回所占的资源,如果对线程的返回状态不感兴趣,可以将rval_ptr设置为NULL。

int pthread_join(pthread_t tid, void **rval_ptr)

除此之外线程也可以调用以下函数将此线程设置为分离状态,设置为分离状态的线程在线程结束时,操作系统会自动收回它所占的资源。设置为分离状态的线程,不能再调用pthread_join()等待其结束。

int pthread_detach(pthread_t tid)

如果一个线程是可结合的,意味着这条线程在退出时不会自动释放自身资源,而会成为僵尸线程,同时意味着该线程的退出值可以被其他线程获取。因此,如果不需要某条线程的退出值的话,那么最好将线程设置为分离状态,以保证该线程不会成为僵尸线程。

如果在创建线程时就知道不需要了解线程的终止状态,那么可以通过修改pthread_attr_t结构中的detachstate属性,让线程以分离状态启动,调用函数如下:

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)

如果想要获取某个线程的分离状态,那么可以通过以下函数:

int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);

若函数调用成功返回0,否则返回对应的错误代码。

参数:

  • attr:指向一个线程属性的指针。
  • detachstate:如果值为PTHREAD_CREATE_DETACHED,则表示线程是分离状态,如果值为PTHREAD_CREATE_JOINABLE则表示线程是结合状态。

其实linux线程库中还有其他的api函数,这里就不做过多的赘述。

libuv的线程处理

因为libuv是一个跨平台的框架,它的底层处理可以在Windows、可以在linux,所以线程的实现它也是视平台而定的,在这里我们只讲解linux平台下的处理,当然对应的Windows也是差不多的。

线程也是有生命周期的,可以把线程当做一个handle,那么libuv的线程就是thread handle。

数据类型

uv_thread_t 是 thread handle 的数据类型,通过它可以定义一个 thread handle 的实例。

uv_thread_t

github/libuv/include/uv/unix.h文件中存在以下的定义,它就是linux下线程的数据结构,实际上就是使用了linux平台本身的线程库 pthread

typedef pthread_t uv_thread_t;

线程主体

既然能创建线程就必须有一个线程主体去执行线程里面的工作,在linux线程库 pthread 中定义的线程主体是void *(*start_routine) (void *)类型,而libuv则是以下类型:

void (*entry)(void *arg)
  • 传入了一个参数arg,它由用户创建线程时指定。

libuv创建线程

因为线程是一个handle,所以在使用的时候也是需要创建的,libuv创建线程的函数是uv_thread_create()

int uv_thread_create(uv_thread_t *tid, void (*entry)(void *arg), void *arg);

传入的参数:

  • tid:线程句柄
  • entry:线程主体。
  • arg:线程参数。

源码的实现如下:

int uv_thread_create(uv_thread_t *tid, void (*entry)(void *arg), void *arg) {
  uv_thread_options_t params;
  params.flags = UV_THREAD_NO_FLAGS;
  return uv_thread_create_ex(tid, &params, entry, arg);
}

/* 这才是真正创建线程的地方 */
int uv_thread_create_ex(uv_thread_t* tid,
                        const uv_thread_options_t* params,
                        void (*entry)(void *arg),
                        void *arg) {
  int err;
  pthread_attr_t* attr;
  pthread_attr_t attr_storage;
  size_t pagesize;
  size_t stack_size;

  /* Used to squelch a -Wcast-function-type warning. */
  union {
    void (*in)(void*);
    void* (*out)(void*);
  } f;

  stack_size =
      params->flags & UV_THREAD_HAS_STACK_SIZE ? params->stack_size : 0;

  attr = NULL;
  if (stack_size == 0) {
    stack_size = thread_stack_size();
  } else {
    pagesize = (size_t)getpagesize();
    /* Round up to the nearest page boundary. */
    stack_size = (stack_size + pagesize - 1) &~ (pagesize - 1);
#ifdef PTHREAD_STACK_MIN
    if (stack_size < PTHREAD_STACK_MIN)
      stack_size = PTHREAD_STACK_MIN;
#endif
  }

  if (stack_size > 0) {
    attr = &attr_storage;

    if (pthread_attr_init(attr))
      abort();

    if (pthread_attr_setstacksize(attr, stack_size))
      abort();
  }

  /* 最终调用线程库 pthread 的pthread_create()函数创建线程*/
  f.in = entry;
  err = pthread_create(tid, attr, f.out, arg);

  if (attr != NULL)
    pthread_attr_destroy(attr);

  return UV__ERR(err);
}

libuv的线程分离状态

其实这个就非常简单了,直接是依赖linux的线程库的函数,具体见:

int uv_thread_join(uv_thread_t *tid) {
  return UV__ERR(pthread_join(*tid, NULL));
}

libuv线程的其他API

这里面的API很多都是依赖linux的线程库的函数,我就简单列举一下:

  • 获取线程句柄:
uv_thread_t uv_thread_self(void) {
  return pthread_self();
}
  • 判断线程是否相同:
int uv_thread_equal(const uv_thread_t* t1, const uv_thread_t* t2) {
  return pthread_equal(*t1, *t2);
}

当然啦,其实还有很多互斥锁、信号量相关的实现都是差不多的,说白了就是在操作系统之上抽象了一个操作系统模拟层。

example

本章的学习是下一章的基础,就简单用一个龟兔赛跑的故事来实现两个线程吧,在例程中创建两个线程句柄,分别为tortoise、hare,他们在长度固定的跑到比赛,假设为10米,乌龟慢一点,3秒跑一米,兔子快一点,1秒跑一米,代码的实现如下:

#include <stdio.h>
#include <unistd.h>

#include <uv.h>

void hare_entry(void *arg) 
{
    int track_len = *((int *) arg);
    while (track_len) {
        track_len--;
        sleep(1);
        printf("hare ran another step\n");
    }
    printf("hare done running!\n");
}

void tortoise_entry(void *arg) 
{
    int track_len = *((int *) arg);
    while (track_len) 
    {
        track_len--;
        printf("tortoise ran another step\n");
        sleep(3);
    }
    printf("tortoise done running!\n");
}

int main() {
    int track_len = 10;
    uv_thread_t hare;
    uv_thread_t tortoise;
    uv_thread_create(&hare, hare_entry, &track_len);
    uv_thread_create(&tortoise, tortoise_entry, &track_len);

    uv_thread_join(&hare);
    uv_thread_join(&tortoise);
    return 0;
}

编译运行后的结果如下:

tortoise ran another step
hare ran another step
hare ran another step
tortoise ran another step
hare ran another step
hare ran another step
hare ran another step
tortoise ran another step
hare ran another step
hare ran another step
hare ran another step
tortoise ran another step
hare ran another step
hare ran another step
hare done running!
tortoise ran another step
tortoise ran another step
tortoise ran another step
tortoise ran another step
tortoise ran another step
tortoise ran another step
tortoise done running!

参考

libuv官方文档

例程代码获取

libuv-learning-code

猜你喜欢

转载自blog.csdn.net/jiejiemcu/article/details/105687789