本文转自:https://blog.csdn.net/zhangye3017/article/details/80367223
1. 什么是线程
线程是进程执行内部的一个执行分支,在一个进程内部运行的多种执行流;内部本质上是多个线程在同一个地址空间运行;第一个pcb称之为主线程;有多个线程就有多个执行流;一个进程至少有一个线程
2. 图解线程
PCB2所代表的进程通过vfork创建一个子进程,子进程再vfork一个新的子进程,以此类推产生两个新的子进程;
此时PCB1、PCB2、PCB3都指向同一块虚拟地址空间,通过操作把PCB1所指向的虚拟空间的资源(主要是数据和代码),分成3部分分别给PCB1、PCB2、PCB3
- 这些进程就被称为线程,这些线程之间满足互不干扰的条件,且这些线程共用同一虚拟空间,并且共用部分资源,在访问这些公共资源时,这些线程可以互相访问到对方的资源
3. linux下的线程
linux下并没有真正意义上的线程存在,linux中使用进程来模拟实现线程,父进程创建子进程,子进程执行父进程的一部分代码,并且与父进程共享同一个地址空间。这些一个一个被创建出来的子进程可看到为线程,这种线程也称之为轻量级进程
注:轻量级进程(LWP)是一种实现多任务的方法。与普通进程相比,LWP与其他进程共享所有(或大部分)它的逻辑地址空间和系统资源;linux下,CPU看到的所有进程都可以看作为轻量级进程
4. 线程的资源(私有和共享)
共享 | 私有 |
---|---|
数据段,代码段 | 每个线程都有自己的栈结构 |
文件描述符表 | 上下文,(包含各种计数器的值、程序计数器和栈指针) |
每种信号的处理方式 | 线程id |
当前工作的目录 | errno变量(当线程异常退出时的错误退出码) |
用户id和线程组id | 信号屏蔽字 |
调度优先级 |
5. 线程的优缺点
优点
- 线程占用的资源比进程少,只虚复制PCB即可
- 创建时代价较小
- 线程间的切换(调度)需要操作系统做的工作少很多
- 线程间共享数据更容易
- 在等待慢速 I/O操作结束的同时,程序可执行其他的计算任务。
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
注:关于I/O密集型和计算密集型可参考这篇文章:CPU-bound(计算密集型) 和I/O bound(I/O密集型
缺点
性能损失(一个很少被外部事件阻塞的计算密集型线程往往无法与其他线程共享一个处理器。如果计算密集型线程的数量比可用的处理器多,那么就有可能造成较大的性能损失,这里的性能损失指的是操作系统增加了额外的同步和调度开销,而可用的资源不变);
健壮性降低(由于线程是共享同一块虚拟地址空间的,在运行期间,因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的,也就是说线程之间是缺乏保护的);
缺乏访问控制(当在线程中调用某些函数(OS函数,处理signal函数,调用kill/exit函数),可能会导致线程退出,从而使所有的线程都异常退出);
编程难度提高(线程共用同一块虚拟地址空间,势必在处理多线程时会有访问同一个资源等问题,此时就涉及到共享资源的处理)
6. 线程控制
6.0 写在前面
在操作系统内部,它不管什么进程线程的,它只以PCB为准,只有在用户态里才有线程的概念。一般实现线程会用到一个POSIX线程库,在这里可以通过调用POSIX库里的函数来实现有关线程的各种操作。不过内核中也有一种内核级线程。
两个基本类型:
用户级线程:管理过程全部由用户程序完成,操作系统内核心只对进程进行管理。如POSIX线程库。
系统级线程(核心级线程):由操作系统内核进行管理。操作系统内核给应用程序提供相应的系统调用和应用程序接口API,以使用户程序可以创建、执行、撤消线程。
POSIX线程库
由系统库支持。线程的创建和撤销以及线程状态的变化都由库函数控制并在目态(user态)完成,与线程相关的控制结构TCB保存在目态并由系统维护。由于线程对操作不可见(操作系统可见的必然保存在kernel态由系统维护),系统调度仍以进程为单位(同一进程内线程相互竞争),核心栈的个数与进程个数相对性。
与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的
要使用这些库函数,就要引入头文件
gcc在链接这些线程函数库时要使用编译器命令的“-lpthread”选项(pthread是共享库文件)
6.1 创建线程
注:创建出新线程后,新线程去执行函数,主线程继续往下运行,谁先谁后不一定,同理fork父子进程
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
//参数:
//thread:线程id,将线程id存入,线程标识符,线程栈的起始地址,输出型参数
//attr:线程属性,NULL,8种选项
//函数指针:新线程执行该函数指针指向的代码,线程回调函数
//arg:传递给函数指针指向的函数的参数,线程回调函数的参数
//返回值:成功返回0,失败返回错误码,如:
//EAGAIN 描述: 超出了系统限制,如创建的线程太多。
//EINVAL 描述: tattr 的值无效。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
例:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
void* thread_func(void* arg)
{
(void)arg;
while(1)
{
printf("I am new thread\n");
sleep(1);
}
}
int main()
{
pthread_t tid;
if ( pthread_create(&tid,NULL,thread_func,NULL) == -1){
perror("pthread_create");
exit(1);
}
while(1){
printf("I am main thread\n");
sleep(1);
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
结果:
6.2 线程ID
线程是进程的一个执行分支,并且线程在内核中的存在状态是轻量级进程,因此线程ID和进程ID存在一定的关系,可查看:linux下的线程ID和进程ID
6.3 线程的查看以及多线程的调试
线程是进程的执行分支,多个线程共享同一块虚拟地址空间,在这其中,多个线程共享数据段、 代码段等等,但还是存在自己私有的一些结构,对于这些结构,我们可以通过不同的方法可以进行查看。可参考:线程的查看以及多线程的调试
6.4 等待线程——pthread_join
- 功能:以阻塞的方式回收新线程,可以得到线程的退出码,并回收其资源
- 如果不使用pthread_join回收线程,有可能造成和僵尸进程一样的问题,造成内存泄漏;
#include <pthread.h>
int pthread_join(pthread_t thread, //要等待的线程ID
void **retval);//用于接收线程退出的退出码
等待线程的目的:
- 保证线程的退出顺序:保证一个线程退出并且回收资源后允许下一个进程退出
- 回收线程退出时的资源情况:保证当前线程退出后,创建的新线程不会复用刚才退出线程的地址空间
- 获得新线程退出时的结果是否正确的退出返回值,这个有点类似回收僵尸进程的wait,保证不会发生内存泄露等问题
6.5 终止线程
方式1:在一个线程中return
如果线程通过return返回,那么retval所指向的单元里存放的是tread函数的返回值
例:main函数创建一个新线程,新线程执行完自己的函数,使用return退出,那么返回值就是退出码
方式2:exit
如果线程是自己调用exit终止的,那么就是直接退出,并且exit表示进程的退出
exit和return的区别:
return是函数的退出,exit是进程的退出
return执行结束后会调用exit或和exit类似的函数,return会释放局部变量并且弹出栈桢,回到上一个函数继续执行
方式3: 使用pthread_exit()
线程自己调用函数终止,pthread_ jion()函数里的retval(退出码)就是pthread_exit的参数
#include <pthread.h>
void pthread_exit(void *retval);
线程的退出函数,retval是退出码,该函数只是当前线程退出,不影响其他线程
- 1
- 2
- 3
方式4:
如果线程是通过pthread_ cancel被别的线程异常终止,则retval(退出码)就是PTHREAD_ CANCELED
#include <pthread.h>
int pthread_cancel(pthread_t thread);//线程ID
//可以终止自己的线程也可以终止别人的线程
//成功返回0,失败返回-1
//终止自己算是成功退出,发回0,终止别的进程,则那个进程算是失败退出,返回-1;
//不是立马执行,会延迟一会,等到cancel的时间点
//系统调用的都是cancel点
- 1
- 2
- 3
- 4
- 5
- 6
- 7
例1:使用return退出
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
void* thread_func(void* arg)
{
(void)arg;
int count = 0;
while(1)
{
++count;
if(count > 10)
return NULL;//使用return方式退出
printf("I am new thread:%d,count: %d\n",pthread_self(),count);
sleep(1);
}
}
int main()
{
pthread_t tid;
if ( pthread_create(&tid,NULL,thread_func,NULL) == -1){
perror("pthread_create");
exit(1);
}
while(1){
printf("I am main thread:%d\n",pthread_self());
sleep(1);
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
结果:
例2:exit退出
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
void* thread_func(void* arg)
{
(void)arg;
int count = 0;
while(1)
{
++count;
if(count > 10)
exit(1);//使用return方式退出
printf("I am new thread:%d,count: %d\n",pthread_self(),count);
sleep(1);
}
}
int main()
{
pthread_t tid;
if ( pthread_create(&tid,NULL,thread_func,NULL) == -1){
perror("pthread_create");
exit(1);
}
while(1){
printf("I am main thread:%d\n",pthread_self());
sleep(1);
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
结果:
例3:pthread_exit退出
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
void* thread_func(void* arg)
{
(void)arg;
int count = 0;
while(1)
{
++count;
if(count > 10)
pthread_exit(NULL);
printf("I am new thread:%d,count: %d\n",pthread_self(),count);
sleep(1);
}
}
int main()
{
pthread_t tid;
if ( pthread_create(&tid,NULL,thread_func,NULL) == -1){
perror("pthread_create");
exit(1);
}
while(1){
printf("I am main thread:%d\n",pthread_self());
sleep(1);
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
结果:
例4:pthread_cancel
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
void* thread_func(void* arg)
{
(void)arg;
while(1)
{
printf("I am new thread:%d\n",pthread_self());
sleep(1);
}
}
int main()
{
pthread_t tid;
if ( pthread_create(&tid,NULL,thread_func,NULL) == -1){
perror("pthread_create");
exit(1);
}
int count = 0;
void* ret;
while(1){
++count;
if(count > 5){
pthread_cancel(tid);
pthread_join(tid,&ret);
}
if(ret == PTHREAD_CANCELED)
printf("thread is return,thread id: %d, return code:PTHREAD_CANCELED\n",tid);
else
printf("thread is return,thread id: %d, return code:NULL\n",tid);
printf("I am main thread: %d,count: %d\n",pthread_self(),count);
sleep(1);
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
结果:
6.6 线程分离
6.6.1 线程的两种状态——可结合、可分离
线程分为两种状态:可结合态和分离态;默认情况下,线程被创建成可结合的。
1. 可结合态:
- 这种状态下的线程是能够被其他进程回收其资源或杀死的,这句话我的理解是:与其说它能够被其他进程回收或杀死,不如说它需要被其他进程回收或杀死;当它在被其他线程回收之前,它的存储器资源(如栈)是不会释放的;
- 这跟子进程很相似,如果不用父进程wait回收的话,就会变成僵尸进程同理,如果一个可结合态线程不用pthread_join回收,则会变成类似僵尸进程
2. 分离态
- 这种状态下的线程是不能够被其他线程回收或杀死的;它的存储资源在它终止时由系统自动释放
- 为了避免存储器泄漏,每个可结合线程需要显示的调用pthread_join回收;>要么就将其变成分离态的线程
6.6.2 线程分离函数—–pthread_detach
#include <pthread.h>
int pthread_detach(pthread_t thread);
//将pthread_thread对应的线程设为分离态的线程
- 1
- 2
- 3
- 4
pthread_detach的两种用法:
- 新线程中写:pthread_detach(pthread_self());
- 主线程中写:pthread_detach(thread);
注:多个线程,是在同一个进程中的,它们都共享着同一块虚拟空间,第一种方法是将自己从这些线程中分离出来;
第二种方法是将指定的线程从这些线程中分离出去;简单来说就是,一个是自己把自己弄出去,一个是让别人把自己弄出去
<link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/markdown_views-ea0013b516.css">
</div>