好的公文写作网站,西安学校网站建设费用,网站的建设原始代码,前端框架模板此为牛客Linux C课程笔记。
0. 关于线程 注意#xff1a;LWP号和线程id不同#xff0c; LWP号是CPU分配时间片的依据#xff0c;线程id是用于在进程内部区分线程的。
1. 线程与进程的区别 对于进程来说#xff0c;相同的地址(同一个虚拟地址)在不同的进程中#xff0c;反…此为牛客Linux C课程笔记。
0. 关于线程 注意LWP号和线程id不同 LWP号是CPU分配时间片的依据线程id是用于在进程内部区分线程的。
1. 线程与进程的区别 对于进程来说相同的地址(同一个虚拟地址)在不同的进程中反复使用而不冲突。原因是他们虽虚拟址一样但页目录、页表、物理页面各不相同。相同的虚拟址映射到不同的物理页面内存单元最终访问不同的物理页面。
但线程不同两个线程具有各自独立的PCB但共享同一个页目录也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间。
实际上无论是创建进程的fork还是创建线程的pthread_create底层实现都是调用同一个内核函数clone。 如果复制对方的地址空间那么就产出一个“进程”如果共享对方的地址空间就产生一个“线程”。
因此Linux内核是不区分进程和线程的。只在用户层面上进行区分。所以线程所有操作函数 pthread_* 是库函数而非系统调用。
优点 1. 提高程序并发性 2. 开销小 3. 数据通信、共享数据方便 缺点 1. 库函数不稳定 2. 调试、编写困难、gdb不支持 3. 对信号支持不好 优点相对突出缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。
2. 线程相关操作函数 2.1 获取线程id
#include pthread.h
pthread_t pthread_self(void);功能获取线程ID。其作用对应进程中 getpid() 函数。
2.2 创建线程 pthread_create
#include pthread.h
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);功能创建一个子线程
参数
thread传出参数线程创建成功后子线程的线程ID被写到该变量中。attr : 设置线程的属性一般使用默认属性即NULLstart_routine : 函数指针这个函数是子线程需要处理的逻辑代码arg : 给第三个参数回调函数使用是回调函数的参数
返回值
成功0失败返回错误号。这个错误号和之前errno不太一样获取错误号的信息使用
#include string.h
char * strerror(int errnum);创建线程示例代码如下
#include stdio.h
#include pthread.h
#include string.h
#include unistd.hvoid* callback(void* arg) {printf(its child thread, thread id is %lu\n, pthread_self());printf(arg %d\n, *(int *)arg);
}int main()
{pthread_t pid;int a 5;int ret pthread_create(pid, NULL, callback, a);if(ret ! 0) {// 说明创建失败char * errstr strerror(ret);printf(error: %s\n, errstr);}printf(its main thread, thread id is %lu\n, pthread_self());sleep(1);return 0;
}发现无法编译 查阅文档发现 编译时加-pthread即可运行结果如下
2.3 终止线程 pthread_exit
注意不能使用exit函数终止当前线程exit将终止当前进程进程中的所有线程将一并终止。
#include pthread.h
void pthread_exit(void *retval);参数retval表示线程退出状态通常传NULL
多线程环境中应尽量少用或者不使用exit函数取而代之使用pthread_exit函数将单个线程退出。任何线程里exit导致进程退出其他线程未工作结束主控线程退出时不能return或exit。
2.4 连接已终止的线程回收线程pthread_join
#include pthread.h
int pthread_join(pthread_t thread, void **retval);功能和一个已经终止的线程进行连接回收子线程的资源 注意这个函数是阻塞函数调用一次只能回收一个子线程一般在主线程中使用 参数
thread需要回收的子线程的IDretval: 接收子线程退出时的返回值即pthread_exit的void *retval参数 而且是传出参数。
返回值0 : 成功非0 : 失败返回的错误号
不使用传出参数的一个简单使用如下
#include stdio.h
#include pthread.h
#include string.h
#include unistd.hvoid* callback(void* arg) {printf(子线程运行中...\n);sleep(2);
}int main()
{pthread_t pid;int ret pthread_create(pid, NULL, callback, NULL);if(ret ! 0) {// 说明创建失败char * errstr strerror(ret);printf(error: %s\n, errstr);}pthread_join(pid, NULL);printf(子线程已回收\n);return 0;
}子线程执行2秒后主进程才输出“子线程已回收”说明pthread_join函数是阻塞的。
pthread_join函数比较难以理解的地方是他的第二个参数void **retval是void二级指针类型这是因为
首先这个参数是想接收pthread_exit所传出的void *retval 这个参数本身是void *的一级指针类型而pthread_join函数的void **retval在设计时是设计成一个传出参数的以便把pthread_exit传出的void *retval带回主线程所以要想把 void * 类型变量设计成传出参数即是 void **。
示例程序如下
#include stdio.h
#include pthread.h
#include string.h
#include unistd.hint value 10;void* callback(void* arg) {printf(子线程运行中...\n);pthread_exit((void *)value);
}int main()
{pthread_t pid;int ret pthread_create(pid, NULL, callback, NULL);if(ret ! 0) {// 说明创建失败char * errstr strerror(ret);printf(error: %s\n, errstr);}int *thread_retval; // 给pthread_join调用接收pthread_exit的传出参数pthread_join(pid, (void **)thread_retval);printf(exit data : %d\n, *thread_retval);return 0;
}运行结果如下
2.5 线程分离pthread_detach
#include pthread.h
int pthread_detach(pthread_t thread);功能使进程处于分离状态。被分离的线程在终止的时候会自动释放资源返回给系统避免产生僵尸线程。
线程分离状态指定该状态线程主动与主控线程断开关系。线程结束后其退出状态不由其他线程获取而直接自己自动释放。网络、多线程服务器常用。
参数需要分离的线程的ID
返回值成功0失败返回错误号 注意 线程不能多次分离会产生不可预料的行为。不能去连接pthread_join一个已经分离的线程会报错一般情况下线程终止后其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态这样的线程一旦终止就立刻回收它占用的所有资源而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join这样的调用将返回EINVAL错误。也就是说如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。 2.6 线程取消pthread_cancel
#include pthread.h
int pthread_cancel(pthread_t thread);功能取消线程让线程终止 【注意】线程的取消并不是实时的而有一定的延时。需要等待线程到达某个取消点(检查点)。 类似于玩游戏存档必须到达指定的场所(存档点如客栈、仓库、城里等)才能存储进度。杀死线程也不是立刻就能完成必须要到达取消点。 取消点是线程检查是否被取消并按请求进行动作的一个位置。通常是一些系统调用creatopenpauseclosereadwrite… 执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。也可参阅 APUE.12.7 取消选项小节。 可粗略认为一个系统调用(进入内核)即为一个取消点。如线程中没有取消点可以通过调用pthreestcancel函数自行设置一个取消点。 看下面这个代码示例子线程无限循环
#include stdio.h
#include pthread.h
#include string.h
#include unistd.hvoid* callback(void* arg) {while(1) {printf(子线程运行中...\n);sleep(1);}return NULL;
}int main()
{pthread_t pid;int ret pthread_create(pid, NULL, callback, NULL);if(ret ! 0) {char * errstr strerror(ret);printf(error: %s\n, errstr);}pthread_cancel(pid);ret pthread_join(pid, NULL);if(ret ! 0) {char * errstr strerror(ret);printf(error: %s\n, errstr);}printf(线程已回收\n);return 0;
}运行后成功输出”线程已回收“ 这是因为pthread_cancel终止了子线程的运行故pthread_join得以执行。
但是如果将子进程中循环语句中的内容去掉
#include stdio.h
#include pthread.h
#include string.h
#include unistd.hvoid* callback(void* arg) {while(1) {// printf(子线程运行中...\n);// sleep(1);}return NULL;
}int main()
{pthread_t pid;int ret pthread_create(pid, NULL, callback, NULL);if(ret ! 0) {char * errstr strerror(ret);printf(error: %s\n, errstr);}pthread_cancel(pid);ret pthread_join(pid, NULL);if(ret ! 0) {char * errstr strerror(ret);printf(error: %s\n, errstr);}printf(线程已回收\n);return 0;
}运行以后发现没有输出主线程阻塞。这是因为子线程的while(1)死循环中没有任何语句也就不会执行任何系统调用也就不会到达任何一个“取消点”所以子线程并没有被终止主线程被阻塞在pthread_join处。而之前的代码循环语句中的printf会调用系统调用write所以会到达“取消点”pthread_join将已经结束的子线程回收。