[UNIX环境高级编程] 线程基础

1 线程基本概念

典型的UNIX进程可以看成只有一个控制线程:一个进程在某一时刻只能做一件事。有了多个控制线程之后,在程序设计时就可以把进程设计成在某一时刻能够做不止一件事,每个线程各自处理独立的任务。
每个线程都包含有表示执行环境所必须的信息,其中包括进程中标识线程的线程ID一组寄存器值调度优先级和策略信号屏蔽字errno变量以及线程私有数据。一个进程的所有信息对该进程的所有线程都是共享的,包括可执行程序的代码程序的全局内存堆内存以及文件描述符

2 线程标识

如同每个进程都有一个进程ID一样,每个线程也有一个线程ID。进程ID在整个系统中时唯一的,但线程ID不同,线程ID只有在它所属的进程上下文中才有意义。
下面的函数用来对两个线程的ID进行比较。

#include <pthread.h>
// return nonzero value if two thread IDs equal or 0 if not.
int pthread_equal(pthread_t t1, pthread_t t2);

下面的函数用来获取自身的线程ID。

#include <pthread.h>
pthread_t pthread_self(void);

3 线程创建

#include <pthread.h>
int pthread_create(
    pthread_t* thread,
    const pthread_attr_t* attr,
    void*(*start_routine)(void*),
    void* arg
);

当pthread_create函数成功返回时,新创建的线程的线程ID会被设置成thread指向的内存单元。attr参数用于定制各种不同的线程属性。
新创建的线程从start_routine函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要传入的参数有一个以上,需要做成结构体,传入arg。
线程创建时不能保证哪个线程先执行:新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除。

4 线程终止

如果进程中的任意线程调用了exit、_Exit或者_exit,那么整个进程就会终止
单个线程可以通过3种方式退出,因此可以在不终止整个进程的情况下,停止它的控制流:
[1] 线程可以简单地从启动例程中返回,返回值时线程的退出码。
[2] 线程可以被同一进程中的其他线程取消。
[3] 线程调用pthread_exit。

#include <pthread.h>
void pthread_exit(void* retval);

retval参数是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程也可以通过调用pthread_join函数访问到这个指针。

#include <pthread.h>
int pthread_join(pthread_t thread, void** retval);

可以通过调用pthread_join自动把线程置于分离状态1
线程可以通过调用pthread_cancel函数来请求取消统一进程中的其他线程。

#include <pthread.h>
int pthread_cancel(pthread_t thread);

线程可以安排它退出时需要调用的函数,与进程退出时可以调用atexit函数类似,这样的函数称为线程清理处理程序。

#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);

如果线程时通过从它的启动例程中返回而终止时,它的清理函数就不会被调用。

总结进程和线程函数之间的相似之处。

进程原语线程原语描述
forkpthread_create创建
exitpthread_exit退出
waitpidpthread_join等待并获得退出状态
atexitpthread_cancel_push注册退出时调用的函数
getpidpthread_self获得ID
abortpthread_cancel请求非正常退出
#include <pthread.h>
int pthread_detach(pthread_t thread);

5 线程同步

5.1 互斥量

可以使用pthread的互斥接口来保护数据,确保同一时间只有一个线程访问数据。互斥量mutex本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问结束后释放(解锁)互斥量。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程都会被阻塞直到当前线程释放该互斥锁。
互斥变量是用pthread_mutex_t数据类型表示的。在使用互斥变量以前,必须首先对它进行初始化,可以把他设置为常量PTHREAD_MUTEX_INITIALIZER(只使用于静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态分配互斥量,则在释放内存前需要调用pthread_mutex_destroy。

#include <pthread.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int pthread_mutex_destroy(pthread_mutex_t* mutex);
int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutexattr* restrict attr);

对互斥量进行加锁,需要调用pthread_mutex_lock。解锁需要调用pthread_mutex_unlock,若不希望线程阻塞,可以使用pthread_mutex_trylock,如果可以加锁,则锁住并返回0,否则返回EBUSY。

#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_timedlock互斥量原语允许绑定线程阻塞时间。pthread_mutex_timedlock与pthread_mutex_lock是基本等价的,但是在达到超时时间时,pthread_mutex_timedlock不会对互斥量进行加锁,而是返回错误码ETIMEOUT。

#include <pthread.h>
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t* restrict mutex, const struct timespec* restrict abs_timeout);

5.2 读写锁

读写锁与互斥量类似,不过读写锁允许更高的并行性。一次只有一个线程可以占有写锁,但可以由多个线程同时占有读锁。
当附加了写锁后,无论读锁还是写锁都会被阻塞。当附加了读锁后,写锁会被阻塞。
与互斥量相比,读写锁在使用之前必须初始化,在释放它们底层的内存之前必须销毁。

#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);
int pthread_rwlock_init(pthread_rwlock_t* restrict rwlock, const pthread_rwlockattr_t* restrict attr);

int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);

int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock);
int phtread_rwlock_trywrlock(pthread_rwlock_t* rwlock);

int pthread_rwlock_unlock(pthread_rwlock_t* rwlock);

5.3 带有超时的读写锁

#include <pthread.h>
#include <time.h>
int pthread_rwlock_timedrdlock(pthread_rwlock_t* restrict rwlock, const struct timespce* restrict abs_timeout);
int pthread_rwlock_timedwrlock(pthread_rwlock_t* restrict rwlock, const struct timespec* restrict abs_timeout);

5.4 条件变量

条件变量是线程可用的另一种同步机制。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
在使用条件变量之前,必须先对它进行初始化。由pthread_cond_t数据类型表示的条件变量可以由两种方式初始化:常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量,动态分配需要使用pthread_cond_init,使用pthread_cond_destroy函数释放。
使用pthread_cond_wait等待条件变量变为真,pthread_cond_timedwait可以设置一个超时值,超时返回一个错误码。

#include <pthread.h>
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t* restrict cond, const pthread_condattr_t* restrict attr);
int pthread_cond_destroy(pthread_cond_t* cond);

int pthread_cond_timewait(pthread_cond_t* restrict cond, pthread_mutex_t* restrict mutex, const struct timespec* restrict abstime);
int pthread_cond_wait(pthread_cond_t* restrict cond, pthread_mutex_t* restrict mutex);

pthread_cond_signal函数至少能唤醒一个等待该条件的线程,pthread_cond_broadcast函数能唤醒等待该条件的全部线程。

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t* cond);
int pthreda_cond_broadcast(pthread_cond_t* cond);

在调用pthread_cond_signal或者pthread_cond_broadcast时,我们认为这是在给线程或者条件发信号,一定要注意,一定要在改变条件状态之后再给线程发信号

5.5 自旋锁

自旋锁与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)阻塞状态。自旋锁可用于以下情况:锁被持有的时间短而且线程并不希望在重新调度上花费太多的成本

#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t* lock, int pshared);
int pthread_spin_destroy(pthread_spinlock_t* lock);

int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);

5.6 屏障

屏障是多用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后从该点继续执行。pthread_join就是一种屏障,允许一个线程等待,直到另一个线程退出。

#include <pthread.h>
int pthread_barrier_init(
    pthread_barrier_t* restrict barrier,
    const pthread_barrierattr_t* restrict attr,
    unsigned count
);
int pthread_barrier_destroy(pthread_barrier_t* barrier);

int pthread_barrier_wait(pthread_barrier_t* barrier);

  1. 在默认情况下,线程的终止状态会保存直到对该线程调用pthread_join。如果线程已经被分离,线程的底层资源可以在线程终止时立即被回收。在线程被分离后,我们不能用pthread_join函数等待它的终止状态,可以调用pthread_detach分离线程。 ↩︎

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值