Pthreads API

大纲

  • gerneral
  • thread opreation api
  • mutex
  • condition value

一、 Introduction

1980-90s,存在数种不同的线程接口,1995年POSIX.1 c对POSIX线程API进行标准化,后来被SUSv3接受。

1.1 线程数据类型

数据类型 描述
pthread_t 线程ID
pthread_attr_t 线程属性
pthread_mutex_t 互斥变量
pthread_mutexattr_t
pthread_cond_t 条件变量
pthread_condattr_t
pthread_key_t 线程特有数据的键key
pthread_once_t 一次性初始化控制上下文

二、 线程 Pthread

int pthread_create (pthread_t *__restrict __newthread,
			   const pthread_attr_t *__restrict __attr,
			   void *(*__start_routine) (void *),
			   void *__restrict __arg) ; // 创建新线程
void pthread_exit (void *__retval) ; // 终止本线程
int pthread_join (pthread_t __th, void **__thread_return); // 连接线程th,只能以阻塞态执行
int pthread_detach (pthread_t __th); // 线程分离
pthread_t pthread_self (void); // 获取本线程ID
int pthread_equal (pthread_t __thread1, pthread_t __thread2); // 比较线程ID,是否相等
int pthread_yield (void); // 主动让出CPU,触发线程调度。
int pthread_cancel (pthread_t __th); // 终止线程th
  • 线程分离:分离后,不能再通过join()来连接该线程。
  • yield()
    • 调用之后,本线程将被放在end of the queue for its static priority.
    • 如果没有更高/同等优先级的线程,那么还是继续运行该线程。但是这中间有线程调度,故而有context切换。
  • join()
    • 线程之间是对等的,故而没有父子之分。不必像process那样,只能父wait()子
    • 无法"连接任意线程",只能连接它知道的线程。
    • 不能以non-block方式连接。
  • pthread_t
    • 应该把pthread_t当作一个对象看待,使用pthread_self(), pthread_equal()等方法,避免不可移植的操作。
    • SUSv3并未要求pthread_t是个标量,因而也有系统定义为struct。在NPTL(Native POSIX Thread Library)中,pthread_t是一个强制转化unsigned long int的指针。

三、 互斥量 mutex

作用:保护对共享变量的访问

mutex : mutual exclusion

pthread_mutex_t mtx = PTHREAD_MUTEX_INITTIALIZER;

int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
  • 只有mutex的所有者(调用lock的线程)才能unlock该mutex。
  • 如果mutex已经locked,再调用lock()的线程会一直阻塞,直到mutex available。
  • 如果多个线程等待获取mutex,那么当mutex释放时,不能确定哪个线程将如愿以偿
  • lock()自己线程已经锁定的mutex,可能会导致自锁(在Linux中默认如此);也可能返回错误EDEADLK(视情况而定)。
  • unlock() 一个未被locked mutex或其他线程lock 的mutex,都会返回错误。
int pthread_mutex_trylock(pthread_mutex_t* mutex);
int pthread_mutex_timedlock (pthread_mutex_t *__restrict __mutex,
				    const struct timespec *__restrict __abstime);
  • trylock() 当mutex已经锁定时返回EBUSY错误
  • timedlock() 阻塞时间超过abstime时返回ETIMEDOUT错误

3.1 动态分配的 mutex

静态初始值PTHREAD_MUTEX_INITIALIZER,只能用于初始化:静态分配且携带默认attribute 的mutex。
其他情况下,必须调用pthread_mutex_init()进行动态初始化。

int pthread_mutex_init (pthread_mutex_t *__mutex,
			       const pthread_mutexattr_t *__mutexattr);
  • 若mutexattr==NULL,那么mutex各属性将使用默认值。
int pthread_mutex_destroy (pthread_mutex_t *__mutex);
  • 对于动态分配于堆中、或自动分配于栈中的互斥量,当不再需要时,应该用destroy()将其摧毁。
  • 用destroy()销毁的mutex,可以用init()重新初始化。

3.2 mutex的类型

对于以下行为,结果取决于mutex的类型:

  • 同一线程对同一mutex连续两次lock
  • 线程unlock自己不拥有的mutex
  • 线程unlock没有锁定的mutex

PTHREAD_MUTEX_NORMAL

  • 该类型不具有死锁自检功能
  • 对于两种错误的unlock行为,行为不确定(Linux中,两种行为都会成功)

PTHREAD_MUTEX_ERRORCHECK

  • 对所有错误都会进行检查,并返回对应的errno

PTHREAD_MUTEX_RECURSIVE

  • 有一个计数器,允许同一线程多次加锁,解锁至计数器==0时才会释放。

非标准的静态初始值

  • 例如PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP, PTHREAD_MUTEX_DEFAULT

四、 条件变量 condition value

作用:通知状态的改变

4.1 general

pthread condition提供如下函数,用于线程间同步的通知:

int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);//创建一个condition的内核变量
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);//等待内核变量变为signaled
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);//在指定时间内等待内核心变量变为signaled
int pthread_cond_destroy(pthread_cond_t *cond);//“析构”一个condition的内核变量
int pthread_cond_signal(pthread_cond_t *cond);//把内核变量置为signaled,指定一个等待线程激活,但不能指定特定线程
int pthread_cond_broadcast(pthread_cond_t *cond);//把内核变量置为signaled,指定所有等待线程激活
  • 发送信号:signal() 通知一个等待线程;broadcast() 通知所有等待线程
  • 等待:wait() 等待通知,获得通知前一直阻塞线程;timedwait(),阻塞等待,超过时间则返回ETIMEOUT错误

4.2 静态初始化和动态初始化

与mutex 类似

pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 静态初始化
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr); // 动态初始化

4.3 condition value 总是与一个 mutex 相关

  • 条件变量总是和互斥量结合使用。条件变量发出通知,互斥量提供对共享变量的互斥访问。
  • wait(&cond, &mutex), signal(&cond). 多个线程wait() 同一个cond时,它们指定的mutex必须相同。因为wait()会将cond和mutex动态绑定,所以,多个线程指定的mutex不同时,结果不可预料。

signal()使用案例

pthread_mutex_lock(&mtx);
// 在此修改共享变量, mutex 起保护共享变量作用
pthread_mutex_unlock(&mtx);
pthread_cond_signal(&cond); // 唤醒wait()线程
  • 最后两步(解锁、通知),顺序可以颠倒。不过“先signal后unlock”会出现其他线程已唤醒,但是mutex还没法获取的现象,导致重新阻塞等mutex。

wait()使用案例

pthread_mutex_lock(&mtx);
while(x) // 在这里check 共享变量状态,如果不满足,则进入循环体
{
	pthread_mutex_wait(&cond, &mtx); // 注意wait()上下文环境,理解wait()的操作
}
// 这里使用共享变量
pthread_mutex_unlock(&mtx); // 因为wait()自动lock了mutex,所以这里要unlock。
  • wait(&cond, &mutex) 执行的操作
    • 解锁mutex。(默认 mutex此前是被本线程locked状态)
    • 堵塞线程,直到获得cond通知
    • 锁定mutex。如果mutex没有锁定成功(被其他线程抢先锁定),则继续阻塞等待mutex。
  • wait() 这三步是原子操作。

4.4 动态分配的要销毁

int pthread_cond_destroy(pthread_cond_t *cond);
上一篇:c-禁用__thread支持


下一篇:linux基础优化