线程的表示
线程标识TID,表示线程的数据类型pthread_t。
获取自己的TID:
#include <pthread.h>
pthread_t pthread_self(void);
比较两个线程:
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2); //相同返回非0,不相同返回0
创建线程
#include <pthread.h>
int pthread_create(pthread_t *restrict thread, const pthread_attr_t *restrict attr, void *(*start_routine)(void*), void *restrict arg);
参数:
第一个参数thread用于存放新产生的线程的TID,函数的返回值存在这里
第二个参数attr是所创建线程的属性。通常为NULL,即系统默认属性
第三个参数start_routine是线程开始函数
第四个参数arg是线程开始函数的参数,而且是唯一参数。如果start_routine有多个参数,那么把他们封装在结构中。
终止线程
#include <pthread.h>
int pthread_exit(void *value_ptr);
pthread_exit函数终止自己的执行。
参数:
value_ptr是线程终止出口状态。当另一个线程调用pthread_join()等待该线程时,那个线程就可以得到value_ptr给出的值。value_ptr可以是一个数据结构。只有当线程是可汇合的线程时,value_ptr才非空,否则为NULL
#include <pthread.h>
int pthread_join(pthread_t thread, void **value_ptr);
这个函数会把调用这个函数的线程悬挂起来,等待thread指定的线程终止。如果value_ptr是非NULL,他所指的对象将存放线程thread终止时传递的值。而且这个函数还会释放thread线程占用的系统资源。
注册退出清理函数:pthread_cleanup_push,类似于进程的退出注册函数atexit一样。
i. 调用时机1:pthread_exit
ii. 调用时机2:其他线程要求pthread_cancel取消本线程时,响应用
iii. 调用时机3:非0参数,使用pthread_clean_pop。参数0时,不调用直接删除
如果一个线程不是分离的,它的资源要在另一个线程调用pthread_join()与他汇合之后才被释放。这种线程就是可汇合的线程,否则是分离的线程。所以pthread_join函数等待的线程必须是可汇合的。
#include <pthread.h>
int a[SIZE], b[SIZE];
void max_fun(int *arg)
{
int *ap = arg;
...
pthread_exit((void *)&ap[k]);
}
int main()
{
...
int **ptr1, **ptr2;
pthread_create(...);
pthread_create(...);
pthread_join(tid1, (void **)&ptr1);
pthread_join(tid2, (void **)&otr2);
...
}
可汇合与分离的线程
#include <pthread.h>
int pthread_detach(pthread_t thread);
这个函数使一个原本可汇合的线程改变为分离的线程。这个函数对同一个线程只调用一次。
特殊属性的线程
线程属性:pthread_attr_t
#include <pthread.h>
int pthread_attr_init(pthread_attr_t *attr); //初始化属性对象
int pthread_attr_destroy(pthread_attr_t *attr);
第一个函数初始化属性对象,传进去的参数只是一个指针,这个函数有两个动作,一个是为属性对象分配空间,一个是给他赋初值。
可以制定的属性有:
(1)分离状态:PTHREAD_CREATE_DETACHED(分离的线程),PTHREAD_CREATE_JOINABLE(可汇合的线程)
#include <pthread.h>
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
(2)栈大小
#include <pthread.h>
int pthread_attr_getstacksize(const pthread_attr_t *restrict attr, size_t *restrict stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
(3)栈地址
#include <pthread.h>
int pthread_attr_getstackaddr(const pthread_attr_t *restrict attr, void **restrict stackaddr);
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
也可以通过函数同时设置或获取栈地址和大小:
#include <pthread.h>
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
int pthread_attr_getstack(const pthread_attr_t *restrict attr, void **restrict stackaddr, size_t *restrict stacksize);
(4)栈溢出保护区大小:注意栈有向低地址延伸的,也有向高地址延伸的。
#include <pthread.h>
int pthread_attr_getguardsize(const pthread_attr_t *restrict attr, size_t *restrict guardsize);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
线程同步的方法:
(1)互斥执行。两个线程必须依次访问某个共享对象
(2)条件同步。线程必须等待某个事件发生
(3)栅栏同步。控制线程执行过程中的汇合,比如在某处等齐所有线程
互斥变量
互斥变量的类型为pthread_mutex_t,使用之前必须初始化,且只能初始化一次,初始化方法:
#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
或者
#include <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);
互斥变量的属性
数据结构pthread_mutexattr_t,属性的初始化和销毁:
#include <pthread.h>
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
可定制的属性:
(1)共享属性:PTHREAD_PROCESS_PRIVATE(仅由同一个进程内的线程使用),PTHREAD_PROCESS_SHARED(可以由多个进程的线程使用)
(2)类型属性:PTHREAD_MUTEX_NORMAL(基本类型,如果在加锁状态,则阻塞直到该锁被释放才能对其成功加锁),PTHREAD_MUTEX_RECURSIVE(递归类型,允许同一个线程在对其解锁之前多次加锁,为了释放这种互斥变量,必须对他解锁相同的次数),PTHREAD_MUTEX_ERRORCHECK,PTHREAD_MUTEX_DEFAULT
#include <pthreadh>
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr, int *restrict type);
互斥变量的加锁与解锁
#include <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);
如果类型为PTHREAD_MUTEX_RECURSIVE,pthread_mutex_lock函数会维护互斥变量的锁计数器。每加一道锁计数器+1,每次解锁计数器-1.当锁为0时,置为未锁状态。
pthread_mutex_lock和pthread_mutex_trylock的不同之处在于,如果mutex当前已经被锁住,线程不会受阻,而是立即以EBUSY失败返回。但如果是PTHREAD_MUTEX_RECURSIVE类型并且已被调用线程占有,则mutex计数器+1,函数立即成功返回。
spin锁
当上锁受阻时,线程不必阻塞而是可以轮询直到获得锁为止。
读写锁
主要用于保护读操作频繁但写操作很少的共享数据。但是其上锁和解锁操作比互斥变量开销大。
条件变量
当线程访问某个共享数据,不仅需要互斥,还要求这个共享数据满足某种状态或条件,如果其他线程不改变这个状态,就无法继续处理。那么可以将自己加入到一个等待队列,然后阻塞自己并等待被生产线程唤醒。