文章目录
前言
一、多线程
1. 概述
2. 创建线程
3. 线程退出
4. 线程回收
5. 线程分离
6. 线程取消
7. 线程的ID比较
二、线程同步
前言
记录学习多线程的知识重点与难点,若涉及版权问题请联系本人删除!
一、多线程
1. 概述
- 线程是轻量级的进程,一个进程可以涵盖多个线程。
- 线程是操作系统调度的最小单位,进程是资源分配的最小单位。
- 多个线程有不同的“地位”:进程的虚拟地址空间的生命周期默认和主线程一样,与创建的子线程无关。当主线程执行完毕,虚拟地址空间就会被释放。
- 每个线程都有唯一的线程ID(无符号的长整型数),类型为pthread_t。调用pthread_self函数就能得到当前线程的ID。
#include <pthread.h>
pthread_t pthread_self(void);
//使用pthread库,链接时需要加上选项-lpthread来指定动态库
进程与线程的区别:
- 进程有独立的地址空间,线程共用同一个地址空间。每个线程都有自己的栈区和寄存器,多个线程共享代码段、堆区、全局数据区和文件描述符表。
- 线程的上下文切换比进程快得多。
上下文切换:进程/线程分时复用CPU时间片,在切换前会保存当前状态,下次执行该任务时加载保存的状态。
控制多个线程个数:
- 针对文件IO:线程个数=2*CPU核心数。
- 针对复杂算法:线程个数=CPU核心数。
2. 创建线程
调用pthread_create函数:
【1】头文件:#include <pthread.h>
【2】函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
【3】参数说明:
- thread:传出的参数,保存创建的线程ID。
- attr:线程的属性,一般为NULL表示默认的属性。
- start_routine:函数指针,线程的执行函数。
- arg:作为实参传递到start_routine指向的函数内部。
【4】返回值:成功返回0,失败返回错误码。
程序实例:创建子线程,并在主线程内打印子线程和主线程的tid。同时,在子线程执行函数中打印信息。
#include <stdio.h> #include <pthread.h> #include <unistd.h> void* doing(void *arg) { printf("我是子线程, ID: %ld\n", pthread_self()); return NULL; } int main(int argc, char ** argv) { /* 创建子线程 */ pthread_t tid; if (!pthread_create(&tid, NULL, doing, NULL)) { printf("子线程创建成功,其ID为:%ld\n", tid); } /* 主线程才会执行 */ printf("主线程的ID:%ld\n", pthread_self()); while (1) { sleep(1); } return 0; }
3. 线程退出
调用pthread_exit函数,调用后线程就退出了。只有当所有线程都退出了,虚拟地址空间才会释放。
【1】头文件:#include <pthread.h>
【2】函数原型:void pthread_exit(void *retval);
【3】参数说明:retval表示子线程退出后返回主线程的数据。不需要时使用NULL。
程序实例:创建子线程,在子线程的执行函数中睡眠3秒后打印信息,主进程创建完子线程后调用pthread_exit函数退出。
#include <stdio.h> #include <pthread.h> #include <unistd.h> /* 子线程执行函数 */ void* doing(void *arg) { sleep(3);//延迟三秒,确定主线程是否会退出 for (int i = 0; i < 9; ++i) { printf("我是子线程!\n"); if (i == 3) { pthread_exit(NULL); } } return NULL; } int main(int argc, char **argv) { /* 创建子线程 */ pthread_t tid; if (!pthread_create(&tid, NULL, doing, NULL)) { printf("子线程创建成功,ID: %ld\n", tid); } /* 主线程:调用pthread_exit不会释放虚拟地址空间 */ pthread_exit(NULL); return 0; }
4. 线程回收
主线程调用pthread_join函数来阻塞式回收子线程。若子线程还在运行,那么该函数就会阻塞。该函数只能回收一个子线程,若想回收多个可以考虑采用循环。
【1】头文件:#include <pthread.h>
【2】函数原型:int pthread_join(pthread_t thread, void **retval);
【3】参数说明:
- thread:要回收的子线程ID。
- retval:接收子线程通过pthread_exit传出的数据。
【4】返回值:回收成功返回0,失败返回错误号。
【5】注意:
- 若子线程返回的数据位于子线程的栈区中,那么当子线程退出后其栈区就会被释放,主线程获取的数据就是无效的。
- 由于多个线程共用堆区和全局数据区,可以将子线程的数据保存于全局变量中。
- 由于主线程要回收子线程,一般都是最后退出。因此,可以将主线程的栈区变量传入子线程,在子线程中进行修改。
程序实例:借助主线程的栈区变量,将其传入子线程中,子线程将数据保存至该变量中,最后通过pthread_exit函数返回主线程,主线程调用pthread_join函数回收子线程并获取子线程返回的数据。
#include <stdio.h> #include <pthread.h> void* doing(void *arg) { int *i = (int *)arg; *i = 666666; printf("子线程将参数修改为: %d\n", *i); pthread_exit(i); return NULL; } int main(int argc, char **argv) { /* 创建子线程 */ pthread_t tid; int variable = 0;//主线程栈区变量 pthread_create(&tid, NULL, doing, &variable); /* 主线程获取子线程退出时的数据 */ void *ret = NULL; pthread_join(tid, &ret);//回收子线程 printf("主线程获取子线程数据: %d\n", *(int*)ret); return 0; }
5. 线程分离
如果总是让主线程来回收子线程,那么可能会出现子线程一直运行,而主线程阻塞在pthread_join函数,无法执行其他任务。因此,可以调用pthread_detach函数将子线程分离。当子线程退出时,其内核资源就会被系统其他进程接管并回收。
【1】头文件:#include <pthread.h>
【2】函数原型:int pthread_detach(pthread_t thread);
【3】参数说明:thread是要分离的线程ID。
【4】返回值:成功返回0,失败返回错误号。
程序实例:主线程与子线程分离,主线程执行完任务后就退出。
#include <stdio.h> #include <pthread.h> void* doing(void *arg) { for (int i = 0; i < 9; ++i) { printf("Hello, Can! %d\n", i); if (i == 4) { pthread_exit(NULL); } } return NULL; } int main(int argc, char **argv) { /* 创建子线程 */ pthread_t tid; int retCreate = pthread_create(&tid, NULL, doing, NULL); if (retCreate != 0) { perror("pthread_create error!"); return -1; } /* 主线程与子线程分离 */ int retDetach = pthread_detach(tid); if (retDetach != 0) { perror("pthread_detach error!"); pthread_join(tid, NULL); } /* 主线程执行其他任务 */ for (int i = 0; i < 5; i++) { printf("我是主线程, %d\n", i); } pthread_exit(NULL); return 0; }
6. 线程取消
线程取消就是一个线程杀死另一个线程。线程A杀死线程B需要两个条件:①线程A调用pthread_cancel函数;②线程B进行一次系统调用,从用户态切换回内核态。
【1】头文件:#include <pthread.h>
【2】函数原型:int pthread_cancel(pthread_t thread);
【3】参数说明:thread是要杀死的线程ID。
【4】返回值:成功返回0,失败返回错误号。
程序实例:主线程杀死子线程。当子线程执行pthread_self函数时就会被杀死,因为该函数间接调用了系统调用函数。
#include <stdio.h> #include <pthread.h> #include <unistd.h> void* doing(void *arg) { printf("子线程: 我要调用pthread_self了!\n"); printf("子线程: %ld\n", pthread_self()); sleep(2);//确保杀死子线程时间足够 printf("子线程: 我还活着吗?\n"); return NULL; } int main(int argc, char **argv) { /* 创建子线程 */ pthread_t tid; if (pthread_create(&tid, NULL, doing, NULL) != 0) { perror("pthread_create error!"); return -1; } /* 主线程杀死子线程 */ if (pthread_cancel(tid) != 0) { perror("pthread_cancel error!"); } /* 主线程退出 */ pthread_exit(NULL); return 0; }
7. 线程的ID比较
调用pthread_equal函数来比较两个线程的ID是否相等。
【1】头文件:#include <pthread.h>
【2】函数原型:int pthread_equal(pthread_t t1, pthread_t t2);
【3】参数说明:t1和t2就是两个线程的ID。
【4】返回值:相同返回非0值,不同返回0.
程序实例:主线程创建子线程,然后判断主线程和子线程的ID是否相同。
#include <stdio.h> #include <pthread.h> void* doing(void *arg) { printf("我是子线程\n"); return NULL; } int main(int argc, char **argv) { /* 创建子线程 */ pthread_t tid; if (pthread_create(&tid, NULL, doing, NULL) != 0) { perror("create error!"); return -1; } /* 比较线程ID是否相等*/ pthread_t tidMain = pthread_self(); if (pthread_equal(tidMain, tid) > 0) { printf("主线程ID: %ld, 子线程ID: %ld, 二者相等\n", tidMain, tid); } else { printf("主线程ID: %ld, 子线程ID: %ld, 二者不相等\n", tidMain, tid); } return 0; }