进程概念:进程是运行中的程序;
进程的状态:大概分为就绪、运行、阻塞;
进程加载:分页(页表),虚拟内存;
进程的创建:调用fork()函数(fork与vfork的区别),调用一次,返回两次,父进程返回子进程的pid,子进程返回0;
以及写时拷贝技术;
父子进程数据共享分析:
共享:文件描述符(浅拷贝);
不共享:全局变量、栈区、堆区;
僵尸进程:子进程结束,父进程未结束,并且父进程未获取子进程的退出数据(退出码);
僵尸进程的处理:wait 和 waitpid
信号:是一种预先定义好的事件;
发送信号:kill函数、raise函数;
修改信号的响应方式:signal函数 、sigaction函数;
三种方式:忽略(SIG_IGN)、默认(SIG_DFL)、自定义:信号处理函数;
信号的底层实现:0.11版本源代码;
进程间的通讯 :信号、有名管道(FIFO)、无名管道、信号量、消息队列、共享内存;
一、引言:
进程在一个时间只能干一件事,那么要想干两件事情,只能一件干完再去干另外一件事情。但是这不是我们想要的,我们的意图是同时干两件事情,要想这样,只能使进程进行分身---线程,既然说线程是进程的分身,那么从本质上是一样的,但是也有一些地方不一样,就像分身,不同的分身执行不同的任务,这里也是一样的,那么线程到底是怎么定义的呢?
二、线程的概念:
在一个程序中多个执行路线叫线程,那么更详细点就是说:线程是进程内部的一个执行序列。因此,说一个进程内部可以有多个线程。形象点:就是演出一场戏需要舞台、演员,而这里的演员就是我们的线程、舞台就是地址空间,这里地址空间和所有演员就构成了我们的进程。
在线程模式下:一个进程至少有一个线程,也可以有多个线程
图解:
我们可以通过线程库创建新的线程,我们将main函数执行的序列称之为主线程,新生成的线程称为函数线程。
线程与进程之间的区别:
a.进程是资源分配的最小单位,线程是调度的最小单位;
b.父子进程只共享文件描述符,但是一个进程内的多个线程可以共享文件描述符、全局数据、堆区数据、
c.创建线程所需要的资源比创建进程的资源少,所以线程间的切换比进程间的切换效率高;
为什么进程分为多线程执行:
我们知道,一个进程一个时间只能做一件事情,而一个进程内部是多线程的话,那么多个线程就相当于是进程的分身,可以去同时执行多个任务。这只是一个原因。
再就是在没有线程的情况下,增加一个处理器并不能让一个进程的执行速度提高。但是如果是分成多个线程以后,那么不同的线程执行在不同的处理器上,这样进程效率就会显著的提高。
比如:在我们wps上进行写文档的时候,就是多线程的实现,一个负责接收,一个负责显示在屏幕上,一个进行计数,还有存往磁盘上面。所以,当我们在用这个wps程序的时候就会发现,它的好多的功能都是同时实现的,这就是多线程的好处。
图解:
三、线程的管理:
进程是需要管理的,同样的线程也是需要管理的,那么管理线程也是要维持线程的各种信息,这些信息包含了线程的各种关键资料,存放这些信息的结构称之为线程控制表或者叫线程控制块,那么线程控制块到底有什么资源呢。
我们说过线程共享一个进程空间,因此很多资源是共享的,这些的资源不是放在线程控制块的,而是放在进程块中的,由于线程是不同的执行序列,那么有些资源也是不会共享的,上面我们只是说了一部分,并且这些不被共享的资源就需要放在线程控制块中。但是应当让共享的资源越多越好,因为我们发明线程的目的就是经常协作,共享自然是我们希望看到的。
如果某资源不独享会导致线程运行错误,则该资源就由每个线程独享;而其他资源都由进程里面的所有线程共享。按照这句话,进行如图概括:
通过了解知道线程是进程的一部分,那么是谁来管理线程的呢?一种是让进程来管理线程,另外一种是操作系统来管理线程。这也就生成了两种线程的实现方式,即内核态线程和用户态线程。由进程自己管理的就是用户态实现,操作系统管理就是内核态实现。但是我们知道进程是在cpu上实现并发(多道编程),而cpu是由操作系统管理的,因此,进程的实现只能由操作系统来进行,而不存在用户态实现的情况。但是线程就不同了,线程是进程内部的东西,这就有必要了。
四、线程的创建方式:
a.内核态线程实现:
优点:用户编程简单,线程切换由内核负责,如果一个线程阻塞,进程并不会阻塞;
缺点:效率低,因为线程在内核态实现,每次线程切换都需要陷入内核,由操作系统进行调度。并且操作系统需要我维护线程表,当线程的数量大大高于进程的数量,随着线程的数量增加,系统内核空间将会被耗尽。如果内核空间溢出,操作系统将停止运转。最重要的是内核态的实现需要修改操作系统。
b.用户态线程的实现:
用户态是如何进行线程调度的呢?用户自己写一个执行系统作调度器。
优点:灵活性高,线程切换快,切换在用户态进行,不需要陷入内核态;不用修改操作系统。
缺点:编程负责,用户需要自己管理线程调度,安全问题。当执行过程中,一个线程阻塞,它无法将控制权交出来,这样整个进程无法推进。操作系统随即把cpu控制权交给另外一个进程。
c.混合模式:
混合模式:将内核态和用户态结合起来。用户态的执行系统负责进程内部线程在非阻塞时的切换;内核态的操作系统负责阻塞线程的切换,即使我们同时实现内核态和用户态线程管理。也就是用户态线程被多路复用到内核态线程上。所以,在分配线程的时候,我们可以将需要执行阻塞操作的线程设为内核态线程。
五、如何使用线程库创建线程:
pthread_create函数:
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
第一个参数:指向pthread_t类型数据的指针,线程被创建时,这个指针指向的变量中被写入一个标识符,我们用该标识符来引用新线程。必须在创建线程时设置,必须传地址。
第二个参数:用于设置线程的属性,一般不需要特殊的属性,只需要设置该参数为NULL。
第三个参数:线程函数,指定新创建出的线程的指定函数
void *(*start_routinue)(void *);
所以pthread_create函数第三个参数必须传一个函数指针,而这个函数指针指向的函数以一个void的指针为参数,返回的也是一个指向void的指针;
第四个参数:就是第三个参数函数指针所指向线程函数给传的一个void*参数。
返回值:pthread_create 函数调用成功返回0,失败返回错误代码;
pthread_exit函数:仅结束调用这个函数的线程
这个函数的作用,终止调用它的线程并返回一个指向某个对象的指针。注意:不能用它来返回一个指向局部变量的指针,因为线程调用该函数后,局部变量就不存在了。
void pthread_exit(void *)
参数是传递线程结束的信息;
pthread_join函数:
等待线程结束,接收线程结束信息。类似于进程中用来收集子进程信息的wait函数。
pthread_join(pthread_t th, void ** thread_return);
第一个参数:是将要等待的线程,线程通过pthread_create返回的标识符来指定.
第二个参数:是一个二级指针,指向一个一级指针,而这个一级指针指向线程的返回值。
返回值:成功返回0,失败返回错误代码。
六、测试:
代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
void* fun(void *arg)
{
int i = 0;
for (; i<5; ++i)
{
printf("i am fun\n");
sleep(1);
}
}
void main()
{
pthread_t id;
int res = pthread_create(&id,NULL,fun,NULL);
assert(res == 0);
int i = 0;
for (; i<3; ++i)
{
printf("i am main\n");
sleep(1);
}
}
Linux 终端下gcc命令:gcc -o pthread pthread_create.c -lpthread(-lpthread 是链接线程库)按代码逻辑来看,应该是打印出来的是五个i am fun 和三个i am main。但是我们看一下结果。
显示出来是各为三个,那么这是为什么呢,当我们再实验一下,换一下,看如下代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
void* fun(void *arg)
{
int i = 0;
for (; i<3; ++i)
{
printf("i am fun\n");
sleep(1);
}
}
void main()
{
pthread_t id;
int res = pthread_create(&id,NULL,fun,NULL);
assert(res == 0);
int i = 0;
for (; i<5; ++i)
{
printf("i am main\n");
sleep(1);
}
}
按照逻辑,这个是五个 i am main 三个i am fun ,看结果:
看结果与我们的预想是一模一样的,那么两个就颠倒了一下,结果却差这么多,那么从两个实验代码中可以推到出,问题出在了main函数中,也就是我们的主线程中。
结论:
主函数与线程函数的运行关系:
主线程和函数线程并行运行,创建出函数线程后,主线程和函数线程谁先开始运行是不一定的,由当前系统和调度算法确定。
主线程如果先结束,默认调用exit函数,exit会结束整个进程,函数线程也会随之结束,那这并不是我们想要看到的结果,我们的函数线程的工作还没有完,就强行结束,这是万万不能的。那怎么解决呢。
解决方法:使主线程延迟结束,也就是像我们前面讲父子进程的时候,wait函数一样。我们这里是调用pthread_join函数,在主线程中添加函数:pthread_join(id, NULL);
修改后代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
void* fun(void *arg)
{
int i = 0;
for (; i<5; ++i)
{
printf("i am fun\n");
sleep(1);
}
}
void main()
{
pthread_t id;
int res = pthread_create(&id,NULL,fun,NULL);
assert(res == 0);
int i = 0;
for (; i<3; ++i)
{
printf("i am main\n");
sleep(1);
}
pthread_join(id,NULL);
}
结果:
是不是与我们的预想一模一样了。惊不惊喜,哈哈哈。