24-线程共享资源问题

2019-07-14 10:13发布

class="markdown_views prism-atom-one-light">

1. 线程共享资源

  如果说pthread_create函数跟fork函数是对应的,一个创建线程,一个创建进程。   但是进程调用fork创建进程的代价较高,调用的过程实际上非常复杂,即便是依靠写时复制机制,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着fork调用在时间上的开销比pthread_create函数更多(关于fork函数参考:18-用fork函数创建新进程)。    但线程解决了这个问题,线程之所以能够方便,快速的共享数据,是因为进程调用fork创建子进程所需复制的诸多进程属性在线程间本来就是共享的,无需复制内存页,页表等(因为复制内存页和页表花费的开销不少),这对线程来说,效率提高了不少。当然这是有代价的:不过这要避免出现多个线程同时修改同一份数据的情况,这需要使用线程同步机制(这里暂时不讨论同步问题)。 在这里插入图片描述
图1(图片摘自《linux/unix系统编程手册》)

从图1来看,由于同一进程的多个线程共享进程的资源,比如全局内存(数据段和堆),除此之外还共享以下资源和环境:
代码文本段
打开的文件描述符
信号处理函数
当前工作目录
用户id和组id
进程id和父进程id

有些资源是每个线程各自独有一份,非共享:
线程id
用户空间栈
errno变量
信号屏蔽字
调度优先级

2. 线程共享实验——全局变量

#include #include #include #include //全局变量 int var = 100; //线程主控函数 void *tfn(void *arg) { //修改全局变量var的值 var = 200; printf(" create thread succesful "); return NULL; } int main(void) { //主控线程第一次打印var printf("before pthread_create var = %d ", var); pthread_t tid; pthread_create(&tid, NULL, tfn, NULL); sleep(1); //主控线程再次打印var printf("after pthread_create, var = %d ", var); return 0; }
程序执行结果:
在这里插入图片描述 说明线程间是共享全局变量的

3. 线程共享实验——局部变量


  既然全局变量是共享的,那么局部变量是不是也是共享的呢?为了验证这个问题,我们来看实验二
#include #include #include #include //线程主控函数 void *tfn(void *arg) { int i = (int)arg; //局部变量 int temp_val = 250; if(i == 0){ //线程1修改了temp_val后,打印temp_val的值 temp_val = 100; printf("pthread1 , temp_val = %d " , temp_val); }else{ //线程2打印temp_val的值 printf("pthread2 , temp_val = %d " , temp_val); } printf(" create thread succesful "); return NULL; } int main(void) { pthread_t tid[2]; int i; for(i = 0; i < 2; i++){ pthread_create(&tid[i] , NULL , tfn , (void *)i); } sleep(1); return 0; }
程序执行结果:
在这里插入图片描述   局部变量temp_val的初始值是250,在创建了2个线程,线程1修改了temp_val的值为100并打印,而线程2再访问temp_val的值还是250,这说明线程间是不共享局部变量的
  问题来了,为什么线程间不共享局部变量,为了弄明白这个问题,我们还得回到图1。 在这里插入图片描述
图2

  首先,线程的生命周期一般是在线程主函数中,当线程一创建就会在用户栈开辟一块空间给线程主函数使用,这意味着我们每创建一个线程都会在用户栈开辟一块空间给线程主函数使用。如图2所示:线程1修改局部变量temp_val的值为100,实际上修改的是线程1的栈里的temp_val的值,并不会影响线程2的栈中的temp_val,所以线程2打印temp_val的值还是250,也就是说多线程不共享用户栈。

4. 线程和进程的区别


  另外之前在学习进程时,我们的理解是进程就是程序运行的执行体,而实际上进程一旦创建就自动包含了一个主线程,真正的执行体是主线程。   那么进程是什么?我们可以把进程理解为空间上的概念,它为所有的执行体(线程)提供必要的资源(内存、文件描述符、代码等),而线程,是时间上的概念,它是抽象的、假想的、动态的指令执行过程。   可以把进程理解为学校或学校的各种资源,线程是学校里的一个个学习的学生,学生共享学校里的各种资源。   但是以上这些都是概念上的线程和进程的区别,如果我们要真正的本质区分进程和线程,就是看是否共享PCB进程控制块,因为进程间PCB是各自独立的,而线程间PCB是共享的。   因为线程间是共享全局变量的,这说明了线程间的虚拟地址空间是一样的,pthread_create函数底层也复制了一份一模一样的PCB,所以说一个线程修改了数据空间的数据的话,其他线程都会因此受到影响。