小小书童观战Thread调度

作为一个已经被PHP阉割的码农来说,探讨这个话题其实还是有点吃力的。首先什么是调度,全国火车运营中心要管理和调度全国的火车运行,什么时候发车,什么时候让车,什么时候再启动出发,什么时候准点到达目的地。火车调度中心如果想调度这些火车它必须有以下信息才能调度: 车次(ID),车目前运行到哪里了(status),其他跟他行驶一条道的车目前是什么状态(other_threads) ..... 当运营中心有了全部火车的这些信息,再加上稳定的环境(不发生地震),基本上可以完成调度任务(当然实际情况要比这复杂地多)。

操作系统的调度其实跟火车的调度类似,就是要记住每一个线程的状态,根据一定的策略调度这些线程去完成每一个线程赋予的使命。C/C++的多线程调度就是基于操作系统来完成的。要搞清楚线程的调度过程,先弄明白两个东西: 用户线程和内核线程。因为用户线程是第三方库或者说是用户态完成的调度。内核线程是利用操作系统的调度来完成调度(他们的创建也是一样的)。

好,那我们先得去了解用户态线程如何调度,再了解内核态线程如何调度,最后用户态线程和内核态线程之间如何完成交互的。

先看下内核线程如何创建出来的,Linux发家史告诉我们linux2.4之前没有引入线程的概念,2.4之后linux-kernel才增加了轻量级进程(也就是我们所说的线程)。线程和进程对于linux来说都是进程,因为核心数据结构都是task_struct:

struct task_struct {

      struct thread_info *thread_info;  // 指向进程或者线程的基本信息

      struct mm_struct *mm; // 进程或者线程的页表和虚拟内存 

      struct mm_struct *active_mm; // 内核的页表目录

      struct fs_struct *fs; // 文件系统对象

      struct file_struct *files; // 句柄对象

      struct signal_struct *sinall; // 信号量

      usigned_int pid; 

      usigned_int tid;

      usigned_int tgid;

      usigned_int pgid;

      usigned_int sid;

        ..... 

}

结构体中存放了某个进程或者线程的所有信息。clone() --> do_fork() ---> copy_process(), copy_process()会创建并拷贝当前进程的tast_struct, 同时创建这个子进程的thread_info结构。将task_struct放到调度队列里面返回在队列里面的索引值,即pid。clone的时候根据参数来选择要不要共享打开的文件,文件系统信息,信号处理函数,进程的地址空间。这就是进程和线程不一样的本质所在。

内核线程的调度由操作系统的调度策略决定,操作系统的调度策略大致有:分时调度策略, 实时调度策略等等,这里不打算介绍这些。

再看下用户线程如何创建,用户态线程的创建通过pthread库提供的API函数来完成。码农最常用到的API有:

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

int  pthread_cancel( pthread_t thread );

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

int  pthread_detach( pthread_t  thread);

....

pthread_create函数的第一个参数thread存放创建出来的线程的ID,第二参数pthread_attr_t *attr定制各种线程属性而存在的,如:

1) __detachstate: 与进程中的其他线程脱离关系,默认情况下是:
PTHREAD_CREATE_JOINABLE表示不脱离进程,PTHREAD_CREATE_DETACHED表示脱离进程的管理,执行结束后资源自行释放
2) __schedpolicy:线程的调度策略,实时还是非实时,先入先出还是轮转
...

第三个参数是线程的入口函数的起始地址,第四个参数是线程入口函数的参数。入口函数的参数传递是一个参数的时候可以直接传递,多个参数的时候可以组装成一个struct传递。
pthread_create通过一层层的封装最终调用clone函数创建线程:__pthread_create_2_1 -> create_thread() -> do_clone,传给do_clone的标记是CLONE_VM , CLONE_FS , CLONE_FILES

pthread_cancel,pthread_join,pthread_detach函数都是在管理线程和创建线程的进程之间的关系,至于创建出来的n个线程如何调度,需要pthread库给出一个用户态线程调度策略。比方说:用户态线程发起了一个长时间的内核操作,那么用户态线程是一直占用CPU等待内核的返回吗?还是记住当前的线程的状态然后切换其他线程执行,等到内核返回的之后再将之前的线程调度回来再继续执行吗?还是其他的策略呢?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容