Linux——线程深度剖析(一),原来和进程有这些不同

Linux——线程深度剖析

  • 1.并发和并行
  • 2.同步和异步
  • 3.线程概念
    • 3.1什么是线程?
    • 3.2Linux内核线程实现原理
  • 4.线程的优缺点
  • 5.线程的操作
    • 5.1获取线程ID。
    • 5.2创建一个线程。
    • 5.3线程间共享全局变量
    • 5.4线程终止
    • 5.5线程等待
    • 5.6线程分离

1.并发和并行

  1. 并发 :在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。其中两种并发关系分别是同步和互斥。并发是指同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上有多个进程被同时执行的效果–宏观上并行,针对单核处理器
  2. 并行 :在单处理器中多道程序设计系统中,进程被交替执行,表现出一种并发的外部特种;在多处理器系统中,进程不仅可以交替执行,而且可以重叠执行。在多处理器上的程序才可实现并行处理。从而可知,并行是针对多处理器而言的。并行是同时发生的多个并发事件,具有并发的含义,但并发不一定并行,也亦是说并发事件之间不一定要同一时刻发生。(同一时刻,有多条指令在多个处理器上同时执行–针对多核处理器

2.同步和异步

  1. 同步:进程之间的关系不是相互排斥临界资源的关系,而是相互依赖的关系。进一步的说明:就是前一个进程的输出作为后一个进程的输入,当第一个进程没有输出时第二个进程必须等待。具有同步关系的一组并发进程相互发送的信息称为消息或事件。
  2. 异步:异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。

3.线程概念

3.1什么是线程?

Linux——线程深度剖析(一),原来和进程有这些不同_第1张图片

  • 在Linux环境下,线程就是轻量级的进程(LWP),本质仍是进程,而我们通俗所说的线程是C库中的概念
  • 线程分为主线程和工作线程,每个线程都拥有自己的task_struct结构体,但其中的内存指针指向同一块虚拟地址空间,没有独立的地址空间(共享)。
  • 线程:最小的执行单位,调度的基本单位
  • 进程:最小分配资源单位,可看成是只有一个线程的进程。
  • getpid()得到的是进程的pid,在内核中,每个线程都有自己的PID,要得到线程的PID,必须用syscall(SYS_gettid);
  • pthread_self函数获取的是线程标识符ID,线程ID在某进程中是唯一的,在不同的进程中创建的线程可能出现ID值相同的情况。是该线程在共享区中开辟空间的对应首地址

3.2Linux内核线程实现原理

  • 对于进程来说,相同的地址(同一个虚拟地址)在不同的进程中,反复使用而不冲突。原因是他们虽虚拟址一样,但,页目录、页表、物理页面各不相同。相同的虚拟址,映射到不同的物理页面内存单元,最终访问不同的物理页面。
  • 线程不同,两个线程具有各自独立的PCB,但内存指针指向同一个虚拟地址空间,共享同一个页目录,也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间。
  • 每个线程在共享区都会开辟一段属于自己的空间,存放:
    1.线程id
    2.处理器现场和栈指针(内核栈)
    3.独立的栈空间(用户空间栈)
    4.errno变量
    5.信号屏蔽字
    6.调度优先级
  • 如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。
  • 无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数clone。

4.线程的优缺点

  • 优点:1. 提高程序并发性 2. 开销小 3. 数据通信、共享数据方便
  • 缺点:1. 库函数,不稳定 2. 调试、编写困难、gdb不支持 3. 对信号支持不好

5.线程的操作

5.1获取线程ID。

其作用对应进程中 getpid() 函数。

pthread_t pthread_self(void); 
  • pthread_t:typedef unsigned long int pthread_t;

  • 返回值:成功:0; 失败:无!

  • 线程ID:pthread_t类型,本质:在Linux下为无符号整数(%lu),其他系统中可能是结构体实现

  • 线程ID是进程内部,识别标志。(两个进程间,线程ID允许相同)


5.2创建一个线程。

其作用,对应进程中fork() 函数。( 链接这些线程函数库时要使用编译器命令的“-lpthread”选项)

 `int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);`
  • 返回值:成功:0; 失败:错误号 -----Linux环境下,所有线程特点,失败均直接返回错误号。
  • 参数1:出参,保存系统为我们分配好的线程ID
  • 参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数
  • 参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
  • 参数4:线程主函数执行期间所使用的参数,但注意,传参不能传临时变量,因为临时变量在离开其作用域后空间就会被销毁,最好传入在堆上开辟的空间。
#include 
#include 
#include 
#include 
#include 
void* func(void* arg)
{
    while(1)
    {
        printf("i am work thread\n");
        sleep(1);
    }
    return NULL;
}

int main()
{
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, TStart, NULL);
    if(ret < 0)
    {
        perror("wrong!");
        return -1;
    }

    while(1)
    {
        printf("i am main thread\n");
        sleep(1);
    }
    return 0;
}

主进程结束后,所有的工作线程都会结束。该进程的进程pid=3472
在这里插入图片描述
pstack 3472查看该进程的堆栈信息:
Linux——线程深度剖析(一),原来和进程有这些不同_第2张图片

  • 【LWP:3472】:主线程PID,类型是pid_t,与进程PID3472相同
  • 【LWP:3473】:工作线程PID,类型是pid_t
  • 【Thread 0x7fdfc61af740】:主线程ID,类型是thread_t,是一段地址(64位),是该线程在共享区中开辟空间的对应首地址(图解的粉色框框)
  • 【Thread 0x7fdfc59bf700】:工作线程ID,类型是thread_t,是一段地址(64位),同上。

5.3线程间共享全局变量

线程默认共享数据段、代码段等地址空间,常用的是全局变量。而进程不共享全局变量,只能借助mmap

  1 #include <stdio.h>
  2 #include <signal.h>
  3 #include <unistd.h>
  4 #include <stdlib.h>
  5 #include <sys/wait.h>
  6 #include <pthread.h>
  7 int var=100;
  8 void* TStart(void* arg)
  9 {
 10     var = 200;
 11     printf("thread\n");
 12     return NULL;
 13 }
 14 
 15 int main()
 16 {
 17        printf("At first var = %d\n", var);
 18 
 19         pthread_t tid;
 20             pthread_create(&tid, NULL, TStart, NULL);
 21                 sleep(1);
 22 
 23                     printf("after pthread_create, var = %d\n", var);
 24 
 25                         return 0;
 26 }

Linux——线程深度剖析(一),原来和进程有这些不同_第3张图片


5.4线程终止

  • 从入口函数的return返回,线程退出
  • void pthread_exit(void *retval); 参数:返回信息,返回给等待线程退出的执行流信息,可传递NULL,谁调用谁退出
  • int pthread_cance1 (pthread_t thread);thread:线程标识符,调用该函数的执行流可以取消其他线程,但是需要知道其他线程的线程标识符也可以执行流自己取消自己,传入自己的线程标识符
  • int pthread_cancel(pthread_t thread);thread:线程标识符,调用该函数的执行流可以取消其他线程

注意:
1.线程在创建出来的时候,属性当中默认是joinable属性(意味着线程在退出的时候需要其他执行流来回收线程的资源)
2.使用exit将指定线程退出,可以吗? 结论:线程中,禁止使用exit函数,会导致进程内所有线程全部退出。

5.5线程等待

获取线程退出状态 其作用,对应进程中 waitpid() 函数

int pthread_join(pthread_t thread, void **retval);
  • 参数1 :线程ID
  • 参数2:存储线程结束状态,
    1.如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
    2.如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存放的是常数PTHREAD_CANCELED。
    3.如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。
    4.如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数
  • 成功:0;失败:错误号
#include 
#include 
#include 
#define THREADCOUNT 1
void* myStartThread(void* arg)
{
    (void)arg;
    while(1)
    {
        sleep(5);
        printf("i am workthread\n");
        pthread_exit(NULL);
    }
}
int main()
{
	pthread_t tid[THREADCOUNT];
    for(int i = 0; i < THREADCOUNT; i++)
    {
        int ret = pthread_create(&tid[i], NULL, myStartThread, NULL);
        if(ret < 0)
        {
            perror("pthread_create");
            return -1;
        }
    }
    
	for(int i = 0; i < THREADCOUNT; i++)
    {
        pthread_join(tid[i], NULL);}
    
    while(1)
    {
        printf("i am main thread\n");
        sleep(1);
    }

    return 0;	
} 

5.6线程分离

int pthread_detach(pthread_t thread);

改变线程的属性,将joinable属性改变成为detach属性,当线程退出的时候,不需要其他线程在来回收退出线程的资源,操作系统会默认回收掉

#include 
#include 
#include 
#define THREADCOUNT 1
void* myStartThread(void* arg)
{
	//1.pthread_detach(pthread_self());
    (void)arg;
    while(1)
    {
        sleep(5);
        printf("i am workthread\n");
    }
}
int main()
{
	pthread_t tid[THREADCOUNT];
    for(int i = 0; i < 1; i++)
    {
        int ret = pthread_create(&tid[i], NULL, myStartThread, NULL);
        if(ret < 0)
        {
            perror("pthread_create");
            return -1;
        }
    }
    
	for(int i = 0; i < THREADCOUNT; i++)
    {
        //2.pthread_detach(tid[i]);
    }
    
    while(1)
    {
        printf("i am main thread\n");
        sleep(1);
    }

    return 0;	
} 

可以放在工作线程之中,刚进入工作线程就进行分离,也可以在主线程中根据工作线程ID进程分离。

你可能感兴趣的:(linux)