线程

本文详细阐述了线程与进程的概念及其关系,包括两者的创建、调度、资源管理及线程间的通信等内容。同时介绍了线程与进程的区别,如资源消耗、调度单位等方面。

线程&进程的概念

线程:一个程序里的一个执行路线(执行流)称为线程。即:线程是一个进程内部的控制序列。
进程:程序的一次执行实例,即:担当分配系统资源(CPU时间,内存)的实体。

线程&进程关系

  • 进程是操作系统分配资源的最小单位
  • 线程是CPU调度的基本单位
  • 同一个进程中的多个线程可以并发进行,在多处理器的情况下可以同时同时并行。

进程&线程的共享

  • 同一地址空间:如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到。
  • 文件描述符表
  • 每种信号的处理方式
  • 当前工作目录
  • 用户ID和组ID

线程的优缺点

优点:
1. 创建一个新线程的代价要比创建一个新进程小的多
2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
3. 线程占用的资源比进程少
4. 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
5. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
6. I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作

缺点:
1. 健壮性降低
2. 缺乏访问控制
3. 编程难度提高

线程创建

功能:创建一个新的线程 
原型:
#include <pthread.h>
int pthread_create(pthread_t *thread,const           pthread_attr_t *attr, void *(*star t_routine)(void*), void *arg); 
参数:   
    thread:返回线程ID    
    attr:设置线程的属性,attr为NULL表示使用默认属性              
    start_routine:是个函数地址,线程启动后要执行的函数 
    arg:传给线程启动函数的参数 
返回值:成功返回0;失败返回错误码

线程创建时并不能保证哪个线程会先运行。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除。
pthread函数在调用失败时通常会返回错误码,不需要依赖那些随着函数执行不断变化的全局变量,这样可以吧错误的范围限制在引起出错的函数中。

线程ID&进程ID

  • 线程又被称为轻量级进程(Light Weighted Process),每一个用户态的线程,在内核中都对应一个调度实体,也拥有自己的进程描述符(task_struct结构体)。
  • 没有线程之前,一个进程对应内核里的一个进程描述符,对应一个进程ID。但是引入线程概念之后,一个用户进程下管辖N个用户态线程,每个线程作为一个独立的调度实体在内核态都有自己的进程描述符,进程和内核的描述符一下⼦子就变成了1:N关系,POSIX标准又要求进程内的所有线程调用getpid函数时返回相同的进程ID。

由此Linux内核引⼊入了线程组的概念。
多线程的进程,又被称为线程组,线程组内的每一个线程在内核之中都存在一个进程描述符 (task_struct)与之对应。

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <pthread.h>
  5 #include <error.h>
  6
  7 pthread_t mtid;
  8 
  9 void *child_func()
 10 {
 11         while(1)
 12         {
 13                 printf("子线程\n");
 14                 sleep(1);
 15         }
 16         return NULL;
 17 }
 18 int main()
 19 {
 20         int ret=pthread_create(&mtid,NULL,child_func,NULL);
 21         if(ret!=0)
 22                 perror("can't create thread"),exit(1);
 23         while(1)
 24         {
 25                 printf("主线程\n");
 26                 sleep(1);
 27         }
 28         return 0;
 29 }


可以看出上面tid进程是多线程的,进程ID为16947,进程内有2个线程,线程ID分别为16947,16948。
正如我们期望的那样:

  • 线程组内,线程的进程ID一样,线程ID不同
  • 线程组内的第一个线程,在用户态被称为主线程(main thread),在内核中被称为group leader,内核在创建第一个线程时,会将线程组的ID的值设置成第一个线程的线程ID,group_leader指针则指向自身,既主线程的进程描述符。所以线程组内存在一个线程ID等于进程ID,而该线程即为线程组的主线程。
  • 线程组其他线程的ID则有内核负责分配,其线程组ID总是和主线程的线程组ID一致,无论是主线程直接创建线程,还是创建出来的线程再次创建线程,都是这样。

  • 线程和进程不一样,进程有父进程的概念,但在线程组里面,所有的线程都是对等关系。

线程ID及进程地址空间布局

  • pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
  • 前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该程。
  • pthread_ create函数产生并标记在第一个参数指向的地址中的线程ID中,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
  • 线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID

线程终止

如果需要只终止某个线程而不是终止整个进程,可以有三种方法:

线程可以调用pthread_exit终止自己。(主动)

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <pthread.h>
  5 #include <error.h>
  6 
  7 pthread_t mtid;
  8 
  9 void *child_func()
 10 {
 11         while(1)
 12         {
 13                 printf("子线程\n");
 14                 sleep(1);
 15                 pthread_exit((void *)mtid);
 16         }
 17         return NULL;
 18 }
 19 int main()
 20 {
 21         int ret=pthread_create(&mtid,NULL,child_func,NULL);
 22         if(ret!=0)
 23                 perror("can't create thread"),exit(1);
 24         while(1)
 25         {
 26                 printf("主线程\n");
 27                 sleep(1);
 28         }
 29         return 0;
 30 }

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

 9 void *child_func()
 10 {
 11         while(1)
 12         {
 13                 printf("子线程\n");
 14                 sleep(1);
 15                 //pthread_exit((void *)mtid);
 16                 return (void *)NULL;
 17         }
 18         return NULL;
 19 }

 20 int main()
 21 {
 22         int ret=pthread_create(&mtid,NULL,child_func,NULL);
 23         if(ret!=0)
 24                 perror("can't create thread"),exit(1);
 25         while(1)
 26         {
 27                 printf("主线程\n");
 28                 sleep(1);
 29                 return 0;
 30         }

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

20 int main()
 21 {
 22         int ret=pthread_create(&mtid,NULL,child_func,NULL);
 23         if(ret!=0)
 24                 perror("can't create thread"),exit(1);
 25         while(1)
 26         {
 27                 printf("主线程\n");
 28                 sleep(1);
 29                 pthread_cancel(mtid);
 31         }
 32         return 0;
 33 }

int main()
 21 {
 22         int ret=pthread_create(&mtid,NULL,child_func,NULL);
 23         if(ret!=0)
 24                 perror("can't create thread"),exit(1);
 25         while(1)
 26         {
 27                 printf("主线程\n");
 28                 sleep(1);
 29                 pthread_cancel(pthread_self());

线程等待和分离

已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。 创建新的线程不会复用刚才退出线程的地址空间。
这样会造成资源浪费!!!

功能:等待线程结束 
原型:   
int pthread_join(pthread_t thread, void **value_ptr); 
参数:    
    thread:线程ID    
    value_ptr:它指向一个无类型指针,后者指向线程的返回值 
返回值:成功返回0;失败返回错误码

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

  1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
  2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数 PTHREAD_ CANCELED。
  3. 如果thread线程是自己调用pthread_exit 终止的 ,valueptr所指向的单元存放的是传给pthread_exit 的参数。
  4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ ptr参数。
  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <pthread.h>
  4 #include <unistd.h>
  5 #include <error.h>
  6 
  7 pthread_t tid;
  8 
  9 void *thread1(void *arg)
 10 {
 11         printf("thread 1 return\n");
 12         return (void *)1;
 13 }
 14 
 15 void *thread2(void *arg)
 16 {
 17         printf("thread 2 pthread_exit\n");
 18         pthread_exit((void *)NULL);
 19 }
 20 
 21 void *thread3(void *arg)
 22 {
 23         while(1)
 24         {
 25                 printf("thread 3 is running...\n");
 26                 sleep(1);
 27         }
 28         return (void *)NULL;
 29 }
 30 int main()
 31 {
 32         int ret;
 33         void *tret;
 34 
 35         ret=pthread_create(&tid,NULL,thread1,NULL);
 36         if(ret!=0)
 37                 perror("pthread_create1"),exit(1);
 38         ret=pthread_join(tid,&tret);
 39         if(ret!=0)
 40                 perror("pthread_join1"),exit(1);
 41         printf("thread 1 return, thread 1 id = %X,return code:%ld\n",tid,(long)tret);
 42 
 43         ret=pthread_create(&tid,NULL,thread2,NULL);
 44         if(ret!=0)
 45                 perror("pthread_create2"),exit(1);
 46         ret=pthread_join(tid,&tret);
 47         if(ret!=0)
 48                 perror("pthread_join2"),exit(1);
 49         printf("thread 2 pthread_exit, thread 2 id = %X,return code:%ld\n",tid,(long)tret);
 50 
 51         ret=pthread_create(&tid,NULL,thread3,NULL);
 52         if(ret!=0)
 53                 perror("pthread_create3"),exit(1);
 54         sleep(3);
 55         pthread_cancel(tid);
 56         pthread_join(tid,&tret);
 57         if(tret==PTHREAD_CANCELED)
 58                 printf("thread 3 return, thread 3 id = %X,return code:PTHREAD_CANCELED\n",tid);
 59         else
 60                 printf("thread 3 return, thread 3 id = %X,return code:NULL",tid);
 61         return 0;
 62 }


调用线程将一直阻塞,直到指定的线程调用pthread_exit 、从启动例程中返回或者被取消。如果线程简单地从他的启动例程返回,value_ptr就包含返回码。如果线程被取消,由value_ptr指定的内存单元就设置为PTHREAD_CANCELED。

分离线程

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。
  • 在线程被分离后,我们不能用pthread_join函数等待它的终止状态,因为对分离状态的线程调用pthread_join会产生未定义行为。
#include <pthread.h>
//可以是线程组内其他线程对目标线程进行分离
int pthread_detach(pthread_t thread);
//也可以是线程自己分离
int pthread_detach(pthread_self());

joinable和分离是冲突的,一个线程不能既是joinable又是分离的。

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h>
#include <unistd.h> 
#include <pthread.h>
void *thread_run( void * arg ) 
{    
    pthread_detach(pthread_self());    
    printf("%s\n", (char*)arg);    
    return NULL; 
}
int main( void ) 
{    
    pthread_t tid;    
    if (pthread_create(&tid, NULL, thread_run, "thread1 run...") != 0 ) 
    {        
        printf("create thread error\n");        
    }
    int ret = 0;    
    sleep(1);
    if ( pthread_join(tid, NULL ) == 0 ) 
    {        
        printf("pthread wait success\n");        
        ret = 0;    
    } 
    else 
    {        
        printf("pthread wait failed\n");        
        ret = 1;    
    }    
    return ret; 
}

线程函数和进程函数之间的相似之处

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值