您的位置:首页 > 运维架构 > Linux

linux线程介绍

2015-08-04 22:47 716 查看
一、什么是线程?

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

1、【线程技术发展】

  Linux 2.2内核

    •不存在真正意义上的线程

  Linux 2 .4内核

    •消除线程个数的限制,允许动态地调整进程数上限

  在Linux 内核2.6之前,进程是最主要的处理调度单元,并没支持内核线程机制

进程所维护的是程序所包含的资源(静态资源), 如: 地址空间, 打开的文件句柄集, 文件系统状态, 信号处理handler, 等;线程所维护的运行相关的资源(动态资源), 如: 运行栈, 调度相关的控制信息, 待处理的信号集, 等;

然而, 一直以来, linux内核并没有线程的概念. 每一个执行实体都是一个task_struct结构, 通常称之为进程. 进程是一个执行单元, 维护着执行相关的动态资源. 同时, 它又引用着程序所需的静态资源(注意这里说的是linux中的进程).通过系统调用clone创建子进程时, 可以有选择性地让子进程共享父进程所引用的资源.
这样的子进程通常称为轻量级进程

linux上的线程就是基于轻量级进程, 由用户态的pthread库实现的.使用pthread以后, 在用户看来, 每一个task_struct就对应一个线程, 而一组线程以及它们所共同引用的一组资源就是一个进程.

在linux 2.6以前, pthread线程库对应的实现是一个名叫linuxthreads的lib. linuxthreads利用前面提到的轻量级进程来实现线程。

  Linux 2.6内核

    •实现共享地址空间的进程机制, 在1996年第一次获得线程的支持

  为了改善LinuxThread问题,根据新内核机制重新编写线程库, 改善Linux对线程的支持

    •由IBM主导的新一代POSIX线程库(Next Generation POSIX Threads,简称为NGPT)

      –NGPT项目在2002年启动

      –为了避免出现有多个Linux线程标准,在2003年停止该项目

    •由Red Hat主导的本地化POSIX线程库 (Native POSIX Thread Library,简称为NTPL)

      –最早在Red Hat Linux9中被支持

      –现在已经成为GNU C 函数库的一部分,同时也成为Linux线程的标准

所以,现在的Linux系统下的多线程遵循POSIX线程接口,称为pthread,在Linux编写多线程程序需要包含头文件pthread.h。

#include <pthread.h>

当然,进包含一个头文件是不能搞定线程的,还需要连接libpthread.so这个库,因此在程序连接阶段应该有类似这样的指令:

gcc program.o -o program -lpthread

2、【什么时候使用多线程】
当多个任务可以并行执行时,可以为每个任务启动一个线程。

二、线程的管理

在2.6内核中引入了线程组的概念,在2.6内核中,如果使用ps命令看,一个多线程的进程,只会显示一个进程,在给线程组中的任何一个线程发送信号的时候,整个线程组中的进程都能收到信号。

struct task_struct  {
         ...
      pid_t pid;
      pid_t tgid;
         ...
     struct task_struct *group_leader;   /* threadgroup leader */
         ...
     struct list_head thread_group;
         ...
};


在linux 2.6中,内核有了线程组的概念,task_struct结构中增加了一个tgid(thread group id)字段。

pid,从字面上是process id,但其实是thread id。(线程id)

tgid,从字面上,应该是thread group id,也就是真正的process id。(进程id)

如果这个task是一个"主线程",则它的tgid等于pid,否则tgid等于进程的pid(即主线程的pid)。在clone系统调用中,传递CLONE_THREAD参数就可以把新进程的tgid设置为父进程的tgid(否则新进程的tgid会设为其自身的pid)。

有了tgid,内核或相关的shell程序就知道某个tast_struct是代表一个进程还是代表一个线程。

而getpid(获取进程ID)系统调用返回的也是tast_struct中的tgid,而tast_struct中的pid则由gettid系统调用来返回。

例如:在执行ps命令的时候不展现子线程,也是有一些问题的。比如程序a.out运行时,创建了一个线程。假设主线程的pid是10001、子线程10002(它们的tgid都是10001)。这时如果你kill 10002,是可以把10001和10002这两个线程一起杀死的,尽管执行ps命令的时候根本看不到10002这个进程。如果你不知道linux线程背后的故事,肯定会觉得遇到灵异事件了。

#include<unistd.h> 
#include<stdlib.h> 
#include<stdio.h> 
#include<unistd.h>
#include<sys/syscall.h>
#include<pthread.h> 

pid_t gettid(void)
{
    return syscall(SYS_gettid);
}

void* doit(void*);  
int main(void)  
{  
    pthread_t tid;  
    pthread_create(&tid,NULL,doit,NULL);  
    printf("main pid = %d \n",getpid());
    printf("main tid = %d \n",gettid());
    
    pause();//主线程挂起否则主线程终止,子线程也就挂了)  
}  
void* doit(void* agr)  
{  
    printf("thread is created!\n");  
    printf("thread pid = %d \n",getpid());
    printf("thread tid = %d \n",gettid());
    
    pause(); //挂起线程  
}


运行结果:

main pid = 11973
main tid = 11973
thread is created!
thread pid = 11973
thread tid = 11974
终端中ps -a后结果:

PID TTY TIME CMD
11973 pts/6 00:00:00 pthread
12067 pts/7 00:00:00 ps
终端中执行:kill 11974 也可以杀死pthread进程

为了应付"发送给进程的信号"和"发送给线程的信号", task_struct里面维护了两套signal_pending, 一套是线程组共享的, 一套是线程独有的.通过kill发送的信号被放在线程组共享的signal_pending中, 可以由任意一个线程来处理; 通过pthread_kill发送的信号(pthread_kill是pthread库的接口, 对应的系统调用中tkill)被放在线程独有的signal_pending中, 只能由本线程来处理.当线程停止/继续,
或者是收到一个致命信号时, 内核会将处理动作施加到整个线程组中。

三、线程函数

1、创建

int pthread_create (pthread_t *__restrict __newthread,//新创建的线程ID
              __const pthread_attr_t *__restrict __attr,//线程属性
              void *(*__start_routine) (void *),//新创建的线程从start_routine开始执行
              void *__restrict __arg)//执行函数的参数

参数:
pthread_create()接口的第一个参数是一个返回参数。当一个新的线程调用成功之后,就会通过这个参数将线程的句柄返回给调用者,以便对这个线程 进行管理。

pthread_create()接口的第二个参数用于设置线程的属性。这个参数是可选的,当不需要修改线程的默认属性时,给它传递NULL就行。具体线程有那些属性,我们后面再做介绍。

当你的程序调用了这个接口之后,就会产生一个线程,而这个线程的入口函数就是start_routine()。

start_routine()函数有一个参数,这个参数就是pthread_create的最后一个参数arg。这种设计可以在线程创建之前就帮它准备好一些专有数据,最典型的用法就是使用C++编程时的this指针。start_routine()有一个返回值,这个返回值可以通过pthread_join()接口获得。

返回值:成功-0,失败-返回错误编号,可以用strerror(errno)函数得到错误信息。

2、等待

int pthread_join(pthread_t thread, void **value_ptr);


参数:

thread:等待退出线程的线程号。

value_ptr:退出线程的返回值。

含义:使一个线程等待另一个线程结束。
作用:代码中如果没有使用pthread_join函数,主线程会很快结束导致整个进程结束,使创建的线程没有开始执行就结束了。加入pthread_join函数 后,主线程会一直等待直到pthread指定的线程结束才结束,使创建的线程有机会执行。

NOTE:另外需要说明的是,一个线程不能被多个线程等待,也就是说对一个线程只能调用一次pthread_join,否则只有一个能正确返回,其他的将返 回ESRCH 错误。

例1:

#include <stdio.h>
#include <pthread.h>
void* thread( void *arg )
{
    printf( "This is a thread and arg = %d.\n", *(int*)arg);
    *(int*)arg = 0;
    return arg;
}
int main( int argc, char *argv[] )
{
    pthread_t th;
    int ret;
    int arg = 10;
    int *thread_ret = NULL;
    ret = pthread_create( &th, NULL, thread, &arg );
    if( ret != 0 ){
        printf( "Create thread error!\n");
        return -1;
    }
    printf( "This is the main process.\n" );
    pthread_join( th, (void**)&thread_ret );
    printf( "thread_ret = %d.\n", *thread_ret );
    return 0;
}


3、结束

一个线程的结束有两种途径,一种是象我们上面的例子一样,等待线程函数结束了,调用它的线程也就结束了;另一种方式是通过函数pthread_exit来实现。
void  pthread_exit(void  *retval)


参数:

retval:pthread_exit()调用线程的返回值

功能:使用函数pthread_exit退出线程,这是线程的主动行为

【线程与进程区别】

1、进程是拥有资源的基本单位;线程是独立调度的基本单位;

2、地址空间:进程之间的地址空间是相互独立的;而同一进程中线程之间的地址是共享的,所以资源也是共享的,在线程中只有栈是独立的;

3、并发性:不仅不同进程之间可以并发;同一进程之间的线程也可以并发;

4、系统开销:进程的开销大,如内存、文件等;线程占用资源较少。

【Linux线程与进程区别】

上述1-4

5、平时在linux下使用的pthread库是GNU C的一部分,遵循POSIX线程标准,也是Linxu线程的标准。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: