作为一个已经被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等待内核的返回吗?还是记住当前的线程的状态然后切换其他线程执行,等到内核返回的之后再将之前的线程调度回来再继续执行吗?还是其他的策略呢?