[Linux系统编程]线程概念,三级映射,线程操作函数

一.线程概念

线程概念:
进程:有独立的 进程地址空间。有独立的pcb。
进程是分配资源的最小单位。
线程:没有独立的进程地址空间。有独立的pcb。
进程是cup执行的最小单位。
线程有线程id和线程号,线程id是用于进程内部标识线程的属性,而线程号是用于cpu标识执行单位的属性。
-
-Linux内核 实现线程的原理如下
-在这里插入图片描述
线程之间的共享与非共享关系
线程共享:
独享 栈空间(内核栈、用户栈),每个线程有独立的栈帧
共享 全局区,代码区,堆区 ./text. /data ./rodata ./bsss heap —> 共享【全局变量】

二. 三级映射(三级页表机制)

进程地址空间内部在经过MMU将数据映射到内存时,要经过三级页表查询逻辑地址映射到的实际物理地址,三级页表存在于进程的pcb中,对于线程来说,虽然pcb各自独立,但pcb中的三级页表是相同的。
在这里插入图片描述

三.线程操作函数

线程id类型: pthread_t

1.pthread_t pthread_self( ); 获取线程id。
返回值:线程id
出错返回:错误的error码。可以用 printf(strerror(ret))打印错误信息;

2.int pthread_create(pthread_t* tid, const pthread_attr_t* attr, 回调函数, void* arg);创建子线程。

参1:传出参数,表新创建的子线程 id
参2:线程属性。传NULL表使用默认属性。
参3:子线程回调函数。创建成功,ptherad_create函数返回时,该函数会被自动调用。void* (void*) 返回值和参数都是void* 类型
参4:参3的参数。没有的话,传NULL
返回值:成功:0 失败:errno

3.void pthread_exit(void* retval); 退出当前线程。

	retval:退出值。 无退出值时,NULL

该函数与进程退出函数exit()类似,目的是退出线程。
将其与其它函数进行对比:

exit(); 退出当前进程。
return: 返回到调用者那里去,结束当前函数。
pthread_exit(): 退出当前线程。

由于主线程可能先于子线程结束,所以子线程的可能在主线程结束前不能完全执行,可以用主线程sleep等待子线程结束来解决,而现在就可以使用pthread_exit来解决了。方法就是将return 0替换为pthread_exit,只退出当先线程,不会对其他线程造成影响。

4.int pthread_join(pthread_t thread, void** retval) 阻塞 回收线程。
thread: 待回收的线程id
retval:传出参数。 回收的那个线程的退出值。
线程异常借助,值为 -1。
返回值:成功:0 失败:errno

回收线程并获取其返回值的例子:

1.#include <stdio.h>  
2.#include <stdlib.h>  
3.#include <string.h>  
4.#include <unistd.h>  
5.#include <errno.h>  
6.#include <pthread.h>  
7.  //子线程返回此结构体
8.struct thrd {  
8.    int var;  
9.    char str[256];  
11.};  
10.  //错误处理
13.void sys_err(const char *str)  
14.{  
11.    perror(str);  
12.    exit(1);  
17.}  
13.  //线程执行函数
19.void *tfn(void *arg)  
20.{  
14.    struct thrd *tval;  
15.    //利用指针堆区创建,由于线程间共享堆区,所以主线程可以通过该指针访 
16.    //问结构体内容,反观创建在栈上,若子线程返回后结束,栈区连同该结构体
17.    //一起释放,主线程就无法访问。
18.    tval = malloc(sizeof(tval));  
19.    tval->var = 100;  
20.    strcpy(tval->str, "hello thread");  
21.    //子线程将此结构体地址转为void*返回
22.    return (void *)tval;  
28.}  
23.  
30.int main(int argc, char *argv[])  
31.{  
24.    pthread_t tid;  
25.    struct thrd *retval;  
26. 
27.    int ret = pthread_create(&tid, NULL, tfn, NULL);  
28.    if (ret != 0)  
29.        sys_err("pthread_create error");  
30.  
31.    //int pthread_join(pthread_t thread, void **retval);  
32.    ret = pthread_join(tid, (void **)&retval);  
33.    if (ret != 0)  
34.        sys_err("pthread_join error");  
35.  
36.    printf("child thread exit with var= %d, str= %s\n", retval->var, retval->str);  
37.      
38.    pthread_exit(NULL);  
39.  
49.}  

结果:
在这里插入图片描述
5.int pthread_cancel(pthread_t thread); 杀死一个线程。 (需要到达取消点(保存点))
取消点:相当于进入内核的一个契机,当子线程逻辑中有某些操作需要进入内核时,检查是否取消该线程。

参数:
thread: 待杀死的线程id
返回值:成功:0 失败:errno

如果,子线程没有到达取消点, 那么 pthread_cancel 无效。
我们可以在程序中,手动添加一个取消点。使用 pthread_testcancel();

成功被 pthread_cancel() 杀死的线程,返回(void*) -1. 可以使用pthead_join 回收。

借助cancel函数杀死线程例子:

1.#include <stdio.h>  
2.#include <stdlib.h>  
3.#include <string.h>  
4.#include <unistd.h>  
5.#include <errno.h>  
6.#include <pthread.h>  
7.  
8.  
9.void *tfn(void *arg){  
10.    while (1) {  
11.        printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());  
12.        sleep(1);  
13.    }  
14.  
15.    return NULL;  
16.}  
17.  
18.int main(int argc, char *argv[]){  
19.    pthread_t tid;  
20.  
21.    int ret = pthread_create(&tid, NULL, tfn, NULL);  
22.    if (ret != 0) {  
23.        fprintf(stderr, "pthread_create error:%s\n", strerror(ret));  
24.        exit(1);  
25.    }  
26.  
27.    printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());  
28.    //5秒后执行cancel函数,杀死线程
29.    sleep(5);  
30.  
31.    ret = pthread_cancel(tid);          // 以tid(线程id)终止线程  
32.    if (ret != 0) {  
33.        fprintf(stderr, "pthread_cancel error:%s\n", strerror(ret));  
34.        exit(1);  
35.    }  
36. 
37.
38.    pthread_exit((void *)0);  
39.}  

结果
在这里插入图片描述

注意一点,pthread_cancel工作的必要条件是进入内核,如果tfn真的奇葩到没有进入内核,则pthread_cancel不能杀死线程,此时需要手动设置取消点,就是pthread_testcancel()

6.int pthread_detach(pthread_t thread); 设置线程分离

线程分离,指线程执行完毕后无需其他线程使用join回收,而是会将自身回收。

thread: 待分离的线程id
返回值:成功:0 失败:errno

1.#include <stdio.h>  
2.#include <stdlib.h>  
3.#include <string.h>  
4.#include <unistd.h>  
5.#include <errno.h>  
6.#include <pthread.h>  
7.  
8.  
9.void *tfn(void *arg)  
10.{  
9.    printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());  
10.  
11.    return NULL;  
14.}  
12.  
16.int main(int argc, char *argv[])  
17.{  
13.    pthread_t tid;  
14.     //  线程创建
15.    int ret = pthread_create(&tid, NULL, tfn, NULL);  
16.    if (ret != 0) {  
17.        fprintf(stderr, "pthread_create error: %s\n", strerror(ret));  
18.        exit(1);  
19.    }  
20. 
21.     // 设置线程分离 线程终止,会自动清理pcb,无需回收  
22.    ret = pthread_detach(tid);            
23.    if (ret != 0) {  
24.        fprintf(stderr, "pthread_detach error: %s\n", strerror(ret));  
25.        exit(1);  
26.    }  
27     //等待子线程结束后,再使用join回收,测试结果
28.    sleep(1);  
29.     
30.    ret = pthread_join(tid, NULL);  //此时线程已经自动回收PCB,此函数会出错
31.    printf("join ret = %d\n", ret);  
32.    if (ret != 0) {  
33.        fprintf(stderr, "pthread_join error: %s\n", strerror(ret));  
34.        exit(1);  
35.    }  
36.  
37.    printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());  
38.  
39.    pthread_exit((void *)0);  
43.}  

结果,产生参数错误
在这里插入图片描述7.进程和线程控制原语对比
线程控制原语 -----------------进程控制原语
pthread_create() ----------------- fork();

pthread_self() -------------------- getpid();

pthread_exit() -------------------- exit(); / return

pthread_join() -------------------- wait()/waitpid()

pthread_cancel() ----------------- kill()

pthread_detach()----------------- 无

四.线程属性设置分离线程

pthread_create()的第二个参数,指定线程的属性,传入NULL表示默认属性,也可以传入线程属性结构体pthread_attr_t 来控制线程属性。

此次使用线程属性设置线程的线程分离

pthread_attr_t attr 创建一个线程属性结构体
pthread_attr_init(&attr); 初始化线程属性
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 设置线程属性为分离态
pthread_create(&tid, &attr, tfn, NULL); 传入修改后的线程属性 创建为分离态的新线程
pthread_attr_destroy(&attr); 销毁线程属性

五.线程使用注意事项

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值