线程:

线程基础概念:

线程在Linux中又称轻量级进程。而且它和进程都有PCB(进程控制块)。可是差别是进程的虚拟地址空间是独享的,也就是每一个进程都有自己的虚拟地址空间。可是线程的PCB是共享的,在同一个虚拟地址空间里面,每一个线程有自己的PCB。尽管每一个线程都有自己的PCB,可是从内核的角度来看,进程和线程是一样的,这是由于同一个虚拟地址空间里面的每一个线程的PCB指向的内存资源的三级页表是同样的。

在Linux下,能够把线程看做是最小的运行单位(进程内部运用多线程完毕任务)。而进程是最小的分配资源单位(系统以进程为单位来创建,而没有创建一个线程来运行的说法)。实际上,不管是创建进程的fork,还是创建线程的pthread_create,底层的实现都是调用同一个内核函数clone。假设复制对方的地址空间。那就会产生一个”进程”。假设共享对方的地址空间,就产生一个”线程”。进程能够看作是仅仅有一个线程的进程。

由于。linux内核不区分进程和线程,仅仅在用户层面上区分。所以全部有关线程操作的函数都是库函数,而不是系统调用。

线程共享资源:

1.共享文件描写叙述符表(pcb都是指向的同一块物理地址,而文件描写叙述符表存在于pcb中,当然同样)

2.共享信号的处理方式(同理)

3.共享当前工作文件夹(同理)

4.共享进程ID和组ID(线程还是处于进程中的。所以进程ID和组ID都同样)

5.共享一部分内存地址空间(.text/.data/.bss/heap/共享库)(方便了数据共享和同步)

线程非共享资源:

1.线程id(在同一个进程中,为了标识不同的线程)

2.寄存器的值(由于线程是并发运行的,每一个线程有自己不同的运行情况,线程间进行切换时,必须要将原来的线程的寄存器集合的值保存下来。以方便又一次切换回来的时候恢复)

3.栈空间(栈空间的独立保证了线程独立运行。不受其他线程的影响)

4.errno变量(同样也是保障线程的独立运行。不受其他线程的影响)

5.信号屏蔽字(同理)

6.调度优先级(线程须要像进程那样被调度,所以须要有被调度的參数,就是优先级)

线程优缺点:

长处:

1.提高了程序的并发性

2.开销比进程小(不用像进程那样,每次都创建自己独有的虚拟空间)

3.数据通信和共享方便(由于线程共享了一部分内存地址空间)

缺点:

1.库函数不如系统调用稳定

2.gdb不支持其调试(gdb的产生远早于线程的增加)

3.对信号的支持不好(同样信号的诞生和线程并非同一时期)

控制原语:

查看线程ID:

函数原型:pthread_t pthread_self(void)

返回值:返回线程ID,无失败情况(由于就算不创建线程,进程能够看作是仅仅有一个主线程的进程)。pthread_t类型在linux系统下是无符号整数。在其他系统可能是结构体。

而且线程ID是进程内部的识别标志。所以不同进程间线程ID同意同样。

创建线程:

函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

返回值:成功返回0.失败返回错误号

參数:thread:传出參数。保存系统为我们分配好的线程ID。attr:通常传NULL,表示使用线程默认属性。

;start_routine:函数指针,指向线程的主函数,该函数运行结束之后,该线程也结束。arg:线程主函数运行时传入的參数

注意:当调用了pthread_create函数之后,当前的线程会继续向下运行,而新创建的线程会去运行我们传入的start_routine函数,该函数运行结束后。新创建的这个线程也就结束了。

当我们使用gcc编译的关于线程操作的时候。须要额外加上-pthread參数。

样例:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h> void *print_id(void *arg) //线程所运行的函数
{
printf("%dth pthread id:%lu\n", (int )arg, pthread_self());
}
int main()
{
int ret;
pthread_t tid;
int i = 0;
for(; i < 5; i++) //循环创建5个线程
{
ret = pthread_create(&tid, NULL, print_id, (void *)i);
if(ret != 0)
{
printf("%s\n", strerror(ret)); //由于线程创建失败返回的错误码不保存在errno中,所以用strerror函数将其转成错误信息进行输出
}
} printf("%dth pthread id:%lu\n", i, pthread_self()); sleep(5); //这里是为了防止各线程还没运行完,进程就先退出了
return 0;
}

须要记住,线程间共享全局变量

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h> int var = 100; void *glb_share(void *arg) //改变全局变量var的值
{
var = 200;
} int main()
{
pthread_t tid;
int ret;
if((ret = pthread_create(&tid, NULL, glb_share, NULL)) != 0)
{
printf("%s\n", strerror(ret));
}
sleep(1);
printf("var : %d \n", var); //输出var的值。输出结果为200。 已经改变。 sleep(1);
return 0;
}

线程退出:

函数原型:void pthread_exit(void *retval)

參数:retval:表示线程退出状态。通常传NULL

注意:exit()函数的作用是退出当前进程,而pthread_exit()函数是退出当前线程。也就是说,假设线程中调用了exit()函数,那么这个进程就退出了,程序也就结束了。

这里我们借助之前循环创建线程的代码来进行一个測试,加强线程和进程之间关系的理解:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h> void *print_id(void *arg)
{
sleep(2); //相比于之前,这里多加了睡眠2s
printf("%dth pthread id:%lu\n", (int )arg, pthread_self());
}
int main()
{
int ret;
pthread_t tid;
int i = 0; for(; i < 5; i++)
{
ret = pthread_create(&tid, NULL, print_id, (void *)i);
if(ret != 0)
{
printf("%s\n", strerror(ret));
}
if(i == 2) //创建了3个线程之后,主线程就退出
pthread_exit(NULL);
} printf("%dth pthread id:%lu\n", i, pthread_self());
sleep(5);
return 0;
}

运行的结果是会输出创建的前3个线程的ID的,在线程sleep的那2秒中,主线程已经退出了。可是其余的线程还能够继续运行。这就说明了线程之间的独立性,而主函数之后的代码也不会运行了,所以印证了之前说的能够把一个进程看成是拥有一个主线程的进程这句话。假如将pthread_exit(NULL)函数换成exit(1)。能够发现程序立即就结束了。这说明了exit函数是退出进程。而pthread_exit是退出单个线程。

堵塞等待线程退出:

函数原型:int pthread_join(pthread_t thread, void **retval)

返回值:成功返回0。失败返回错误号

參数:thread:要退出的线程的ID;retval:存储线程结束状态,假设是被其他线程调用pthread_cancel异常终止了,retval存放的值是常量PTHREAD_CANCELED。

线程分离:

函数原型:int pthread_detach(pthread_t thread)

返回值:成功返回0。失败返回错误号

參数:thread:要进行分离的线程ID

线程分离状态:线程主动与主线程断开联系。

线程结束后,其退出状态不被其他线程获取。而直接自己自己主动释放。

在普通情况下,线程终止之后,它的终止状态一直保留到其他线程调用pthread_join获取为止,然而设置为分离态之后,线程一旦终止就立马回收它占用的全部资源,不会保留终止状态。所以不能对一个设置了分离态的线程调用pthread_join。

样例:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h> void *detach_pthread(void *arg)
{
int exit_code = 233;
pthread_exit((void *)exit_code);
} int main()
{
pthread_t tid;
int ret;
void *retval; pthread_create(&tid, NULL, detach_pthread, NULL);
pthread_detach(tid); //设置分离态 ret = pthread_join(tid, &retval); //堵塞回收线程,并接收返回状态
if(ret != 0) //pthread_join调用失败之后输出原因
{
printf("pthread_join error:%s\n", strerror(ret));
}
else //否则输出退出状态
{
printf("exit code : %d\n", retval);
}
return 0;
}

这段代码运行的结果是:pthread_join error:Invalid argument(无效的參数)

这就说明了设置了分离态的线程已经脱离了pthread_join回收的范围了。

另外也能够通过设置线程的属性来达到线程分离。

杀死线程:

函数原型:int pthread_cancel(pthread_t thread)

返回值:成功返回0,失败返回错误码

參数:thread:要杀死的线程号

注意:这个函数并不像kill函数调用了就杀死所指定,而是要到达一个取消点(检查线程是否被取消)。一般是一些系统调用有取消点。比方read write close等。

只是我们能够调用pthread_testcancel()函数充当一个取消点,被取消的线程的返回值是PTHREAD_CANCELED(-1)。

样例:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h> void *test_cancel(void *arg)
{
int val = 3;
while(1)
{
pthread_testcancel();
}
pthread_exit((void *)val);
}
int main()
{
pthread_t tid;
void *ret = NULL;
pthread_create(&tid, NULL, test_cancel, NULL);
pthread_cancel(tid); //杀死创建的进程
pthread_join(tid, &ret); //堵塞等待线程退出。并获取退出状态
printf("thread exit code = %d\n", (int)ret);
return 0;
}

这段代码输出的结果是thread exit code = -1,说明了线程成功被杀死了,不然会返回3。

检查两个线程ID是否同样:

函数原型:int pthread_equal(pthread_t t1, pthread_t t2);

返回值:假设线程ID同样,返回非0值,否则返回0。

没有失败情况。

线程属性:

默认属性能够解决大多数情况。可是假设对性能有更高的要求,就能够通过改动线程属性。降低线程栈的大小,来降低内存的使用。

设置线程属性的结构体:

typedef struct
{
int detachstate; //线程分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam; //线程的调度參数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set; //线程的栈设置
void* stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
}pthread_attr_t;

主要结构体成员:

1.线程分离状态

2.线程栈大小(默认平均分配)

3.线程栈警备缓冲区大小(位于栈末尾)


属性值不能直接设置。须要用对应的函数操作。

部分相关函数:

线程属性初始化:

函数原型:int pthread_attr_init(pthread_attr_t *attr)

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

參数:attr:设置属性的结构体

注意:须要先初始化属性,然后设置相关属性,再创建线程。

销毁线程属性:

函数原型:int pthread_attr_destroy(pthread_attr_t *attr)

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

參数:要销毁的线程属性

获取线程分离状态:

函数原型:int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate)

返回值:成功返回0,失败返回错误码

參数:attr:设置属性的结构体;detachstate:传入參数,获取状态,PTHREAD_CREATE_DETACHED(分离态)PTHREAD_CREATE_JOINABLE(非分离态)。

设置线程分离状态:

函数原型:int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

返回值:成功返回0.失败返回错误码

參数:attr:设置属性的结构体;detachstate:传出參数。设置状态,PTHREAD_CREATE_DETACHED(分离态)PTHREAD_CREATE_JOINABLE(非分离态)。

获取线程的栈大小:

函数原型:int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);

返回值:成功返回0。失败返回错误码

參数:attr:设置属性的结构体;stacksize:默认的栈的大小

设置线程的栈大小:

函数原型:int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

返回值:成功返回0,失败返回错误码

參数:attr:设置属性的结构体;stacksize:传入參数。将设置的栈的大小

获取线程的栈的首地址和大小:

函数原型:int pthread_attr_getstack(pthread_attr_t *attr, void **stackaddr, size_t *stacksize);

返回值:成功返回0,失败返回错误码

參数:attr:设置属性的结构体;stackaddr:传出參数,栈的首地址;stacksize:传出參数,栈的大小

设置线程的栈的首地址和大小:

函数原型:int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);

返回值:成功返回0。失败返回错误码

參数:attr:设置属性的结构体;stackaddr:传入參数,新的栈的首地址。stacksize:传入參数,设置的栈的大小

当剩下的栈空间不够的时候,我们能够通过malloc函数或者mmap分配的空间作为新的栈的空间。

样例:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h> #define SIZE 0x100000
void *th_fun(void *arg)
{
while (1)
sleep(1);
}
int main(void)
{
pthread_t tid;
int err, detachstate, i = 1;
pthread_attr_t attr;
size_t stacksize;
void *stackaddr; pthread_attr_init(&attr); //初始化线程属性
pthread_attr_getstack(&attr, &stackaddr, &stacksize); //获取栈的信息
pthread_attr_getdetachstate(&attr, &detachstate); //获取分离态信息 if (detachstate == PTHREAD_CREATE_DETACHED) //假设是分离态
printf("thread detached\n");
else if (detachstate == PTHREAD_CREATE_JOINABLE) //假设不是分离态
printf("thread join\n");
else
printf("thread unknown\n"); pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //将线程设置为分离态 while (1) {
stackaddr = malloc(SIZE); //申请内存
if (stackaddr == NULL) {
perror("malloc");
exit(1);
}
stacksize = SIZE;
pthread_attr_setstack(&attr, stackaddr, stacksize); //设置栈大小
err = pthread_create(&tid, &attr, th_fun, NULL);
if (err != 0) {
printf("%s\n", strerror(err));
exit(1);
}
printf("%d\n", i++);
}
pthread_attr_destroy(&attr);
return 0;
}

线程使用注意事项:

1.malloc和mmap申请的内存能够被其他线程释放(由于堆空间共享)

2.避免有僵尸线程,浪费资源

3.假设在多线程中调用fork而且不立即exec,那除了调用fork的线程存在。其他的线程全部都会pthread_exit。

Unix线程概念、控制原语、属性的更多相关文章

  1. UNIX/Linux-进程控制(实例入门篇)

    UNIX进程   进程标识符 要想对进程控制,必须得获取进程的标识.每个进程都有一个非负整数表示的唯一进程ID,虽然是唯一的,但是进程ID可以重用.当一个进程终止后,其进程ID就可以再次使用了. 系统 ...

  2. 线程概念( 线程的特点,进程与线程的关系, 线程和python理论知识,线程的创建)

    参考博客: https://www.cnblogs.com/xiao987334176/p/9041318.html 线程概念的引入背景 进程 之前我们已经了解了操作系统中进程的概念,程序并不能单独运 ...

  3. python 全栈开发,Day41(线程概念,线程的特点,进程和线程的关系,线程和python 理论知识,线程的创建)

    昨日内容回顾 队列 队列 : 先进先出.数据进程安全 队列实现方式: 管道 + 锁 生产者消费者模型 : 解决数据供需不平衡 管道 双向通信 数据进程不安全 EOFError: 管道是由操作系统进行引 ...

  4. python全栈开发,Day41(线程概念,线程的特点,进程和线程的关系,线程和python理论知识,线程的创建)

    昨日内容回顾 队列 队列:先进先出.数据进程安全 队列实现方式:管道+锁 生产者消费者模型:解决数据供需不平衡 管道 双向通信,数据进程不安全 EOFError: 管道是由操作系统进行引用计数的 必须 ...

  5. Python之路(第四十一篇)线程概念、线程背景、线程特点、threading模块、开启线程的方式

    一.线程 ​ 之前我们已经了解了操作系统中进程的概念,程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程.程序和进程的区别就在于:程序是指令的集合,它是 ...

  6. UNIX基础概念

    UNIX基本概念 进程 从用户观点来看:进程是程序的一个执行实例. 从UNIX系统内部来看,是为运行程序提供执行环境的实体,是系统进行资源分配和调度运行的一个单位. 进程有三个特点: 1)进程有一个控 ...

  7. bootstrap-select js jQuery控制select属性变化

    bootstrap-select我想大家都不陌生是一个前端下拉框的插件非常好用,在select的标签中设置属性可以做很多功能控制,不过初始化之后怎么去修改网上找遍中文英文也没有一个交代自己研究好久研究 ...

  8. Linux/Unix 线程同步技术之互斥量(1)

    众所周知,互斥量(mutex)是同步线程对共享资源访问的技术,用来防止下面这种情况:线程A试图访问某个共享资源时,线程B正在对其进行修改,从而造成资源状态不一致.与之相关的一个术语临界区(critic ...

  9. js练习-控制div属性

    要开始练练js了,决定先按照Ferris大大的索引表一个个练,头一个就是控制div属性啦.看似挺简单的,不过平时jquery用惯了,用起来原生js还有点手生呢. 总之就是模仿加练习啦,先看看效果: 一 ...

随机推荐

  1. 摩登家庭第一季/全集Modern Family迅雷下载

    本季Modern Family Season 1 第一季(2009)看点:<摩登家庭>瞄准的是三对各有不同的美国家庭:一对普通父母与三个孩子.一对同性恋夫夫从越南领养了养女组成三口之家,一 ...

  2. linux批量改名

    rename 's/abc/xyz/' * 把包含abc的文件名,替换为xyz

  3. 深入理解Java Callable接口

    概述Callable和Runnbale一样代表着任务,区别在于Callable有返回值并且可以抛出异常.其使用如下: public class CallableDemo { static class ...

  4. 一步一步学SpringDataJpa——JpaRepository查询功能

    原文地址: https://blog.csdn.net/ming070423/article/details/22086169 1.JpaRepository支持接口规范方法名查询.意思是如果在接口中 ...

  5. 【UOJ Easy Round #1】

    数论/Trie/并查集 猜数 这题我是这样分析的…… $a*b=g*l=n=k^2 \ and \ (g|a,g|b) \Rightarrow (g*a')*(g*b' )=g*l=k^2 \\ \R ...

  6. go语言之进阶篇Ticker的使用

    Ticker是一个定时触发的计时器,它会以一个间隔(interval)往channel发送一个事件(当前时间),而channel的接收者可以以固定的时间间隔从channel中读取事件. 1.Ticke ...

  7. Recover Binary Search Tree leetcode java

    题目: Two elements of a binary search tree (BST) are swapped by mistake. Recover the tree without chan ...

  8. RxJava【创建】操作符 create just from defer timer interval MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  9. vue里ref ($refs)用法

    ref 有三种用法: 1.ref 加在普通的元素上,用this.ref.name 获取到的是dom元素 2.ref 加在子组件上,用this.ref.name 获取到的是组件实例,可以使用组件的所有方 ...

  10. 【Java】Java-UTC-时间戳处理

    Java-UTC-时间戳处理 java utc 时间戳_百度搜索 JAVA获取时间戳,哪个更快 - 潇湘客 - ITeye博客 Java获取UTC时间的方法详解_java_脚本之家 Java UTC时 ...