目录
(1)进程和线程

理解:
(2)如何理解之前的进程
蓝色部分括起来的整体叫进程

(3)在Linux中,站在CPU的角度,能否识别当前调度的task_struct是进程还是线程?
单执行流:

多执行流:

(4)Linux下并不存在真正的多线程!而是用进程模拟的!
(5)Linux没有真正意义的线程,那么也就绝对没有真正意义上的线程相关的系统调用!
pid_t vfork(void);
vfork函数的返回值与fork函数的返回值相同:
(6)关于原生线程库pthread
(1) 在32位平台下一共有2^32个地址,也就意味着有2^32个地址需要被映射。

(2)每一个表项中除了要有虚拟地址和与其映射的物理地址以外,实际还需要有一些权限相关的信息,比如我们所说的用户级页表和内核级页表,实际就是通过权限进行区分的。

(3)二级页表
①以32位平台为例,其页表的映射过程如下:
②相关说明:

③补充
注意: 在Linux中,32位平台下用的是二级页表,而64位平台下用的是多级页表。
(4)修改常量字符串为什么会触发段错误
- char* msg = "hello bit\n";
- *msg = 'H';
hellobit字符串—定是在内存中存的,但是msg地址用的是虚拟地址,访问hellobit的时候相当于你自己对应的指针变量是在栈区保存的临时变量,地址是虚拟地址,一定要经过页表的映射,字符常量区映射后也一定会找到物理内存 ; 通过msg指针变量访问虚拟地址,经过转换成物理地址,必须有个查表的过程,查找的时候发现映射到的目标区域权限是只R的,你要进行读取没问题,但是你要进行w,mmu内部触发硬件错误,被OS识别到是这个进程导致的错误,立马发信号终止错误
创建一个新线程的代价要比创建一个新进程小得多。
与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多(不需要切换地址空间和页表)
线程占用的资源要比进程少很多。
能充分利用多处理器的可并行数量。
在等待慢速IO操作结束的同时,程序可执行其他的计算任务。
计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
IO密集型应用,为了提高性能,将IO操作重叠,线程可以同时等待不同的IO操作
计算密集型:执行流的大部分任务,主要以计算为主。比如加密解密、排序查找等。
IO密集型:执行流的大部分任务,主要以IO为主。比如刷磁盘、访问数据库、访问网络等。
①进程是承担分配系统资源的基本实体,线程是调度的基本单位。
②进程更强调独立,线程更强调共享,但都不是绝对的
③线程共享进程数据,但也拥有自己的一部分数据:
①共用一个地址空间,因此所谓的代码段(Text Segment)、数据段(Data Segment)都是共享的:
②除此之外,各线程还共享以下进程资源和环境:

(1)pthread线程库是应用层的原生线程库:
(2)错误检查:
函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);参数:
thread:获取创建成功的线程ID,该参数是一个输出型参数。
attr:用于设置创建线程的属性,传入NULL表示使用默认属性。
start_routine:该参数是一个函数地址,表示线程例程,即线程启动后要执行的函数。
arg:传给线程例程的参数。
返回值说明:
线程创建成功返回0,失败返回错误码。
(1)只创建一个线程
①代码示例
- #include <stdio.h>
- #include <pthread.h>
- #include <unistd.h>
-
- void* Routine(void* arg)
- {
- char* msg = (char*)arg;
- while (1){
- printf("I am %s\n", msg);
- sleep(1);
- }
- }
-
- int main()
- {
- pthread_t tid;
- pthread_create(&tid, NULL, Routine, (void*)"thread 1");
-
- while (1){
- printf("I am main!\n");
- sleep(2);
- }
-
- return 0;
- }

②结果

③ps命令查看详细信息 , ps -aL
-L,看到的就是一个个的进程。-L就可以查看到每个进程内的多个轻量级进程。
(2)创建一批线程
①代码 : 让创建的每一个新线程都去执行Routine函数,Routine函数会被重复进入,即该函数是会被重入的。
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <unistd.h>
- #include <sys/types.h>
-
- void* Routine(void* arg)
- {
- char* msg = (char*)arg;
- while (1){
- printf("I am %s...pid: %d, ppid: %d\n", msg, getpid(), getppid());
- sleep(1);
- }
- }
-
- int main()
- {
- pthread_t tid[5];
- for (int i = 0; i < 5; i++){
- char* buffer = (char*)malloc(64);
- sprintf(buffer, "thread %d", i); //格式化控制
- pthread_create(&tid[i], NULL, Routine, buffer);
- }
-
- while (1){
- printf("I am main thread...pid: %d, ppid: %d\n", getpid(), getppid());
- sleep(2);
- }
-
- return 0;
- }
②结果

(3)获取线程ID
常见获取线程ID的方式有两种:
函数 : pthread_t pthread_self(void);
调用pthread_self函数即可获得当前线程的ID,类似于调用getpid函数获取当前进程的ID。
①代码 : 主线程都将通过输出型参数获取到的线程ID进行打印,此后主线程和新线程又通过调用pthread_self函数获取到自身的线程ID进行打印
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <unistd.h>
- #include <sys/types.h>
-
- void* Routine(void* arg)
- {
- char* msg = (char*)arg;
- while (1){
- printf("I am %s...pid: %d, ppid: %d, tid: %lu\n", msg, getpid(), getppid(), pthread_self());
- sleep(1);
- }
- }
-
- int main()
- {
- pthread_t tid[5];
- for (int i = 0; i < 5; i++){
- char* buffer = (char*)malloc(64);
- sprintf(buffer, "thread %d", i);
- pthread_create(&tid[i], NULL, Routine, buffer);
- printf("%s tid is %lu\n", buffer, tid[i]);
- }
-
- while (1){
- printf("I am main thread...pid: %d, ppid: %d, tid: %lu\n", getpid(), getppid(), pthread_self());
- sleep(2);
- }
- return 0;
- }
②结果

函数:int pthread_join(pthread_t thread, void **retval);
参数:
- thread:被等待线程的ID。
- retval:线程退出时的退出码信息。
返回值说明:
- 线程等待成功返回0,失败返回错误码。
- 调用该函数的线程将阻塞式挂起等待,直到ID为thread的线程终止,thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的。
总结 :
- 如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
- 如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存放的是常数PTHREAD_CANCELED。
- 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。
- 如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数。

(1)获取线程的退出码
①代码
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <unistd.h>
- #include <sys/types.h>
-
- void* Routine(void* arg)
- {
- char* msg = (char*)arg;
- int count = 5;
- while (count--){
- printf("I am %s...pid: %d, ppid: %d, tid: %lu\n", msg, getpid(), getppid(), pthread_self());
- sleep(1);
- }
-
- return (void*)101;
- }
-
-
- int main()
- {
- pthread_t tid[5];
- for (int i = 0; i < 5; i++){
- char* buffer = (char*)malloc(64);
- sprintf(buffer, "thread %d", i);
- pthread_create(&tid[i], NULL, Routine, buffer);
- printf("%s tid is %lu\n", buffer, tid[i]);
- }
-
- printf("I am main thread...pid: %d, ppid: %d, tid: %lu\n", getpid(), getppid(), pthread_self());
-
- for (int i = 0; i < 5; i++){
- void* ret = NULL;
- pthread_join(tid[i], &ret);
- printf("thread %d[%lu]...quit, exitcode: %d\n", i, tid[i], (int)ret);
- }
- return 0;
- }
②结果

(2)线程退出拿到的退出码只能判断结果是否正确
①pthread_join函数通过参数 retval拿到线程退出的退出码,只能判断代码运行完毕,结果是否正确.
②难道不处理异常吗? 多线程不需要考虑异常?
需要考虑,但是做不到!只要一个线程出了问题,整个进程就挂掉了,根本走不到join.
③某个线程崩溃了,整个进程也就跟着挂掉了,此时主线程连等待新线程的机会都没有,这也说明了多线程的健壮性不太强,一个进程中只要有一个线程挂掉了,那么整个进程就挂掉了。并且此时我们也不知道是由于哪一个线程崩溃导致的,我们只知道是这个进程崩溃了
(1)return 返回
在线程中使用return代表当前线程退出,但是在main函数中使用return代表整个进程退出,也就是说只要主线程退出了那么整个进程就退出了,此时该进程曾经申请的资源就会被释放,而其他线程会因为没有了资源,自然而然的也退出了。
(2)pthread_exit函数
pthread_exit函数的功能就是终止线程
函数: void pthread_exit(void *retval);
参数: retval:线程退出时的退出码信息。
说明一下:
- 该函数无返回值,跟进程一样,线程结束的时候无法返回它的调用者(自身)。
- pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其他线程得到这个返回指针时,线程函数已经退出了。
- exit函数的作用是终止进程,任何一个线程调用exit函数也代表的是整个进程终止。
①代码
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <unistd.h>
- #include <sys/types.h>
-
- void* Routine(void* arg)
- {
- char* msg = (char*)arg;
- int count = 5;
- while (count--){
- printf("I am %s...pid: %d, ppid: %d, tid: %lu\n", msg, getpid(), getppid(), pthread_self());
- sleep(1);
- }
-
- pthread_exit((void*)101);
- }
-
-
- int main()
- {
- pthread_t tid[5];
- for (int i = 0; i < 5; i++){
- char* buffer = (char*)malloc(64);
- sprintf(buffer, "thread %d", i);
- pthread_create(&tid[i], NULL, Routine, buffer);
- printf("%s tid is %lu\n", buffer, tid[i]);
- }
-
- printf("I am main thread...pid: %d, ppid: %d, tid: %lu\n", getpid(), getppid(), pthread_self());
-
- for (int i = 0; i < 5; i++){
- void* ret = NULL;
- pthread_join(tid[i], &ret);
- printf("thread %d[%lu]...quit, exitcode: %d\n", i, tid[i], (int)ret);
- }
- return 0;
- }
②结果

(3)pthread_cancel函数
线程是可以被取消的,我们可以使用pthread_cancel函数取消某一个线程
函数 : int pthread_cancel(pthread_t thread);
参数:thread:被取消线程的ID。
返回值说明: 线程取消成功返回0,失败返回错误码。
①线程取消自己
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <unistd.h>
- #include <sys/types.h>
-
- void* Routine(void* arg)
- {
- char* msg = (char*)arg;
- int count = 0;
- while (count < 5){
- printf("I am %s...pid: %d, ppid: %d, tid: %lu\n", msg, getpid(), getppid(), pthread_self());
- sleep(1);
- count++;
- pthread_cancel(pthread_self());
- }
- pthread_exit((void*)101);
- }
-
-
- int main()
- {
- pthread_t tid[5];
- for (int i = 0; i < 5; i++){
- char* buffer = (char*)malloc(64);
- sprintf(buffer, "thread %d", i);
- pthread_create(&tid[i], NULL, Routine, buffer);
- printf("%s tid is %lu\n", buffer, tid[i]);
- }
-
- printf("I am main thread...pid: %d, ppid: %d, tid: %lu\n", getpid(), getppid(), pthread_self());
-
- for (int i = 0; i < 5; i++){
- void* ret = NULL;
- pthread_join(tid[i], &ret);
- printf("thread %d[%lu]...quit, exitcode: %d\n", i, tid[i], (int)ret);
- }
-
- return 0;
- }
每个线程执行一次打印操作后就退出了,其退出码不是101而是-1,因为我们是在线程执行pthread_exit函数前将线程取消的。

②线程可以自己取消自己,但一般不这样做,我们往往是用于一个线程取消另一个线程
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <unistd.h>
- #include <sys/types.h>
-
- void* Routine(void* arg)
- {
- char* msg = (char*)arg;
- int count = 0;
- while (count < 3){
- printf("I am %s...pid: %d, ppid: %d, tid: %lu\n", msg, getpid(), getppid(), pthread_self());
- sleep(1);
- count++;
- }
- pthread_exit((void*)101);
- }
-
- int main()
- {
- pthread_t tid[5];
- for (int i = 0; i < 5; i++){
- char* buffer = (char*)malloc(64);
- sprintf(buffer, "thread %d", i);
- pthread_create(&tid[i], NULL, Routine, buffer);
- printf("%s tid is %lu\n", buffer, tid[i]);
- }
-
-
- pthread_cancel(tid[0]); //取消两个线程
- pthread_cancel(tid[1]);
-
- printf("I am main thread...pid: %d, ppid: %d, tid: %lu\n", getpid(), getppid(), pthread_self());
-
- for (int i = 0; i < 5; i++){
- void* ret = NULL;
- pthread_join(tid[i], &ret);
- printf("thread %d[%lu]...quit, exitcode: %d\n", i, tid[i], (int)ret);
- }
-
- return 0;
- }

③新线程取消主线程
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <unistd.h>
- #include <sys/types.h>
-
- pthread_t main_thread;
-
- void* Routine(void* arg)
- {
- char* msg = (char*)arg;
- int count = 0;
- while (count < 3){
- printf("I am %s...pid: %d, ppid: %d, tid: %lu\n", msg, getpid(), getppid(), pthread_self());
- sleep(1);
- count++;
- pthread_cancel(main_thread);
- }
- pthread_exit((void*)101);
- }
-
- int main()
- {
- main_thread = pthread_self();
- pthread_t tid[5];
- for (int i = 0; i < 5; i++){
- char* buffer = (char*)malloc(64);
- sprintf(buffer, "thread %d", i);
- pthread_create(&tid[i], NULL, Routine, buffer);
- printf("%s tid is %lu\n", buffer, tid[i]);
- }
-
- printf("I am main thread...pid: %d, ppid: %d, tid: %lu\n", getpid(), getppid(), pthread_self());
-
- for (int i = 0; i < 5; i++){
- void* ret = NULL;
- pthread_join(tid[i], &ret);
- printf("thread %d[%lu]...quit, exitcode: %d\n", i, tid[i], (int)ret);
- }
-
- return 0;
- }
主线程右侧显示<default>,也就意味着主线程已经被取消了,我们也就看不到后续主线程等待新线程时打印的退出码了。


注意:
(1)基本概念
(2)线程分离函数
函数: int pthread_detach(pthread_t thread)
参数说明:thread:被分离线程的ID。
返回值说明: 线程分离成功返回0,失败返回错误码
(3)使用示例
①代码
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- #include <unistd.h>
- #include <sys/types.h>
-
- void* Routine(void* arg)
- {
- pthread_detach(pthread_self()); //线程分离
- char* msg = (char*)arg;
- int count = 0;
- while (count < 3){
- printf("I am %s...pid: %d, ppid: %d, tid: %lu\n", msg, getpid(), getppid(), pthread_self());
- sleep(1);
- count++;
- }
- pthread_exit((void*)101);
- }
-
- int main()
- {
- pthread_t tid[5];
- for (int i = 0; i < 5; i++){
- char* buffer = (char*)malloc(64);
- sprintf(buffer, "thread %d", i);
- pthread_create(&tid[i], NULL, Routine, buffer);
- printf("%s tid is %lu\n", buffer, tid[i]);
- }
-
- while (1){
- printf("I am main thread...pid: %d, ppid: %d, tid: %lu\n", getpid(), getppid(), pthread_self());
- sleep(1);
- }
-
- return 0;
- }
②结果


(1)基本概念
thread_create函数会产生一个线程ID,存放在第一个参数指向的地址中,该线程ID和内核中的LWP不是一回事。
内核中的LWP属于进程调度的范畴,因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
pthread_create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,这个ID属于NPTL线程库的范畴,线程库的后续操作就是根据该线程ID来操作线程的。
线程库NPTL提供的pthread_self函数,获取的线程ID和pthread_create函数第一个参数获取的线程ID是一样的。
(2)pthread_t类型
①Linux不提供真正的线程,只提供LWP,也就意味着操作系统只需要对内核执行流LWP进行管理,而供用户使用的线程接口等其他数据,应该由线程库自己来管理,因此管理线程时的“先描述,再组织”就应该在线程库里进行。
②ldd命令查看线程库 , 线程库是一个动态库

③通过地址空间理解


④以地址的形式打印线程ID
- #include <stdio.h>
- #include <pthread.h>
- #include <unistd.h>
-
- void* Routine(void* arg)
- {
- while (1){
- printf("new thread tid: %p\n", pthread_self());
- sleep(1);
- }
- }
-
- int main()
- {
- pthread_t tid;
- pthread_create(&tid, NULL, Routine, NULL);
-
- while (1){
- printf("main thread tid: %p\n", pthread_self());
- sleep(2);
- }
- return 0;
- }
