线程

来源:互联网 发布:手机淘宝如何关注店铺 编辑:程序博客网 时间:2024/05/23 13:07
一、线程概念
        线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix  System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

为什么对于多数合作性任务,多线程比多个独立的进程更优越呢?
        线程共享相同的内存空间。不同的线程可以存取内存中的同一个变量。所以,程序中的所有线程都可以读或写声明过的全局变量。如果曾用fork()编写过重要代码,就会认识到这个工具的重要性。为什么呢?虽然fork()允许创建多个进程,但它还会带来以下通信问题,如何让多个进程相互通信,这里每个进程都有自己独立的内存空间。对这个问题没有一个简单的答案。虽然有许多不通种类的本地IPC(进程间通信),但他们都遇到两个重要的障碍:

1.加强了某种形式的额外内核开销,从而降低性能。
2.对于大多数情形,IPC不是对于代码的“自然”扩展,通常极大地增加了程序的复杂性。
  •  线程的共享资源和私有资源:

线程共享的环境包括:进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。     进程拥有这许多共性的同时,还拥有自己的个性。有了这些个性,线程才能实现并发性。这些个性包括:

1.线程ID

每个线程都有自己的线程ID,这个ID在本进程中是唯一的。进程用此来标   识线程。   

2.寄存器组的值

由于线程间是并发运行的,每个线程有自己不同的运行线索,当从一个线   程切换到另一个线程上时,必须将原有的线程的寄存器集合的状态保存,以便   将来该线程在被重新切换到时能得以恢复。   

          3.线程的堆栈

堆栈是保证线程独立运行所必须的。

线程函数可以调用函数,而被调用函数中又是可以层层嵌套的,所以线程   必须拥有自己的函数堆栈,使得函数调用可以正常执行,不受其他线程的影   响。

4.错误返回码

由于同一个进程中有很多个线程在同时运行,可能某个线程进行系统调用   后设置了errno值,而在该线程还没有处理这个错误,另外一个线程就在此时   被调度器投入运行,这样错误值就有可能被修改。

所以,不同的线程应该拥有自己的错误返回码变量。

5.线程的信号屏蔽码

由于每个线程所感兴趣的信号不同,所以线程的信号屏蔽码应该由线程自   己管理。但所有的线程都共享同样的信号处理器。

6.线程的优先级

由于线程需要像进程那样能够被调度,那么就必须要有可供调度使用的参   数,这个参数就是线程的优先级。

1、引入pthread_equal的原因:

       在线程中,线程ID的类型是pthread_t类型,由于在Linux下线程采用POSIX标准,所以,在不同的系统下,pthread_t的类型是不同的,比如在ubuntn下,是unsigned long类型,而在solaris系统中,是unsigned int类型。而在FreeBSD上才用的是结构题指针。 所以不能直接使用==判读,而应该使用pthread_equal来判断。

 

2、引入pthread_self的原因:

        在使用pthread_create(pthread_t *thread_id,NULL,void* (*fun) (void *),void * args);虽然第一个参数中已经保存了线程ID,但是,前提是主线程首先执行时,才能实现的,而如果不是,那么thread指向一个未出划的变量。那么才子线程想使用时,应该使用pthread_self();


二、线程的实行和创建

       Linux线程的实现包括在用户级实现和核心级实现。在用户级实现线程时,没有核心支持的多线程进程。因此,核心只有单线程进程的概念,而多线程进程由与应用程序连接的过程库实现。核心不知道线程的存在也就不能独立的调度这些线程了。线程的调度由一个线程运行库组织。如果一个线程调用了一个阻塞的系统调用,进程可能被阻塞,当然其中的所有线程也同时被阻塞,所以UNIX所有了异步I/O工具。这种机制的最大缺点是不能发挥多处理器的优势。它的优点是系统的消耗小,可以修改以适应特殊应用场合。而在核心级实现线程时,允许不同进程里的线程按照同一相对优先方法凋度,这适合于发挥多处理器的并发优点。目前Linux众多的线程库中大部分实现的是用户级的线程,只有一些用于研究的线程库才尝试实现核心级进程

       LinuxThread线程库采用称为1-1模型:每个线程实际上在核心是一个个单独的进程,核心的调度程序负责线程的调度,就像调度普通进程。线程是用系统调用clone()创建的,clone()系统调用是fork()的普通形式,它允许新进程共享父进程的存储空间,文件描述符和软中断处理程序。这种模型的优点是最小限度地依赖CPU级多处理技术(每个CPU一个线程),以及最小限度地使用I/O操作。

     系统创建线程是一个复杂的工作,当一个进程启动后,它会自动创建一个线程即主线程(main thread)或者初始化线程(initial thread),然后就利用Pthread_initialize() 初始化系统管理线程并且启动线程机制,这时完成的工作是建立管理线程堆栈以及建立管理线程通信的管道。线程机制启动后,要创建线程必须让Pthread_creat()向管理线程发送请求。管理线程接到请求后首先检查是否需要调整调度策略,如果需要,判断是否能够做到。接着为线程找出一个空段,并且根据需要再分配堆栈。接下来,分配新线程的标识符,处始化新线程描述符,确定线程的调度参数,并根据需要调整管理线程的优先级。最后便创建线程。  新线程创建以后,管理线程会将其插入系统活动线程的双向链表中。

       Linux支持若干线程库,它们有些遵循Posix标准,有些不遵循。最常用的线程库是LinuxThreads,它是一个面向Linux的Posix 1003.1c pthread标准接口。此线程库实现时使用了两个信号SIGUSR1和SIGUSR2,因此用户不能使用它们。此线程库将线程分配在高端存储空间,在初始化进程的堆栈下2M处。她采用按需增长的策略,所以初始化时不会使用很多虚拟空间(现在是4k),如果需要可以增长到2M。为每个线程保留这么大的地址空间意味着,在32为体系结构下,不能有超过大约1000个线程共存,因为这是合理的,每个线程使用核心的进程表的一项,而它通常限制为512项


三、函数原型

就像每个进程有一个进程ID一样,每个线程也有一个线程ID。进程ID在整个系统中是唯一的,但线程ID不同,线程ID只在它所属的进程环境中有效。

 进程ID用pid_t数据类型来表示,是一个非负整数。线程ID    则用pthread_t数据类型来表示,实现的时候可以用一个结构来代表pthread数据类型,所以可移植性的操作系统不能把它作为整数处理。因此必须使用函数来对两个线程ID进行比较。

pthread_t pthread_self(void)

功能:获得本线程ID

返回值:返回本线程的标识符。此函数总是成功


int pthread_equal(pthread_t thread1, pthread_t thread2)

功能:判断两个线程描述符是否相等,在LinuxThreads中,线程ID相同的线程必然是同一个线程

参数: thread1:线程ID1

            thread2:线程ID2

返回值:如果相等,则返回非0值,否则返回0


(1)线程的创建和终止:

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

         功能:创建一个新的线程

          参数:

                  thread :指向的内存单元存放新创建线程的线程ID
        attr:用于定制各种不同的线程属性。(详细见线程属性)
        start_routine:新建的线程从此函数地址开始运行。
        arg:作为start_routine函数的参数。如果需要向 start_routine 函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入

        返回值:若成功则返回0,否则返回出错编号

注:另外,在编译时注意加上-lpthread参数,以调用链接库。因为pthread并非Linux系统的默认库,而是posix线程库,在Linux中将其作为一个库来使用,因此加上 -lpthread(或-pthread)以显示的链接该库。函数在执行错误时的错误信息将作为返回值返回,并不修改系统全局变量errno,当然也无法使用perror()打印错误信息。



void  pthread_exit(void  *retval)

功能:使用函数pthread_exit退出线程,这是线程的主动行为;由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放,但是可以用pthread_join()函数来同步并释放资源。

          参数:retval:pthread_exit()调用线程的返回值,可由其他函数如pthread_join来获取。

          注:exit是退出整个进程,而pthread_exit是退出当前线程


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

功能:以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果进程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable(可结合)的。

参数:

thread: 线程标识符,即线程ID,标识唯一线程。

          retval: 用户定义的指针,用来存储被等待线程的返回值。

返回值 : 0代表成功。 失败,返回的则是错误号。对分离状态的线程进行pthread_join的调用会产生失败。返回EINVAL。

注:线程的分离状态: 

     在任何一个时间点上,线程是可结合的(joinable),或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。

     线程的分离状态决定一个线程以什么样的方式来终止自己。在默认情况下线程是非分离状态的,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。所以如果我们在创建线程时就知道不需要了解线程的终止状态,则可以pthread_attr_t结构中的detachstate线程属性,让线程以分离状态启动。


 int pthread_detach(pthread_t thread);

     功能:使线程进入分离状态

     参数: thread:要分离的线程ID

     返回值:  若成功则返回0,若出错则为非零。


(2)线程的取消


int pthread_cancel(pthread_t thread)

        功能:请求取消统一进程中的其他线程

        参数:thread:要取消的线程ID

        返回值:如果成功则返回0,否则返回错误编号

注:线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。

pthread_cancle只是向目标线程发送一个取消请求,之后函数立即返回。而目标线程在取消请求发出以后还是继续运行,直到线程到达某个取消点。取消点是线程检查自身是否被取消,并按照可取消状态进行动作的一个位置。

下表是一些取消点


线程有两个属性并没有包含在pthread_attr_t结构中,它们是可取消状态和可取消类型。这两个属性影响着线程在响应pthread_cancel函数调用时所呈现的行为。

可取消状态:线程启动时的默认可取消状态是PTHREAD_CANCEL_ENABLE。 PTHREAD_CANCEL_ENABLE:表示允许取消。PTHREAD_CANCEL_DISABLE:即不允许取消(收到的取消请求将被忽略)

int pthread_setcancelstate(int state, int *oldstate)

         功能:设置取消状态,即设置本线程对Cancel信号的反应

         参数:state: PTHREAD_CANCEL_ENABLE或者PTHREAD_CANCEL_DISABLE

                      oldstate:如果不为 NULL则存入原来的Cancel状态以便恢复。

         返回值:成功返回0,错误返回错误编号


可取消类型:分为两种,PTHREAD_CANCEL_DEFFERED:即延迟取消,在线程到达真正的取消点之前,是不会出现真正的取消。PTHREAD_CANCEL_ASYCHRONOUS:立即执行取消动作(退出)。仅当可取消状态状态为Enable时有效。

int pthread_setcanceltype(int type, int *oldtype)

          功能:设置取消类型。

          参数:PTHREAD_CANCEL_DEFFERED或PTHREAD_CANCEL_ASYCHRONOUS

          返回值:成功返回0,错误返回错误编号


如果应用程序在很长一段时间内都不会调用到取消点的函数。则可以使用 pthread_testcancel在程序中自己添加取消点。

void pthread_testcancel(void)

功能:调用此函数时,如果有某个取消请求正处于未决状态(即取消请求还未被响应),而且可取消状态并没有被设置为无效,那么线程就会被取消。但是如果可取消状态被设置为无效,则此函数调用没有任何效果。




例程:

#include <stdio.h>

#include <pthread.h>

#include <stdlib.h>

#include <unistd.h>

#include <string.h>


#define BUFF_SIZE128


typedef struct _global_mem_ {

char buff[BUFF_SIZE];

}gmem_t;


gmem_t globamem = {

};


void * reader(void *arg);

void * writer(void *arg);


int main()

{

void *(*pthread_handlers[])(void *) = {

reader, 

writer,

};

const int THDNO = sizeof(pthread_handlers) / sizeof(*pthread_handlers);

pthread_t tids[THDNO];

int index;


for (index = 0; index < THDNO; index ++)

pthread_create( tids+index, NULL, pthread_handlers[index], NULL);


for (index = 0; index < THDNO; index ++)

pthread_join(tids[index], NULL);


return 0;

}


void * reader(void *arg)

{

while (1)

{

printf("read:%s\n", globamem.buff);

sleep(1);

}


return NULL;

}


void * writer(void *arg)

{

while (1)

{

puts("copy:");

strcpy(globamem.buff, "hello");

sleep(1);

puts("cat");

strcat(globamem.buff, "----------------------------world");

sleep(1);

}


return NULL;

}










                









































原创粉丝点击