Chinaunix首页 | 论坛 | 博客
  • 博客访问: 517193
  • 博文数量: 91
  • 博客积分: 9223
  • 博客等级: 中将
  • 技术积分: 1777
  • 用 户 组: 普通用户
  • 注册时间: 2007-04-02 17:37
个人简介

!!!!!!!!!!!!

文章分类

全部博文(91)

文章存档

2013年(3)

2012年(4)

2011年(37)

2010年(36)

2009年(9)

2008年(2)

分类: LINUX

2010-01-05 17:14:40

             线程

 

【摘要】

线程的介绍及使用。

【关键词】

线程。

一、问题的提出

主要介绍有关线程的内容。

二、解决思路

1)线程的概念

线程包含了表示进程内执行环境必须的信息,其中包括进程中标识线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno变量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文件、程序的全局内存和堆内存、栈以及文件描述符。

2)线程标识

进程ID在整个系统中是唯一的,但线程ID不同,线程ID只在它所属的进程环境中有效。线程ID则用pthread_t数据类型表示。

Int pthread_equal(pthread_t tid1, pthread_t tid2);

比较两个线程的ID

Pthread_t pthread_self(void);

获取自身的线程ID

3)线程的创建

Int pthread_create(pthread_t *tidp, pthread_attr_t *attr, void *(*start_rtn)(void), void *restrict arg);

pthread_create成功返回时,有tidp指向的内存单元被设置为新创建线程的线程IDAttr参数用于定制各种不同的线程属性。新创建的线程从start_rtn函数的地址开始执行,该函数只有一个无类型指针参数arg,如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。

线程创建时并不能保证哪个线程会先运行:是新创建的线程还是调用线程。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的未决信号集被清除。

pthread_t ntid;

 

void printids(char *s)

{

  pid_t pid;

  pthread_t tid;

 

  pid = getpid();

  tid = pthread_self();

  printf("%s pid %u tid %u (0x%x)\n", s, (unsiged int)pid, (unsiged int)tid, (unsigned int)tid);

}

 

void *thr_fn(void *arg)

{

  printids("new thread:");

  return ((void *)0);

}

 

int main(void)

{

  int err;

  err = pthread_create(&ntid, NULL, thr_fn, NULL);

  if(err != 0)

    err_quit("can't create thread:%s\n", strerror(err));

  printids("main thread:");

  sleep(1);

  exit(0);

}

首先是主线程必须休眠,如果主线程不休眠,它就不可能退出,这样在新线程有机会运行之前整个进程可能就已经终止了。

第二个特别之处在于新线程是通过pthread_self函数获取自己的线程ID,而不是从共享内存中读出或者从线程的启动例程中以参数的形式接收到。在本例里,主线程把新线程ID存放在ntid中,但是新建的线程并不能安全地使用它,如果新线程在主线程调用pthread_create返回之前就运行了,那么新线程看到的是未经初始化的ntid的内容,这个内容并不是正确的线程ID

4)线程终止

单个线程可以通过下列三种方式退出,在不终止整个进程的情况下停止它的控制流。

A.线程只是从启动例程中返回,返回值是线程的退出码。

B.线程可以被同一进程中的其他线程取消。

C.线程调pthread_exit

Void pthread_exit(void *rval_ptr);

Int pthread_join(pthread_t thread, void **rval_ptr);

Rval_ptr是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。调用线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。如果线程只是从它的启动例程返回,rval_ptr将包含返回码。如果线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED

可以通过调pthread_join自动将线程至于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL

Pthread_createpthread_exit函数的无类型指针参数能传递的数值可以不止一个,该指针可以传递包含更负责信息的结构的地址,但是注意这个结构所使用的内存在调用者完成调用以后必须仍然是有效的,否则就会出现无效或非法内存访问。

Int pthread_cancel(pthread_t tid);

线程可以调用pthread_cancel函数来请求取消同一个进程中的其他线程。注意pthread_cancel并不等待线程终止,它仅仅提出请求。

Void pthread_cleanup_push(void (*rtn)(void *),void *arg);

Void pthread_cleanup_pop(int execute);

线程可以建立多个清理处理程序。处理程序记录在栈中,也就是说它们的执行顺序与它们注册时的顺序相反。

当线程执行一下动作时调用清理函数,调用参数为arg,清理函数rtn的调用顺序是由pthread_cleanup_push函数来安排的。

l         调用pthread_exit时。

l         响应取消请求时。

l         用非零execute参数调用pthread_cleanup_pop时。

如果execute参数设置为0,清理函数将不被调用。无论哪种情况,pthread_cleanup_pop都将删除上次pthread_cleanup_push调用建立的清理处理程序。

在默认情况下,线程的终止状态会保存到对该线程调用pthread_join,如果线程已经处于分离状态,线程的底层存储资源可以在线程终止时立即被收回。当线程被分离时,并不能用pthread_join函数等待它的终止状态。对分离状态的线程进行pthread_join的调用会产生失败,返回EINVAL

Pthread_detach调用可以用于使线程进入分离状态。

Int pthread_detach(pthread_t tid);

  5)线程同步

  1.互斥量

  互斥量用pthread_mutex_t数据类型来表示,在使用互斥变量以前,必须首先对它进行初始化,可以把它置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态地分配互斥量,那么在释放内存前需要调用pthread_mutex_destory

Int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);

Int pthread_mutex_destory(pthread_mutex_t *mutex);

要用默认的属性初始化互斥量,只需把attr设置为NULL

对互斥量进行加锁,需要调用pthread_mutex_lock,如果互斥量已经上锁,调用线程将阻塞直到互斥量被解锁。

Int pthread_mutex_lock(pthread_mutex_t *mutex);

Int pthread_mutex_trylock(pthread_mutex_t *mutex);

Int pthread_mutex_unlock(pthread_mutex_t *mutex);

如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁。

2.读写锁

读写锁可以有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占用读模式的读写锁。

当读写锁是加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读状态时,所有试图以读模式对它加锁的线程都可以获得访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有线程释放读锁。

读写锁在使用前必须初始化,在释放它们底层的内存前必须销毁。

Int pthread_rwlock_init(pthread_rwlock_t *rwlock, pthread_rwlockattr_t *attr);

Int pthread_rwlock_destory(pthread_rwlock_t *rwlock);

在释放读写锁占用的内存之前,需要调用pthread_rwlock_destory做清理。

要在读模式下锁定读写锁,需要调用pthread_rwlock_rdlock;要在写模式下锁定读写锁,需要调用pthread_rwlock_wrlock。不管以何种方式锁定读写锁,都可以调用pthread_rwlock_unlock进行解锁。

Int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

Int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

Int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

Int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

Int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

3.条件变量

条件变量给多个线程提供了一个回合场所。条件变量与互斥锁一起使用时,允许线程以无竞争的方式等待特定的条件发生。

条件变量本身由互斥量保护的。线程在改变条件前必须首先锁定互斥量,其他线程在获得互斥量之前不会察觉到这种变化,因为必须锁定互斥量以后才能计算条件。

条件变量使用之前必须进行初始化,pthread_cond_t数据类型代表的条件变量可以用两种方式进行初始化,可以把常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量,但是如果条件变量是动态分配的,可以使用pthread_cond_init函数进行初始化。

在释放底层的内存空间之前,可以使用pthread_mutex_destroy函数对条件变量进行去初始化。

Int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *attr);

Int pthread_cond_destroy(pthread_cond_t *cond);

使用pthread_cond_wait等待条件变为真

Int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

Int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, struct timespec *timeout);

传递给pthread_cond_wait的互斥量对条件进行保护,调用者把锁住的互斥量传给函数。函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作是原子操作。这样就关闭了条件检查和线程进入睡眠状态等待条件改变这两个之间的时间通道,这样线程就不会错过条件的任何变化。Pthread_cond_wait返回时,互斥量再次被锁定。

Pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有线程。

Int pthread_cond_signal(pthread_cond_t *cond);

Int pthread_cond_broadcast(pthread_cond_t *cond);

struct msg{

struct msg *m_next;

};

Struct msg *workq;

Pthread_cond_t qready = PTHREAD_COND_INITIALIZER;

Pthread_mutex_t qlock= PTHREAD_MUTEX_INITIALIZER

Void process_msg(void)

{

  Struct msg *mp;

  For(;;)

{

  Pthread_mutex_lock(&qlock);

  While(workq == NULL)

    Pthread_cond_wait(&qready, &qlock);

  Mp=workq;

  Workq = mp->m_next;

  Pthread_mutext_unlock(&qlock);

  }

 Void enqueue_msg(struct msg *mp)

  {

    Pthread_mutex_lock(&qlock);

    Mq->m_next=workq;

    Workq = mq;

    Pthread_mutex_unlock(&qlock);

    Pthread_cond_signal(&qready);

}

6)线程属性

  可以使用pthread_attr_t结构修改线程默认属性,并把这些属性与创建的线程联系起来。可以使用pthread_attr_init函数初始化pthread_attr_t结构。调用pthread_attr_init以后,pthread_attr_t结构所包含的内容就是操作系统实现支持的线程所有属性的默认值。

  线程的属性:

  Detachstate:线程的分离状态属性

  Guardsize:线程栈末尾的警戒缓冲区大小

  Stackaddr:线程栈的最低地址

  Stacksize:线程栈的大小

  Int pthread_attr_init(pthread_attr_t *attr);

  Int pthread_attr_destory(pthread_attr_t *attr);

  如果要去除对pthread_attr_t结构的初始化,可以调用pthread_attr_destroy函数。如果pthread_attr_init实现时为属性对象分配了动态内存空间,pthread_attr_destroy将释放该内存空间。

  Int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);

  Int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

  如果在创建线程时就知道不需要了解线程的终止状态,则可以修改pthread_attr_t结构中的detachstate线程属性,让线程以分离状态启动。可以使用pthread_attr_setdetachstate函数把线程属性detachstate设置为下面的两个合法值之一:设置为PTHREAD_CREATE_DETACHED,以分离状态启动线程;或者设置为PTHREAD_CREATE_JOINABLE,正常启动线程,应用程序可以获取线程的终止状态。

  可以调用pthread_attr_getdetachstate函数获取当前的detachstate线程属性。

  如果pthread_attr_destroy确实出现失败的情况,清理工作就会变得很困难:必须销毁刚刚创建的线程,而这个线程可能已经运行,并且与pthread_attr_destroy函数可能是异步执行的。忽略pthread_attr_destroy的错误返回可能出现最坏情况是:如果pthread_attr_init分配了内存空间,这些内存空间会被泄漏。另一方面,如果pthread_attr_init成功地对线程属性进行了初始化,但pthread_attr_destroy在做清理工作时却出现了失败,就没有任何补救策略,因为线程属性结构对应用来说是不透明的,可以对线程属性结构进行清理的唯一接口是pthread_attr_destroy,但它失败了。

  Int pthread_attr_getstack(pthread_attr_t *attr, void **stackaddr, size_t *stacksize);

  Int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t *stacksize);

  这两个函数可以用于管理stackaddr线程属性,也可以用于管理stacksize线程属性。

  Stackaddr线程属性被定义为栈的内存单元的最低地址,但这并不必然是栈的开始位置。对于某些处理器结构来说,栈是从高地址向低地址方向伸展的,那么stackaddr线程属性就是栈的结尾而不是开始位置。

  应用程序也可以通过pthread_attr_getstacksizepthread_attr_setstacksize函数读取或设置线程属性stacksize

  Int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);

  Int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);

  线程属性guardsize控制着线程末尾之后用以避免栈溢出的扩展内存的大小。这个属性默认配置为PAGESIZE个字节。可以把guardsize线程属性设置为0,从而不允许属性的这种特征行为发生:在这种情况下不会提供警戒缓冲区。同样,如果对线程属性stackaddr做了修改,系统就会假设我们会自己管理栈,并使警戒栈缓冲区机制无效,等同于把guardsize线程设置为0.

  并发度控制着用户级线程可以映射的内核线程或进程的数目。如果操作系统的实现让用户线程到内核级线程或进程之间的映射关系是多对一的话,那么在给定时间内增加可运行的用户级线程树,可能会改善性能。Pthread_setconcurrency函数可以用于提示系统,表明希望的并发度。

  Int pthread_getconcurrency(void);

  Int pthread_setconcurrency(int level);

  pthread_getconcurrency函数返回当前的并发度。Pthread_setconcurrency函数设定的并发度只是对系统的一个提示,系统并不保证请求的并发度一定会采用。如果希望系统自己决定使用什么样的并发度,就把传入的参数level设为0.

7)同步属性

  1.互斥量属性

  pthread_mutexattr_init初始化phtread_mutexattr_t结构,用pthread_mutex_destroy来对该结构进行回收。

  Int pthread_mutexattr_init(pthread_mutexattr_t *addr);

  Int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);

  Pthread_mutexattr_init函数用默认的互斥量属性初始化pthread_mutexattr_t结构。值得注意的两个属性是进程共享属性和类型属性。

  在进程中,多个线程可以访问同一个对象。这是默认的行为。在这种情况下,进程共享互斥量属性需设置为PTHREAD_PROCESS_PRIVATE

  存在这样一种机制,允许相互独立的多个进程把同一个内存区域映射到它们各自独立的地址空间中。就像多个线程访问共享数据一样,多个进程访问共享数据通常也需要同步。如果进程共享互斥量属性设置为PTHREAD_PROCESS_SHARED,从多个进程共享的内存区域中分配的互斥量就可以用于这些进程的同步。

  可以使用pthread_mutexattr_getshared函数查询pthread_mutexattr_t结构,得到它的进程共享属性,可以用pthread_mutexattr_setpshared函数修改进程共享属性。

  Int pthread_mutexattr_getshared(pthread_mutexattr_t *attr, int *pshared);

  Int pthread_mutexattr_setshared(pthread_mustexattr_t *attr,  int pshared);

  进程共享属性设置为PTHREAD_PROCESS_PRIVATE时,允许pthread线程库提供更加有效的互斥量实现,这在多线程应用程序中是默认的情况。

  类型互斥量属性控制着互斥量的特性。POXSIX.1定义了四种类型。

PTHREAD_MUTEX_NORMAL类型是标准的互斥量类型,并不做特殊的错误检查或死锁检测。

PTHREAD_MUTEX_ERRORCHECK互斥量类型提供错误检查。

PTHREAD_MUTEX_RECURSIVE互斥量类型允许同一线程在互斥量解锁之前对该互斥量进行多吃加锁。用一个递归互斥量维护锁的计数,在解锁的次数和加锁的次数不相同的情况下不会释放锁。

PTHREAD_MUTEX_DEFAULT类型可以用于请求默认语义。

可以用pthread_mutexattr_gettype函数得到互斥量类型属性,用pthread_mutexattr_settype函数修改互斥量类型属性。

Int pthread_mutexattr_gettype(pthread_mutexattr_t *attr, int *type);

Int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);

2.读写锁属性

pthread_rwlockattr_init初始化pthread_rwlockattr_t结构,用pthread_rwlockattr_destroy回收结构。

Int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);

Int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);

读写锁的支持的唯一属性是进程共享属性,该属性与互斥量的进程共享属性相同。就像互斥量的进程共享属性一样,用一对函数来读取和设置读写锁的进程共享属性。

Int pthread_rwlockattr_getpshared(pthread_rwlockattr_t *attr, int *pshared);

Int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);

3.条件变量属性

Int pthread_condattr_init(pthread_condattr_t *attr);

Int pthread_condattr_destroy(pthread_condattr_t *attr);

条件变量支持进程共享属性。

Int pthread_condattr_getpshared(pthread_condattr_t *attr, int *pshared);

Int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);

8)重入

  如果一个函数在同一时刻可以被多个线程安全地调用,就称该函数是线程安全的。

  很多函数并不是线程安全的,因为他们返回的数据是存放在静态的内存缓冲区中。通过修改接口,要求调用者自己提供缓冲区可以使函数变为线程安全的。

  如果一个函数对多个线程来说是可重入的,则说这个函数是线程安全的,但并不能说明对信号处理程序来说该函数也是可重入的。如果函数对异步信号处理程序的重入是安全的,那么就说函数是异步-信号安全的。

  POSIX.1提供以线程安全的方式管理FILE对象的方法。可以使用flockfileftrylockfile获取与给定FILE对象关联的锁。这个锁是递归的,当占有这把锁的时候,还可以再次获取该锁,这并不会导致死锁。

  Int ftrylockfile(FILE *fp);

  Int flockfile(FILE *fp);

  Int funlockfile(FILE *fp);

  Int getchar_unlocked(void);

  Int getc_unlocked(FILE *fp);

  Int putchar_unlocked(int c);

  Int putc_unlocked(int c, FILE *fp);

  除非被flockfilefunlockfile的调用包围,否则尽量不要调用这四个函数,因为它们会导致不可预期的结果。

getenv的可重入(线程安全)版本

 

extern char **environ;

pthread_mutex_t env_mutex;

static pthread_once_t init_done = PTHREAD_ONCE_INIT;

 

static void thread_init(void)

{

  pthread_mutexattr_t attr;

 

  pthread_mutexattr_init(&attr);

  pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

  pthread_mutex_init(&env_mutex, &attr);

  pthread_mutexattr_destroy(&attr);

}

 

int getenv_r(char *name, char *buf, int buflen)

{

  int i, len, olen;

 

  pthread_once(&init_done, thread_init);

  len = strlen(name);

  pthread_mutex_lock(&env_mutex);

  for(i=0; environ[i]!= NULL; i++)

  {

    if((strncmp(name, environ[i], len)==0 && (environ[i][len]== '='))

    {

      olen = strlen(&environ[i][len+1]);

      if(olen >= buflen)

      {

        pthread_mutex_unlock(&env_mutex);

        return ENOSPC;

      }

      strcpy(buf, &environ[i][len+1]);

      pthread_mutex_unlock(&env_mutex);

      return 0;

    }

  }

  pthread_mutex_unlock(&env_mutex);

  return ENOENT;

}

必须使用递归互斥量阻止其他线程改变当前正查看的数据结构,同时还要阻止来自信号处理程序的死锁。问题是pthread函数并不保证是异步信号安全的,所以不能把pthread函数用于其他函数,让该函数成为异步信号安全的。

9.线程的私有数据

线程私有数据(也成线程特定数据)是存储和查询与某个线程相关的数据的一种机制。把这种数据成为线程私有数据或线程特定数据的原因是,希望每个线程可以独立访问数据副本,而不需要担心与其他线程的同步访问问题。

在分配线程私有数据之前,需要创建与该数据关联的键。这个键将用于获取对线程私有数据的访问权。使用pthread_key_create创建一个键。

Int pthread_key_create(pthread_key_t *keyp, void (*destructor)(void *));

创建的键存放在keyp指向的内存单元,这个键可以被进程中的所有线程使用,但每个线程把这个键与不同的线程私有数据地址进行关联。创建新键时,每个线程的数据地址设为null值。除了创建键外,pthread_key_create可以选择为该键关联析构函数,当线程退出时,如果数据地址已经被置为非null数值,那么析构函数将会被调用,它唯一的参数就是该数据地址。如果传入的destructor参数为null,就表明没有析构函数与键关联。当线程调用pthread_exit或者线程执行返回,正常退出时,析构函数就会被调用,但如果线程调用了exit_exit_Exitabort或出现其他非正常的退出时,就不会调用析构函数。

线程可以为线程私有数据分配多个键,每个键都可以有一个析构函数与它关联。各个键的析构函数可以互不相同,当然它们也可以使用相同的析构函数。每个操作系统在实现的时候可以对进程可分配的键的数量进行限制。线程退出时,线程私有数据的析构函数将按照操作系统实现中定义的顺序被调用。析构函数可能会调用另一个函数,该函数可能会创建新的线程私有数据而且把这个数据与当前的键关联起来。当所有的析构函数都调用完成以后,系统会检查是否还有非null的线程私有数据值与键关联,如果有的话,再次调用析构函数。这个过程后将会一直重复到线程所有的键都为null值线程私有数据,或者已经做了PTHREAD_DESTRUCTOR_ITERATIONS中定义的最大次数的尝试。

对所有的线程,都可以通过调用pthread_key_delete来取消键与线程私有数据值之间的关联关系。

Int pthread_key_delete(pthread_key_t *key);

注意调用pthread_key_delete并不会激活与键关联的析构函数。要释放任何与键对应的线程私有数据值的内存空间,需要在应用程序中采取额外的步骤。

需要确保分配的键并不会由于在初始化阶段的竞争而发生变动,解决这种竞争的办法是使用pthread_once

pthread_once_t initflag = PTHREAD_ONCE_INIT;

int pthread_once(pthread_once_t *initflag, void (*initfn)(void));

initflag必须是一个非本地变量(即全局变量或静态变量),而且必须初始化为PTHREAD_ONCE_INIT

如果每个线程都调用pthread_once,系统就能保证初始化例程initfn只被调用一次,即在系统首次调用pthread_once时。

创建键时避免竞争出现的一个恰当的方法可以描述如下:

void destructor(void *);

 

pthread_key_t key;

pthread_once_t init_done = PTHREAD_ONCE_INIT;

 

void thread_init(void)

{

  err = pthread_key_create(&key, destructor);

}

 

int threadfunc(void *arg)

{

  pthread_once(&init_done, thread_init);

}

键一旦创建,就可以调用pthread_setspecific函数把键和线程私有数据关联起来。可以通过pthread_getspecific函数获得线程私有数据的地址。

Void *pthread_getspecific(pthread_key_t key);

Int pthread_setspecific(pthread_key_t key, void *value);

10.取消选项

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

  可取消状态属性可以是PTHREAD_CANCEL_ENABLE,也可以是PTHREAD_CANCEL_DISABLE。线程可以通过调用pthread_setcancelstate修改它的可取消状态。

  Int pthread_setcancelstate(int state, int *oldstate)

  Pthread_setcancelstate把当前的可取消状态设置为state,把原来的可取消状态存放在由oldstate指向的内存单元中,这两步是原子操作。

  Pthread_cancel调用并不等待线程终止,在默认情况下,线程在取消请求发出以后还是继续进行,直到线程到达某个取消点。取消点是线程检查是否被取消并按照请求进行动作的一个位置。

  线程启动默认的可取消状态是PTHREAD_CANCEL_ENABLE。当状态为PTHREAD_CANCEL_DISABLE时,对pthread_cancel的调用并不一定杀死线程;相反,取消请求对这个线程来说处于未决状态。当取消状态再次变为PTHREAD_CANCEL_ENABLE时,线程将在下一个取消点上对未决的取消请求进行处理。

  可以调用pthread_testcancel函数在程序中自己添加取消点。

  Void pthread_testcancel(void);

  调用pthread_testcancel时,如果有某个取消请求正处于未决状态,而且取消并没有置为无效,那么线程就会被取消。但是如果取消被置为无效时,pthread_testcancel调用就没有任何效果。

  可以通过调用pthread_setcanceltype来修改取消类型。

  Int pthread_setcanceltype(int type, int *oldtype);

  Type参数可以是PTHREAD_CANCEL_DEFERRED,也可以是PTHREAD_CANCEL_ASYNCHRONOUS,pthread_setcanceltype函数把取消类型设置为type,把原来的取消类型返回到oldtype指向的整型单元。

  异步取消与延迟取消不同,使用异步取消时,线程可以在任意时间取消,而不是非得遇到取消点才能被取消。

11.线程和信号

  每个线程都有自己的信号屏蔽字,但是信号的处理是进程中所有线程共享的。这意味着尽管单个线程可以阻止某些信号,但当线程修改了与某个信号相关的信号处理行为以后,所有的线程都必须共享这个处理行为的改变。这样如果一个线程选择忽略某个信号,而其他的线程可以恢复信号的默认处理行为,或者为信号设置一个新的处理程序,从而可以撤销上述线程的信号选择。进程中的信号是递送到单个线程的。如果信号与硬件故障或计时器超时相关,该信号就被发送到引起该事件的线程中去,而其他的信号则被发送到任何一个线程。

  进程使用sigprocmask来阻止信号发送。线程必须使用pthread_sigmask

  Int pthread_sigmask(int how, sigset_t *set, sigset_t *oset);

  Pthread_sigmask函数与sigprocmask函数基本相同。

  线程可以通过调用sigwait等待一个或多个信号发生。

  Int sigwait(sigset_t *set, int *signop);

  Set参数指出了线程等待的信号集,signop指向的整数将作为返回值,表明发送信号的数量。

  如果信号集中的某个信号在sigwait调用的时候处于未决状态,那么sigwait将无阻塞的返回,在返回之前,sigwait将从进程中移除那些处于未决状态的信号。为了避免错误动作发生,线程在调用sigwait之前,必须阻塞那些它正在等待的信号。Sigwait函数会自动取消信号集的阻塞状态,直到有新的信号被递送。在返回之前,sigwait将恢复线程的信号屏蔽字。如果信号在sigwait调用的时候没有被阻塞,在完成对sigwait调用之前会出现一个时间窗,在这个窗口期,某个信号可能在线程完成sigwait调用之前被递送了。

  使用sigwait的好处在于它可以简化信号处理,允许把异步产生的信号用同步的方式处理。为了防止信号中断线程,可以把信号加到每个线程的信号屏蔽字中,然后安排专用线程做信号处理。这些专用线程可以进行函数调用,不需要担心在信号处理程序中调用哪些函数是安全的,因为这些函数调用来自正常的线程环境,而非传统的信号处理程序,传统信号处理程序通常会中断线程的正常执行。

  如果多个线程在sigwait调用时,等待的是同一个信号,这时就会出现线程阻塞。当信号递送的时候,只有一个线程可以从sigwait中返回。如果信号被捕获,而且线程正在sigwait调用中等待同一个信号,那么这时将由操作系统实现来决定以何种方式递送信号。在这种情况下,操作系统实现可以让sigwait返回,也可以激活信号处理程序,但不可能出现两者皆可的情况。

  要把信号发送到线程,可以调用pthread_kill

  Int pthread_kill(pthead_t thread, int signo);

  可以传一个0值的signo来检查线程是否存在。如果信号的默认处理动作是终止该进程,那么把信号传递给某个线程仍然会杀掉整个进程。

  注意闹钟定时器是进程资源,并且所有的线程共享相同的alarm。所以进程中的多个线程不可能互补干扰的使用闹钟定时器。

12.线程和fork

  子进程通过继承整个地址空间的副本,也从父进程那里继承了所有互斥量、读写锁和条件变量的状态。如果父进程包含多个线程,子进程在fork返回以后,如果紧接着不是马上调用exec的话,就需要清理锁状态。

  在子进程内部只存在一个线程,它是有父进程中调用fork的线程的副本构成的。如果父进程中的线程占有锁,子进程同样占有这些锁。问题是子进程并不包含占有锁的线程的副本,所以子进程没有办法直到它占有了哪些锁并且需要释放哪些锁。

  如果子进程从fork返回以后马上调用某个exec函数,就可以避免这样的问题。这种情况下,老的地址空间被丢弃,所以锁的状态无关紧要。但如果子进程需要继续做处理工作的话,这种方法就行不通,还需要使用其他策略。

  要清除锁状态,可以通过调用pthread_atfork函数建立fork处理程序。

  Int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));

  pthread_atfork函数最多可以安装三个帮助清理锁的函数。Prepare fork处理程序有父进程在fork创建子进程前调用,这个fork处理程序的任务是获取父进程定义的所有锁。Parent fork处理程序是在fork创建子进程以后,但在fork返回之前在父进程环境中调用,这个fork处理程序的任务是对prepare fork处理程序获得的所有锁进行解锁。Child fork处理程序在fork返回之前在子进程环境中调用,与parent fork处理程序一样,chilid fork也必须释放prepare fork处理程序获得的所有锁。

  注意不会出现加锁一次解锁两次的情况。

  可以多次调用pthread_atfork函数从而设置多套fork处理程序。如果不需要使用其中某个处理程序,可以给特定的处理程序参数传入空指针,这样就不会起任何作用。使用多个fork处理程序时,处理程序的调用顺序并不相同。Parentchild fork处理程序是以它们注册时的顺序进行调用的,而prepare fork处理程序的调用顺序与它们注册时的顺序相反。这样可以允许多个模块注册自己的fork处理程序,并且保持锁的层次。

三、总结

无。

四、效果评价

无。

五、推广建议

无。

参考资料
阅读(1350) | 评论(0) | 转发(0) |
0

上一篇:Linux下的信号

下一篇:linux下的core文件

给主人留下些什么吧!~~