心中先有一个抽象的概念,抽象的模型。考虑一下以下这些问题
1,线程是怎么工作的,只要执行完所有任务它就挂了。
2,事件(event)是怎么接收的?例如I/O事件,用户触摸屏幕后事件是怎样传递的?线程是怎么捕获这些事件的?
3,GUI程序或CLI程序有什么区别?
这个程序只有一条线程,当函数return后线程也就挂了,由于这是唯一的线程,所以进程也一并挂了。
int main(int argc, char *argv[]) {
printf("hello world");
return 0;
}
另外知道什么是(用户态)User mode和(内核态)Kernel mode吗?这两者是属于操作系统方面的知识。这里举个比较实际的例子会比较容易理解。例如要将字符串写进某个文件。步骤分别是。
stringToWrite = "hello world"
path = "/my_file.txt"
writeTo(path, stringToWrite)
1,构造一个字符串变量 ————用户态
2,构造目标地址 ————用户态
3,用当前语言的标准库在对应的目录上构造文件,并将字符串写进去 ————用户态————>内核态
在程序执行到某一段时,程序调用了一个system call函数通知操作系统内核去做某些操作,该操作只有操作系统内核才权限去做,本地程序做不到。I/O操作就是典型的system call。用户态通过system call进入内核态,这时writeTo这个函数会堵塞,因为它需要等待操作内核将结果返回,I/O操作是非常耗时的。这是一种典型的同步程序,writeTo需要等候操作系统内核返回结果才能return,线程直接堵塞在这里。
聪明的你大概会想,这个程序跑到这里只能等操作系统完成工作,结果什么做不了,造成CPU资源浪费,因为这个耗时操作不是大量的运算,而只是I/O访问而已,所以CPU是闲着的。本着最大利用CPU的资源的原则,应该要怎么做?分一条线程出来将writeTo放进去不就得了,主线程就不会堵了。但你有没有发现一点,尽管我们不希望等,但也需要知道writeTo的结果啊,这怎么办?线程间通信咯。。。但怎么做?思考一下。下面的写法是无法在两个线程之间进行通信的。
main {
thread = Thread(threadEntry)
thread.run()
return
}
threadEntry {
stringToWrite = "hello world"
path = "/my_file.txt"
writeTo(path, stringToWrite)
return
}
有一个比较简单的方法是将结果存到一个全局变量里,这样两个线程都能访问。在两个线程入口函数外声明一个变量result,并将writeTo的结果填入。
threadEntry {
stringToWrite = "hello world"
path = "/my_file.txt"
result = writeTo(path, stringToWrite)
return
}
好了,最后是你需要思考的问题,相通了再来看Runloop会发现。。。就是这么一回事。
问题是在主线程的入口main函数里,我们要怎么拿到result的值?
main {
thread = Thread(threadEntry)
thread.run()
if result {
// 如果写在这里,result根本都还没被赋值
}
return
}
————————————————————————————————————————————————————————————————————————————————————
扩展阅读,题外话。。。可以跳过
说到Runloop,其实在node.js里有一个很相似的机制叫做EventLoop的,事件循环。node.js是一个执行平台,环境。。。。执行的是JS代码。它是单线程的。。但几乎所有I/O操作都是异步的。
与apache不同的是,在接收用户的请求后,node不会给你创建新的线程来响应这个请求。apache则会,每一个请求就对应一条线程,所以写PHP的思维很简单,因为所有操作都是同步的,代码一句一句往下写就会按顺序执行。如果执行到某个段落后需要从数据库上查数据,那么该线程直接堵塞掉,直到数据库返回数据。就像上面的例子,没有开出子线程的。这种做法的缺点也是明显的,如果大量的用户请求,那么则需要开出大量的线程,假设这些请求都是从数据库里查找数据,那么CPU资源就会严重的浪费,因为I/O操作不需要太多CPU资源。大量的线程也会占用大量的内存。。。node的话是一个奇葩,它只有单线程,为了实现能处理多个请求,它必须要有一个none-blocking机制,如果每次请求都得堵一下,用户不给你炸了。
node的模型是这样的,通过一个入口函数来接收请求,然后执行一会儿直到需要进行I/O操作。。。node会将回调函数和一个任务标识扔到一个队列里进行等候并立即返回。这时node就可以继续处理第二个请求,直到需要I/O,继续放到队列里去。。。当第一个I/O操作完成后,node会将这对标识和回调函数的组合移动到。。。事件队列里等候调用。
node里有一个循环机制,每次循环node都会历遍两个队列里的元素,判断一下那一个元素对应的I/O请求完成了,然后将它放到事件队列。历遍事件队列来调用元素内对应的回调函数。。。