经过了上篇文档的初步学习,对pthread有了一个简单的感性认识,但是对pthread的认识还是比较少,在这篇文档当中将要主要学习pthread的一些常用的API。
首先是pthread的线程创建API: pthread_create
#include<pthread.h>
int pthread_create(pthread_t *thread,//要创建的线程
pthread_attr_t *attr,//线程的相关属性
void* (*start_routine)(void*),//线程要执行的函数指针
void *arg//传递给start_routine的参数
);
当创建函数执行成功的时候返回0,并把创建的线程tid写入传入的线程指针中去(第一个参数),否则返回一个非零值并设置errno。
其中,第一个和第三个参数是必须要设置的,其他两个参数可以根据情况设置,当没有需求的时候传入NULL即可。
pthread_exit: 终止当前线程
#include<pthread.h>
void pthread_exit(void *retval);
//该函数用于退出当前线程,退出之前将调用pthread_cleanup_push
//上述函数将在下文中介绍。
//该函数在线程的上层函数中是被隐式调用的,可以增加一个retval参数
//显示调用,以供pthread_join函数参考
pthread_join:挂起当前线程直到指定的线程终止为止(这个函数对于pthread十分重要,正如上一篇文档中所说的,不调用该函数可能会造成后续的create失败的问题)。
#include<pthread.h>
int pthread_join(pthread_t thread, void **thread_return);
//thread_return为th终止时的参数,
//没有显式指定则为NULL
//需要查阅该参数的使用方法
pthread_cancel: 撤销一个线程
#include<pthread.h>
//撤销线程thread
int pthread_cancel(pthread_t thread);
//用于设置当前线程的撤销状态
//PTHREAD_CANCEL_ENABLE允许撤销
//PTHREAD_CANCEL_DISABLE忽略撤销
//为了避免被另外的线程用pthread_cancel撤销
//oldstate用来保存之前的状态,以便恢复
int pthread_setcancelstate(int state, int *oldstate);
//设置当前线程的撤销类型,包括:
//PTHREAD_CANCEL_ASYNCHRONOUS立即撤销
//PTHREAD_CANCEL_DEFERRED 延迟至撤销点
int pthread_setcanceltype(int type, int *oldtype);
线程属性:(上文中create函数的第二个参数,类型为pthread_attr_t,可以使用pthread_attr_XXXX函数族调用)
detachstate:分离或者切入状态,有两个值PTHREAD_CREATE_JOINABLE(default value),PHTREAD_CREATE_DETACHED
schedpolicy: 调度策略,取值有: SCHED_OTHER, SCHED_FIFO
schedparam: 调度策略相关
inheritsched: PTHREAD_EXPLICIT_SCHED(default value), PTHREAD_INERIT_SCHED
scope: 时间片,取值有:PTHREAD_SCOPE_SYSTEM(default value每个线程 一个系统时间片), PTHREAD_SCOPE_PROGCESS(线程共享系统时间片)。
pthread cleanup宏
pthread cleanup宏主要用来处理线程的推出状态,pthread_exit和pthread_join等可以作为它的参数
#include<pthread.h>
//这些都是宏定义,相关细节可以查看/usr/include/pthread.h
void pthread_cleanup_push( void(*routine)(void*), void *arg);
void pthread_cleanup_pop(int execute);
void pthread_cleanup_push_defer_np(void(*routine)(void*), void *arg);
void pthread_cleanup_pop_restore_np(int execute);
//对这些宏的理解并不深入,在后续的学习中参考相应的例子
//进一步的学习。
//这些宏用于线程结束时释放相关资源,用pthread_exit调用pthread_cleanup
//进行处理时,用完后要用对应的pthread_cleanup_pop从栈中弹出
互斥mutex
由于线程是并发执行的,因此有时候需要对一些数据进行保护,例如多线程标准输出如果不加以处理,那么输出基本上都会是乱码,此时就可以使用mutex对输出流进行控制,一个线程执行写入操作的时候,加一把互斥锁,以防别的线程同时写入。
互斥对象在pthread中的定义为pthread_mutex_t,以下是它的一些API函数:
#include<pthread.h>
//创建互斥对象,用指定的初始化属性初始化互斥对象
//属性包括:
//PTHREAD_MUTEX_INITIALIZER 快速互斥,简单加解锁
//PTHREAD_RECURSIVE_MUTEX_INITIALIZER
//递归互斥,给加锁计数,解锁时需要同样次数的pthread_mutex_unlock
//PTHREAD_ERRORCHECK_MUTEX_INITIALIZER
//创建检错互斥,这种互斥在被锁之后会试图给它加锁的线程返回一个
//EDEADLK错误代码而不阻塞
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutex_attr_t *mutexattr);
//加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
//解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//加锁,但是如果对象已经上锁则返回EBUSY错误代码而不阻塞
int pthread_mmutex_trylock(pthread_mutex_t *mutex);
//析构并释放mutex相关资源
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//以上的函数成功均返回0,和一般的函数一致
条件变量
线程使用条件变量对象来阻塞自己以等待某个特定条件的发生。
条件对象的定义为pthread_cond_t
#include<pthread.h>
//创建指向条件变量的指针,在linux下可以使用PTHREAD_COND_INITIALIZER
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond,
pthread_condattr_t *cond_attr);
//析构
int pthread_cond_destroy(pthread_cond_t *cond);
//两个信号函数
int pthread_cond_singal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
//两个等待条件否则挂起的函数
//当前期望的条件没有到来的时候挂起线程,解锁mutex
//条件成立的时候唤起线程,锁定互斥
int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t *mutex);
//abstime参数和兼容time()返回值的绝对时间,
//UNIX纪元时间1970-01-01起至今的秒数
//如果该时间前条件未发生,则结束并返回ETIMEOUT错误
int pthread_cond_wimewait(pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct timespec *abstime);
//上述两个函数都将被设置为撤销点(这个撤销点的意义?)
至此,基本的pthread的API函数就学习到这里了,在后续的学习当中,将会结合例子更深入的学习。、
==================================================================================================================
1 Introduction
不用介绍了吧…
2 Thread Concepts
1.
a.
b.
c.
d.
e.
f.
3 Thread Identification
1.
2.
#i nclude <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2)
3.
#i nclude <pthread.h>
pthread _t pthread_self(void);
4 Thread Creation
1.
#i nclude <pthread.h>
int pthread_create(
a.
b.
c.
d.
e.
2.
3.
4.
5 Thread Termination
1.
2.
a.
b.
c.
3.
a.
b.
c.
两个函数原型如下:
#i nclude <pthread.h>
void pthread_exit(void *rval_ptr);
int pthread_join(pthread_t thread, void **rval_ptr);
4.
#i nclude <pthread.h>
void pthread_cancel(pthread_t tid)
该函数会使指定线程如同调用了pthread_exit(PTHREAD_CANCELLED)。不过,指定线程可以选择忽略或者进行自己的处理,在后面会讲到。此外,该函数不会导致Block,只是发送Cancel这个请求。
5.
#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.
b.
c.
有一个比较怪异的要求是,由于这两个函数可能由宏的方式来实现,因此这两个函数的调用必须得是在同一个Scope之中,并且配对,因为在pthread_cleanup_push的实现中可能有一个{,而pthread_cleanup_pop可能有一个}。因此,一般情况下,这两个函数是用于处理意外情况用的,举例如下:
void *thread_func(void *arg)
{
}
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.
#i nclude <pthread.h>
int pthread_detach(pthread_t tid);
通过修改调用pthread_create函数的attr参数,我们可以指定一个线程在创建之后立刻就进入Detached状态
6 Thread Synchronization
1.
a.
b.
#i nclude <pthread.h>
int pthread_mutex_init(
int pthread_mutex_destroy(pthread_mutex_t *mutex);
c.
#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.
a.
b.
c.
d.
#i nclude <pthread.h>
int pthread_rwlock_init(
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.
a.
b.
#i nclude <pthread.h>
int pthread_cond_init(
int pthread_cond_destroy(pthread_cond_t *cond);
c.
#i nclude <pthread.h>
int pthread_cond_wait(
int pthread_cond_timedwait(
d.
struct timespec {
};
注意timespec的时间是绝对时间而非相对时间,因此需要先调用gettimeofday函数获得当前时间,再转换成timespec结构,加上偏移量。
e.
#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 *
一个进程中的每个线程都由一个线程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,进程将终止,线程将随之终止。