此为牛客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 : 设置线程的属性,一般使用默认属性,即NULL
  • start_routine : 函数指针,这个函数是子线程需要处理的逻辑代码
  • arg : 给第三个参数(回调函数)使用,是回调函数的参数

返回值:

  • 成功:0
  • 失败:返回错误号。这个错误号和之前errno不太一样,获取错误号的信息使用:
#include <string.h>
char * strerror(int errnum);

创建线程示例代码如下:

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>void* 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:需要回收的子线程的ID
  • retval: 接收子线程退出时的返回值(即pthread_exit的void *retval参数), 而且是传出参数。

返回值:0 : 成功;非0 : 失败,返回的错误号

不使用传出参数的一个简单使用如下:

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>void* 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.h>int 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,失败:返回错误号

注意:

  1. 线程不能多次分离,会产生不可预料的行为。
  2. 不能去连接(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);

功能:取消线程(让线程终止)

【注意】:线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点(检查点)。
类似于玩游戏存档,必须到达指定的场所(存档点,如:客栈、仓库、城里等)才能存储进度。杀死线程也不是立刻就能完成,必须要到达取消点。
取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write…
执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。也可参阅 APUE.12.7 取消选项小节。
可粗略认为一个系统调用(进入内核)即为一个取消点。如线程中没有取消点,可以通过调用pthreestcancel函数自行设置一个取消点。

看下面这个代码示例,子线程无限循环:

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>void* 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.h>void* 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将已经结束的子线程回收。

【Linux系统编程学习】Linux线程控制原语相关推荐

  1. linux线程并不真正并行,Linux系统编程学习札记(十二)线程1

    Linux系统编程学习笔记(十二)线程1 线程1: 线程和进程类似,但是线程之间能够共享更多的信息.一个进程中的所有线程可以共享进程文件描述符和内存. 有了多线程控制,我们可以把我们的程序设计成为在一 ...

  2. linux系统编程学习_(2)进程控制-- fork函数、exec函数族、回收子进程--孤儿进程僵尸进程、wait函数

    linux系统编程学习_(2)进程控制-- fork函数.exec函数族.回收子进程–孤儿进程僵尸进程.wait函数 进程控制 fork()函数 创建一个子进程. pid_t fork(void); ...

  3. 嵌入式Linux系统编程学习之二常用命令

    嵌入式Linux系统编程学习之二常用命令 文章目录 嵌入式Linux系统编程学习之二常用命令 前言 一.常用命令 1.su(用户切换) 2.useradd(添加用户) 3.passwd(修改密码) 4 ...

  4. 嵌入式Linux系统编程学习之一目录结构

    嵌入式Linux系统编程学习之一目录结构 文章目录 嵌入式Linux系统编程学习之一目录结构 前言 一.Linux目录结构 前言 Linux目录结构 一.Linux目录结构 /bin:存放Linux的 ...

  5. 【Linux系统编程学习】Linux进程控制原语(fork、exec函数族、wait)

    此为牛客Linux C++和黑马Linux系统编程课程笔记. 1. fork函数 1.1 fork创建单个子进程 #include<unistd.h> pid_t fork(void); ...

  6. 【Linux | 系统编程】Linux系统编程(文件、进程线程、进程间通信)

    文章目录 Linux系统编程 文件IO open/close函数 read/write函数 文件描述符 阻塞.非阻塞 fcntl函数 lseek函数 传入传出参数 文件系统 文件存储 文件操作 sta ...

  7. Linux系统编程学习之《编程前的准备》

    在进行Linux系统编程钱,先来看看编程前的准备吧! 先说说我为什么学习Linux系统编程,因为我觉得现在Linux是IT行业的主流,学习一下Linux相关知识,对于学计算机专业的我来说肯定是有必要的 ...

  8. 【Linux系统编程学习】信号、信号集以其相关函数

    此为牛客Linux C++和黑马Linux系统编程课程笔记. 文章目录 0. 信号的概念 1. Linux信号一览表 2. 信号相关函数 3. kill函数 4. raise函数 5. abort函数 ...

  9. 【Linux系统编程学习】匿名管道pipe与有名管道fifo

    此为牛客Linux C++和黑马Linux系统编程课程笔记. 0. 关于进程通信 Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间.任何一个进程的全局变量在另一个进程中都看不到 ...

  10. 【Linux系统编程】Linux系统调用

    00. 目录 文章目录 00. 目录 01. 系统调用概述 02. 系统调用实现 03. 系统调用和库函数的区别 04. 附录 01. 系统调用概述 系统调用顾名思义,说的是操作系统提供给用户程序调用 ...

最新文章

  1. mysql io线程异常_mysql主从同步IO线程NO
  2. 指针的引用做函数的参数
  3. php伪静态后无法获得url中参数_php runtime、http web中rewrite浅解和方案
  4. 央视消息 | 没考驾照的人可能要恭喜了!
  5. Differential Geometry之第九章常平均曲率曲面
  6. 内存映射与DMA笔记
  7. 前几行 python_调包侠神器2.0发布,Python机器学习模型搭建只需要几行代码
  8. python初学者怎么入门-初学者如何学习Python?掌握这17个实用小技巧快速入门!...
  9. IE6/7 单选按钮 radio 无法选中解决方法
  10. 奇怪的比赛|2012年蓝桥杯B组题解析第四题-fishers
  11. dubbo优势_Dubbo的作用和特点
  12. 【SQL注入-01】SQL语句基础及SQL注入漏洞原理及分类
  13. struts2环境搭建教程
  14. java调用fudannlp_利用FudanNLP進行新聞關鍵詞提取 | 學步園
  15. 点击按钮显示和隐藏图片
  16. Linux的进程空间管理
  17. opencv3学习:reshape函数
  18. 《权力的游戏》Python探索性分析
  19. Shader --- 法阵
  20. IDEA访问数据库时,其中一个字段数据库中有值,但是访问到的数据的时候其中一个始终是null

热门文章

  1. Pointcut is not well-formed: expecting #39;name pattern#39; at character position 36
  2. 学习编程,英语很重要!!
  3. JavaScript 参考教程——写在前面
  4. 计算机网络层实验路由表苏州科技,苏州科技大学计算机网络实验报告课案.docx...
  5. 搜索引擎优化系统知名乐云seo_seo技术出名 乐云seo:如何进行搜索引擎优化?
  6. ble连接过程建立_九点之蓝牙连接
  7. python做自动化如何定位动态元素_python-web自动化-元素定位
  8. python实现贝叶斯分类器_python实现简单的朴素贝叶斯分类器
  9. 文件共享服务器imac,iMac怎么在网络上共享设备windows文件夹和服务 | MOS86
  10. ajax省市二级联动硬编码,AJAX请求接受硬编码的JSON,但不接受软编码