Linux线程

1.Linux下的线程概念

在Linux下,其实没有真正意义上的线程概念,是用进程来模拟的

Linux的进程叫做轻量级进程

LWP是轻量级进程,在Linux下进程是资源分配的基本单位,线程是cpu调度的基本单位,而线程使用进程pcb描述实现,操作系统在创建线程时给每个线程都创建一个pcb结构体,并且同一个进程中的所有pcb共用同一个虚拟地址空间,因此相较于传统进程更加的轻量化有了更多执行流之后。进程变成了分配资源的基本实体,进程一旦被创建好之后,里面可能有多个执行流

与进程相比,线程在CPU执行时可能更加轻量化:pcb上下文肯定要切换,但是线程的地址空间、页表不用换;CPU调度时,看到的是LWP,也就是轻量级进程Light Weight Process

1.1 线程的优点

创建一个新线程的代价要比创建一个新进程小得多

与进程之间的切换相比,线程之间的切换需要OS的工作量更少

线程占用的资源比进程少得多

1.2 线程能够看到进程的所有资源,因为所有PCB都共享地址空间

好处:线程间通信成本低

坏处:存在大量的临界资源,势必需要使用各种互斥和同步机制保证临界资源的安全

1.3 线程异常

线程是进程的一个执行分支,当发生野指针/除0等异常操作导致线程退出的同时,也意味着进程触发了该错误,操作系统会给进程发送信号,终止进程。这体现了多线程下鲁棒性降低了。

1.4 线程的共享与独享

线程共享

文件描述符表

每种信号的处理方式

工作目录

用户id组id

线程独有

上下文数据(寄存器):体现了多个线程是可以切换的

独立的栈结构:体现了线程是独立运行的,各自的上下文数据不会互相影响

2.Linux线程控制:pthread线程库

首先要强调一点:pthread库并不是系统库,而是Linux下为了模拟线程而采用的第三方库。本质是封装了对于轻量级进程的某些操作。

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

thread:返回线程ID,这是进程地址空间的共享区的地址

attr:线程属性,可以指定线程的属性,如线程的栈大小,线程的优先级等,NULL是默认属性

start_routine:线程函数,线程启动后要执行的函数,线程函数的返回值作为线程的返回值

arg:传给线程启动函数的参数,如果

2.1同步和异步的区别

同步(synchronous):进程之间的关系不是相互排斥临界资源的关系,而是相互依赖的关系。进一步的说明:就是前一个进程的输出作为后一个进程的输入,当第一个进程没有输出时第二个进程必须等待。具有同步关系的一组并发进程相互发送的信息称为消息或事件。

异步(asynchronous):异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。


 

2.2线程概念

LWP是轻量级进程,在Linux下进程是资源分配的基本单位,线程是cpu调度的基本单位,而线程使用进程pcb描述实现,操作系统在创建线程时给每个线程都创建一个pcb结构体,并且同一个进程中的所有pcb共用同一个虚拟地址空间,因此相较于传统进程更加的轻量化有了更多执行流之后。进程变成了分配资源的基本实体,进程一旦被创建好之后,里面可能有多个执行流

进程:独立的地址空间,拥有PCB

线程:没有独立的地址空间(共享),拥有PCB

区别在:是否共享地址空间

线程:最小的执行单位,调度的基本单位

进程:最小分配资源单位,可看成是只有一个线程的进程

2.3Linux内核线程实现原理

轻量级进程,也有PCB,创建线程使用的底层函数和进程一样,都是clone

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

三级映射:

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

对于进程来说,相同的虚拟地址在不同的进程中反复使用而不会冲突,因为他们的页目录、页表、物理页面各不相同。

对于线程来说,虽然两个线程具有各自独立的PCB,但共享同一个页目录。

实际上无论创建进程的fork还是创建线程的的pthread_create,地层实现都是调用clone

克隆对方的地址空间,就产生一个进程

共享对方的自豪ikongjian,就产生一个线程

Linux内核是不区分进程和线程的,只在用户层面上进行区分

2.4线程优、缺点

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

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

优点相对突出,缺点均不是硬伤。Linux下由于实现方法导致进程、线程差别不是很大。

2.5线程与共享

线程之间共享全局变量

线程默认共享数据段、代码段等地址空间、常用的是全局变量。

进程不共享全局变量,只能用mmap

mmap:将一个文件或者其他对象映射进内存。文件被映射到多个页上,如果文件的大小不是所有页的总和,

最后一个页不被使用的空间将会清零。mmap在用户空间映射调用系统中作用很大。

头文件<sys/mman.h>

函数原型

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);


int munmap(void* start,size_t length);

mmap()必须以PAGE——SIZE为单位进行映射,而内存也只能以页为单位进行映射。

用法

内存映射的步骤

用open系统调用打开文件,并返回描述符fd

用mmap建立内存映射,并返回映射首地址指针start

对映射文件进行各种操作,显示,修改

用munmap(void *start,size_t length)关闭内存映射

用close系统调用关闭文件fd

主要用途

1.将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,内存读写取代IO读写,提高性能

2.将图书文件进行匿名内存映射,可以为关联进程提供共享内存空间

3.为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中

参数

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

start:指向欲映射的内存其实地址,通常设置为NULL,代表系统自动选定地址,映射成功后返回改地址

length:代表将文件中多大的部分映射到内存

prot:映射区域的保护方式

PROT_EXEC PROT_READ PROT_WRITE PROT_NONE

参数flags:影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。

MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。

MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。

MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。

MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。

MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。

MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。


 

2.6线程控制原语

(1)pthread_self函数 获取线程ID,对应进程中的getpid()函数

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

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

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

注意:不应使用全局变量 pthread_t tid,在子线程中通过pthread_create传出参数来获取线程ID,而应使用pthread_self。

(2)pthread_create函数 创建一个新线程,对应进程中的fork()

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

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

参数:

pthread_t:当前linux中可以理解为:typedef unsigned long int pthread_t;

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

参数2:传出NULL,表示使用线程默认属性

参数3:函数值帧,指向线程主函数,该函数运行结束,线程就结束

参数4:线程主函数执行期间所使用的参数,如要传多个参数,可用结构封装

#include <stdio.h>

#include <pthread.h>

#include <unistd.h>


void *fun(void *arg)

{

printf("I'm thread, Thread ID = %lu\n",pthread_self());

return NULL;

}


int main(void)

{

pthread_t tid;


pthread_create(&tid,NULL,fun,NULL);

sleep(1);//在多线程环境中,父线程终止,全部子线程被迫中止

printf("I am main,my pid = %d \n",getpid());


return 0;

}

结论:线程中,禁止使用exit函数,会导致进程内所有线程全部退出。

所以,多线程环境中,应尽量少用,或者不使用exit函数,取而代之使用pthread_exit函数,将单个线程退出。

任何线程里exit导致进程退出,其他线程未工作结束,主控线程退出时不能return或exit。

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

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

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

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

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



 

终止线程方式

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

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

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

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

控制原语对比

进程线程
forkpthread_create
exitpthread_exit
waitpthread_join
killpthread_cancel
getpidpthread_self



 

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值