zoukankan      html  css  js  c++  java
  • Linux系统编程——线程(1)

    前情提要: Linux用户级线程和内核级线程区别

    线程概要

    Linux内核线程实现原理

    类Unix系统中,早期是没有“线程”概念的,80年代才引入,借助进程机制实现出了线程的概念。因此在这类系统中,进程和线程关系密切。

    1. 轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone

    2. 从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页表是相同的

    3. 进程可以蜕变成线程

    4. 线程可看做寄存器和栈的集合

    5. 在linux下,线程最是小的执行单位;进程是最小的分配资源单位

    查看线程命令:ps -elf|grep thread

    1556722663332

    三级映射:进程PCB --> 页目录(可看成数组,首地址位于PCB中) --> 页表 --> 物理页面 --> 内存单元


    线程的共享/不共享资源

    线程共享资源 线程不共享资源
    文件描述符表 线程id
    每种信号的处理方式 处理器现场和栈指针(内核栈)
    当前工作目录 独立的栈空间(用户空间栈)
    用户ID和组ID errno变量
    内存地址空间(.text/.data/.bss/heap/共享库) 信号屏蔽字
    调度优先级

    线程优缺点

    优点: 1. 提高程序并发性 2. 开销小 3. 数据通信、共享数据方便

    缺点: 1. 库函数,不稳定 2. 调试、编写困难、gdb不支持 3. 对信号支持不好

    线程控制原语


    pthread_self

    获取线程ID。其作用对应进程中 getpid() 函数。

    ​ pthread_t pthread_self(void); 返回值:成功:0; 失败:无!

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

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


    pthread_create

    创建一个新线程。 其作用,对应进程中fork() 函数。

    ​ int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void (start_routine) (void *), void *arg);

    ​ 返回值:成功:0; 失败:错误号 -----Linux环境下,所有线程特点,失败均直接返回错误号。

    参数:

    ​ pthread_t:当前Linux中可理解为:typedef unsigned long int pthread_t;

    参数1:传出参数,保存系统为我们分配好的线程ID

    ​ 参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。

    ​ 参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。

    ​ 参数4:线程主函数执行期间所使用的参数。

    练习:创建一个新线程,打印线程ID。注意:链接线程库 -lpthread

    #include <stdio.h>
    #include <pthread.h>
    #include <unistd.h>
    
    void *tfn(void *arg)
    {
    	printf("I'm thread, Thread_ID = %lu
    ", pthread_self());
    	return NULL;
    }
    
    int main(void)
    {
    	pthread_t tid;
    	pthread_create(&tid, NULL, tfn, NULL);
    	sleep(1);
    	printf("I am main, my pid = %d
    ", getpid());
    
    	return 0;
    }
    

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

    全局变量:

    #include <stdio.h>
    #include <pthread.h>
    #include <stdlib.h>
    #include <unistd.h>
    
    int var = 100;
    
    void *tfn(void *arg)
    {
    	var = 200;
    	printf("thread
    ");
    	return NULL;
    }
    
    int main(void)
    {
    	printf("At first var = %d
    ", var);
    	pthread_t tid;
    	pthread_create(&tid, NULL, tfn, NULL);
    	sleep(1);
    	printf("after pthread_create, var = %d
    ", var);
    
    	return 0;
    }
    

    1556723666858

    传参:

    #include <func.h>
    
    void *tfn(void *arg){
        int* var = (int*)arg;
        *var = 200;
        printf("thread
    ");
        return NULL;
    }
    
    int main()
    {
        int var = 100;
        printf("At first var = %d
    ", var);
        pthread_t tid;
        pthread_create(&tid, NULL, tfn, &var);
        sleep(1);
    
        printf("after pthread_create, var = %d
    ", var);
        
        return 0;
    }
    

    1556723909222


    pthread_exit

    作用:将单个线程退出

    ​ void pthread_exit(void *retval); 参数:retval表示线程退出状态,通常传NULL

    线程中,**禁止使用exit函数,会导致进程内所有线程全部退出**。所以,多线程环境中,应尽量少用,或者不使用exit函数,取而代之使用pthread_exit函数,将单个线程退出。任何线程里exit导致进程退出,其他线程未工作结束,主控线程退出时不能return或exit。
    

    另注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。


    pthread_join

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

    ​ int pthread_join(pthread_t thread, void **retval); 成功:0;失败:错误号

    ​ 参数:thread:线程ID (【注意】:不是指针);retval:存储线程结束状态。

    ​ 对比记忆:

    ​ 进程中:main返回值、exit参数-->int;等待子进程结束 wait 函数参数-->int *

    ​ 线程中:线程主函数返回值、pthread_exit-->void *;等待线程结束 pthread_join 函数参数-->void **

    调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

    1. 如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。

    2. 如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存放的是常数PTHREAD_CANCELED。

    3. 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。

    4. 如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数。

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <stdlib.h>
    
    typedef struct {
    	int a;
    	int b;
    } exit_t;
    
    void *tfn(void *arg)
    {
    	exit_t *ret;
    	ret = malloc(sizeof(exit_t)); 
    
    	ret->a = 100;
    	ret->b = 300;
    
    	pthread_exit((void *)ret);
    }
    
    int main(void)
    {
    	pthread_t tid;
    	exit_t *retval;
    
    	pthread_create(&tid, NULL, tfn, NULL);
    
    	/*调用pthread_join可以获取线程的退出状态*/
    	pthread_join(tid, (void **)&retval);      //wait(&status);
    	printf("a = %d, b = %d 
    ", retval->a, retval->b);
    
    	return 0;
    }
    

    1556724875987


    pthread_cancel

    杀死(取消)线程 其作用,对应进程中 kill() 函数。

    ​ int pthread_cancel(pthread_t thread); 成功:0;失败:错误号

    ​ 【注意】:线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点(检查点)。

    ​ 类似于玩游戏存档,必须到达指定的场所(存档点,如:客栈、仓库、城里等)才能存储进度。杀死线程也不是立刻就能完成,必须要到达取消点。

    ​ 取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write..... 执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。也可参阅 APUE.12.7 取消选项小节。

    可粗略认为一个系统调用(进入内核)即为一个取消点。如线程中没有取消点,可以通过调用pthreestcancel函数自行设置一个取消点。

    被取消的线程, 退出值定义在Linux的pthread库中。常数PTHREAD_CANCELED的值是-1。可在头文件pthread.h中找到它的定义:#define PTHREAD_CANCELED ((void *) -1)****。因此当我们对一个已经被取消的线程使用pthread_join回收时,得到的返回值为-1。

    终止线程的三种方法。注意“取消点”的概念。

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <stdlib.h>
    
    
    void *tfn1(void *arg)
    {
    	printf("thread 1 returning
    ");
    
    	return (void *)111; 
    }
    
    void *tfn2(void *arg)
    {
    	printf("thread 2 exiting
    ");
    	pthread_exit((void *)222);
    }
    
    void *tfn3(void *arg)
    {
    	while (1) {
    		//printf("thread 3: I'm going to die in 3 seconds ...
    ");
    		//sleep(1);
    
    		pthread_testcancel();	//自己添加取消点*/
    	}
    
        return (void *)666;
    }
    
    int main(void)
    {
    	pthread_t tid;
    	void *tret = NULL;
    
    	pthread_create(&tid, NULL, tfn1, NULL);
    	pthread_join(tid, &tret);
    	printf("thread 1 exit code = %d
    
    ", (int)tret);
    
    	pthread_create(&tid, NULL, tfn2, NULL);
    	pthread_join(tid, &tret);
    	printf("thread 2 exit code = %d
    
    ", (int)tret);
    
    	pthread_create(&tid, NULL, tfn3, NULL);
    	sleep(3);
        pthread_cancel(tid);
    	pthread_join(tid, &tret);
    	printf("thread 3 exit code = %d
    ", (int)tret);
    
    	return 0;
    }
    

    1556725457481

    终止线程方式

    总结:终止某个线程而不终止整个进程,有三种方法:

    1. 从线程主函数return。这种方法对主控线程不适用,从main函数return相当于调用exit。

    2. 一个线程可以调用pthread_cancel终止同一进程中的另一个线程。

    3. 线程可以调用pthread_exit终止自己。


    控制原语对比

    ​ 进程 线程

    ​ fork pthread_create

    ​ exit pthread_exit

    ​ wait pthread_join

    ​ kill pthread_cancel

    ​ getpid pthread_self 命名空间

  • 相关阅读:
    java中级或者高级面试题分享
    java常使用的框架
    spring的定时器
    ArrayList源码理解
    缓存 Memached
    ORM框架
    Web处理方式
    git使用
    Entity Framework
    .net 学习笔记2
  • 原文地址:https://www.cnblogs.com/Mered1th/p/10801287.html
Copyright © 2011-2022 走看看