现在的位置: 首页 > 综合 > 正文

Pthread 介绍

2018年05月13日 ⁄ 综合 ⁄ 共 8644字 ⁄ 字号 评论关闭
 

1 Introduction
不用介绍了吧…
2 Thread Concepts
1.    
Thread由下面部分组成:
a.    
Thread ID
b.    
Stack
c.    
Policy
d.    
Signal mask
e.    
Errno
f.     
Thread-Specific Data
3 Thread Identification
1.    
pthread_t用于表示Thread
ID,具体内容根据实现的不同而不同,有可能是一个Structure,因此不能将其看作为整数
2.    
pthread_equal函数用于比较两个pthread_t是否相等
#i nclude <pthread.h>
 
int pthread_equal(pthread_t tid1, pthread_t tid2)


3.    
pthread_self函数用于获得本线程的thread id
#i nclude <pthread.h>
 
pthread _t pthread_self(void);

 
4 Thread Creation
1.    
创建线程可以调用pthread_create函数:
#i nclude <pthread.h>
 
int pthread_create(
      
pthread_t *restrict tidp,
      
const pthread_attr_t *restrict attr,
      
void *(*start_rtn)(void *), void *restrict arg);

a.    
pthread_t *restrict tidp:返回最后创建出来的Thread的Thread ID
b.    
const pthread_attr_t *restrict
attr:指定线程的Attributes,后面会讲道,现在可以用NULL
c.    
void *(*start_rtn)(void *):指定线程函数指针,该函数返回一个void *,参数也为void*
d.    
void *restrict arg:传入给线程函数的参数
e.    
返回错误值。
2.    
pthread函数在出错的时候不会设置errno,而是直接返回错误值
3.    
在Linux系统下面,在老的内核中,由于Thread也被看作是一种特殊,可共享地址空间和资源的Process,因此在同一个Process中创建的
不同Thread具有不同的Process
ID(调用getpid获得)。而在新的2.6内核之中,Linux采用了NPTL(Native POSIX Thread
Library)线程模型(可以参考http://en.wikipedia.org/wiki
/Native_POSIX_Thread_Library和http://www-128.ibm.com/developerworks/linux
/library/l-threading.html?ca=dgr-lnxw07LinuxThreadsAndNPTL),在该线程模型下同一进程下不同线程调用getpid返回同一个PID。

4.    
不能对创建的新线程和当前创建者线程的运行顺序作出任何假设
5 Thread Termination
1.    
exit, _Exit, _exit用于中止当前进程,而非线程
2.    
中止线程可以有三种方式:
a.    
在线程函数中return
b.    
被同一进程中的另外的线程Cancel掉
c.    
线程调用pthread_exit函数
3.    
pthread_exit和pthread_join函数的用法:
a.    
线程A调用pthread_join(B,
&rval_ptr),被Block,进入Detached状态(如果已经进入Detached状态,则pthread_join函数返回EINVAL)。如果对B的结束代码不感兴趣,rval_ptr可以传NULL。

b.    
线程B调用pthread_exit(rval_ptr),退出线程B,结束代码为rval_ptr。注意rval_ptr指向的内存的生命周期,不应该指向B的Stack中的数据。

c.    
线程A恢复运行,pthread_join函数调用结束,线程B的结束代码被保存到rval_ptr参数中去。如果线程B被Cancel,那么rval_ptr的值就是PTHREAD_CANCELLED。

两个函数原型如下:
#i nclude <pthread.h>
 
void pthread_exit(void *rval_ptr);
 
int pthread_join(pthread_t thread, void **rval_ptr);

4.    
一个Thread可以要求另外一个Thread被Cancel,通过调用pthread_cancel函数:
#i nclude <pthread.h>
 
void pthread_cancel(pthread_t tid)

该函数会使指定线程如同调用了pthread_exit(PTHREAD_CANCELLED)。不过,指定线程可以选择忽略或者进行自己的处理,在后面会讲到。此外,该函数不会导致Block,只是发送Cancel这个请求。

5.    
线程可以安排在它退出的时候,某些函数自动被调用,类似atexit()函数。需要调用如下函数:
#i nclude <pthread.h>
 
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);

这两个函数维护一个函数指针的Stack,可以把函数指针和函数参数值push/pop。执行的顺序则是从栈顶到栈底,也就是和push的顺序相反。

在下面情况下pthread_cleanup_push所指定的thread cleanup handlers会被调用:
a.    
调用pthread_exit
b.    
相应cancel请求
c.    
以非0参数调用pthread_cleanup_pop()。(如果以0调用pthread_cleanup_pop(),那么handler不会被调用
有一个比较怪异的要求是,由于这两个函数可能由宏的方式来实现,因此这两个函数的调用必须得是在同一个Scope之中,并且配对,因为在
pthread_cleanup_push的实现中可能有一个{,而pthread_cleanup_pop可能有一个}。因此,一般情况下,这两个函数
是用于处理意外情况用的,举例如下:

void *thread_func(void *arg)
{
   
pthread_cleanup_push(cleanup, “handler”)
 
    // do
something
 
   
Pthread_cleanup_pop(0);
    return((void
*)0);
}

 
6.    
进程函数和线程函数的相关性:
Process Primitive Thread Primitive Description
fork pthread_create 创建新的控制流
exit pthread_exit 退出已有的控制流
waitpid pthread_join 等待控制流并获得结束代码
atexit pthread_cleanup_push 注册在控制流退出时候被调用的函数
getpid pthread_self 获得控制流的id
abort pthread_cancel 请求非正常退出

7.    
缺省情况下,一个线程A的结束状态被保存下来直到pthread_join为该线程被调用过,也就是说即使线程A已经结束,只要没有线程B调用
pthread_join(A),A的退出状态则一直被保存。而当线程处于Detached状态之时,党线程退出的时候,其资源可以立刻被回收,那么这个
退出状态也丢失了。在这个状态下,无法为该线程调用pthread_join函数。我们可以通过调用pthread_detach函数来使指定线程进入
Detach状态:

#i nclude <pthread.h>
 
int pthread_detach(pthread_t tid);

通过修改调用pthread_create函数的attr参数,我们可以指定一个线程在创建之后立刻就进入Detached状态
6 Thread Synchronization
1.    
互斥量:Mutex
a.    
用于互斥访问
b.    
类型:pthread_mutex_t,必须被初始化为PTHREAD_MUTEX_INITIALIZER(用于静态分配的mutex,等价于pthread_mutex_init(…,
NULL))或者调用pthread_mutex_init。Mutex也应该用pthread_mutex_destroy来销毁。这两个函数原型如下:(attr的具体含义下一章讨论)

#i nclude <pthread.h>
 
int pthread_mutex_init(
      
pthread_mutex_t *restrict mutex,
      
const pthread_mutexattr_t *restrict attr)
 
int pthread_mutex_destroy(pthread_mutex_t *mutex);

c.    
pthread_mutex_lock用于Lock
Mutex,如果Mutex已经被Lock,该函数调用会Block直到Mutex被Unlock,然后该函数会Lock
Mutex并返回。pthread_mutex_trylock类似,只是当Mutex被Lock的时候不会Block,而是返回一个错误值EBUSY。pthread_mutex_unlock则是unlock一个mutex。这三个函数原型如下:

#i nclude <pthread.h>
 
int pthread_mutex_lock(pthread_mutex_t *mutex);
 
int pthread_mutex_trylock(pthread_mutex_t *mutex);
 
int pthread_mutex_unlock(pthread_mutex_t *mutex);

 
2.    
读写锁:Reader-Writer Locks
a.    
多个线程可以同时获得读锁(Reader-Writer lock in read
mode),但是只有一个线程能够获得写锁(Reader-writer lock in write mode)
b.    
读写锁有三种状态
                                         
i.   
一个或者多个线程获得读锁,其他线程无法获得写锁
                                        
ii.   
一个线程获得写锁,其他线程无法获得读锁
                                       
iii.   
没有线程获得此读写锁
c.    
类型为pthread_rwlock_t
d.    
创建和关闭方法如下:
#i nclude <pthread.h>
 
int pthread_rwlock_init(
      
pthread_rwlock_t *restrict rwlock,
      
const pthread_rwlockattr_t *restrict attr)
 
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

e.    
获得读写锁的方法如下:
#i nclude <pthread.h>
 
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);

 
pthread_rwlock_rdlock:获得读锁
pthread_rwlock_wrlock:获得写锁
pthread_rwlock_unlock:释放锁,不管是读锁还是写锁都是调用此函数
注意具体实现可能对同时获得读锁的线程个数有限制,所以在调用pthread_rwlock_rdlock的时候需要检查错误值,而另外两个
pthread_rwlock_wrlock和pthread_rwlock_unlock则一般不用检查,如果我们代码写的正确的话。

3.    
Conditional Variable:条件
a.    
条件必须被Mutex保护起来
b.    
类型为:pthread_cond_t,必须被初始化为PTHREAD_COND_INITIALIZER(用于静态分配的条件,等价于pthread_cond_init(…,
NULL))或者调用pthread_cond_init
#i nclude <pthread.h>
 
int pthread_cond_init(
      
pthread_cond_t *restrict cond,
      
const pthread_condxattr_t *restrict attr)
 
int pthread_cond_destroy(pthread_cond_t *cond);

 
c.    
pthread_cond_wait函数用于等待条件发生(=true)。pthread_cond_timedwait类似,只是当等待超时的时候返回
一个错误值ETIMEDOUT。超时的时间用timespec结构指定。此外,两个函数都需要传入一个Mutex用于保护条件

#i nclude <pthread.h>
 
int pthread_cond_wait(
      
pthread_cond_t *restrict cond,
      
pthread_mutex_t *restrict mutex);
 
int pthread_cond_timedwait(
      
pthread_cond_t *restrict cond,
      
pthread_mutex_t *restrict mutex,
      
const struct timespec *restrict timeout);

 
d.    
timespec结构定义如下:
struct timespec {
      
time_t
tv_sec;      

      
long  
tv_nsec;     

};

注意timespec的时间是绝对时间而非相对时间,因此需要先调用gettimeofday函数获得当前时间,再转换成timespec结构,加上偏移量。

e.    
有两个函数用于通知线程条件被满足(=true):
#i nclude <pthread.h>
 
int pthread_cond_signal(pthread_cond_t *cond);
 
int pthread_cond_broadcast(pthread_cond_t *cond);

两者的区别是前者会唤醒单个线程,而后者会唤醒多个线程。

补充:

在传统的Unix模型中,当一个进程需要由另一个实体执行某件事时,该进程派生(fork)一个子进程,让子进程去进行处理。Unix下的大多数网络服务器程序都是这么编写的,即父进程接受连接,派生子进程,子进程处理与客户的交互。

虽然这种模型很多年来使用得很好,但是fork时有一些问题:

1.
fork是昂贵的。内存映像要从父进程拷贝到子进程,所有描述字要在子进程中复制等等。目前有的Unix实现使用一种叫做写时拷贝(copy-on-write)的技术,可避免父进程数据空间向子进程的拷贝。尽管有这种优化技术,fork仍然是昂贵的。

2.
fork子进程后,需要用进程间通信(IPC)在父子进程之间传递信息。Fork之前的信息容易传递,因为子进程从一开始就有父进程数据空间及所有描述字的拷贝。但是从子进程返回信息给父进程需要做更多的工作。

线程有助于解决这两个问题。线程有时被称为轻权进程(lightweight
process),因为线程比进程“轻权”,一般来说,创建一个线程要比创建一个进程快10~100倍。

一个进程中的所有线程共享相同的全局内存,这使得线程很容易共享信息,但是这种简易性也带来了同步问题。

一个进程中的所有线程不仅共享全局变量,而且共享:进程指令、大多数数据、打开的文件(如描述字)、信号处理程序和信号处置、当前工作目录、用户ID和组
ID。但是每个线程有自己的线程ID、寄存器集合(包括程序计数器和栈指针)、栈(用于存放局部变量和返回地址)、error、信号掩码、优先级。在
Linux中线程编程符合Posix.1标准,称为Pthreads。所有的pthread函数都以pthread_开头。以下先讲述5个基本线程函数,
在调用它们前均要包括pthread.h头文件。然后再给出用它们编写的一个TCP客户/服务器程序例子。

第一个函数:

 

int pthread_create (pthread_t *tid,const pthread_attr_t
*attr,void
*     
(*func)(void *),void *arg);

一个进程中的每个线程都由一个线程ID(thread ID)标识,其数据类型是pthread_t(常常是unsigned
int)。如果新的线程创建成功,其ID将通过tid指针返回。

每个线程都有很多属性:优先级、起始栈大小、是否应该是一个守护线程等等,当创建线程时,我们可通过初始化一个pthread_attr_t变量说明这些属性以覆盖缺省值。我们通常使用缺省值,在这种情况下,我们将attr参数说明为空指针。

最后,当创建一个线程时,我们要说明一个它将执行的函数。线程以调用该函数开始,然后或者显式地终止(调用pthread_exit)或者隐式地终止(让
该函数返回)。函数的地址由func参数指定,该函数的调用参数是一个指针arg,如果我们需要多个调用参数,我们必须将它们打包成一个结构,然后将其地
址当作唯一的参数传递给起始函数。

在func和arg的声明中,func函数取一个通用指针(void *)参数,并返回一个通用指针(void
*),这就使得我们可以传递一个指针(指向任何我们想要指向的东西)给线程,由线程返回一个指针(同样指向任何我们想要指向的东西)。调用成功,返回0,出错时返回正Exxx值。Pthread函数不设置errno。

第二个函数:

 

int pthread_join(pthread_t tid,void **status);

该函数等待一个线程终止。把线程和进程相比,pthread_creat类似于fork,而pthread_join类似于waitpid。我们必须要等
待线程的tid,很可惜,我们没有办法等待任意一个线程结束。如果status指针非空,线程的返回值(一个指向某个对象的指针)将存放在status指
向的位置。

第三个函数:

 

pthread_t pthread_self(void);

线程都有一个ID以在给定的进程内标识自己。线程ID由pthread_creat返回,我们可以pthread_self取得自己的线程ID。

第四个函数:

 

int pthread_detach(pthread_t tid);

线程或者是可汇合的(joinable)或者是脱离的(detached)。当可汇合的线程终止时,其线程ID和退出状态将保留,直到另外一个线程调用
pthread_join。脱离的线程则像守护进程:当它终止时,所有的资源都释放,我们不能等待它终止。如果一个线程需要知道另一个线程什么时候终止,
最好保留第二个线程的可汇合性。Pthread_detach函数将指定的线程变为脱离的。该函数通常被想脱离自己的线程调用,
如:pthread_detach
(pthread_self ( ));

 

 

 

 

第五个函数:

void pthread_exit(void *status);

该函数终止线程。如果线程未脱离,其线程ID和退出状态将一直保留到调用进程中的某个其他线程调用pthread_join函数。指针status不能指向局部于调用线程的对象,因为线程终止时这些对象也消失。有两种其他方法可使线程终止:

1.
启动线程的函数(pthread_creat的第3个参数)返回。既然该函数必须说明为返回一个void指针,该返回值便是线程的终止状态。

2. 如果进程的main函数返回或者任何线程调用了exit,进程将终止,线程将随之终止。

 

==================

相关链接:

POSIX thread (pthread) 多线程编程简介

pthread之线程堆栈

Linux下pthread线程库介绍

Pthread 介绍

抱歉!评论已关闭.