《UNIX环境高级编程》十一线程读书笔记

1、线程概念

  • 典型的UNIX进程可以看成只有一个控制线程:一个进程在某一时刻只能做一件事情。有了多个控制线程以后,在程序设计时可以把进程设计称在某一时刻能够做不止一件事,每个线程处理各自独立的人物。
  • 一个进程的又有信息对该进程的所有线程都是共享的,包括可执行程序的带吗、程序的全局内存和堆内存、栈以及文件描述符。

2、线程标识

每个线程有一个线程ID。进程ID在整个系统中是唯一的,但线程ID只有在它所属的进程上下文中才有意义。

对两个线程ID进行比较:

#include <pthread.h>
int pthread_equal(pthread_t tid1,pthread_t tid2);
若相等,返回非0数值;否则,返回0

线程可以通过调用pthread_self函数获得自身的线程ID:

#include <pthread.h>
pthread_t pthread_self(void);
返回调用线程的线程ID

3、线程创建

在POSIX线程的情况下,程序开始运行时,它也是以单进程中的单个控制线程启动的。
新增的线程可以通过调用pthread_create函数创建:

#include <pthread.h>
int pthread_create(pthread_t *restrict tidp,
                    const pthread_attr_t *restrict attr,
                void *(*start_rtn)(void *),void *restrict arg);
若成功,返回0;否则,返回错误编号
  • 当pthread_create成功返回时,新创建线程的线程ID会被设置成tidp指向的内存单元。
  • attr参数用于定制各种不用的线程属性。
  • 新创建的线程从start)rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递的参数由一个以上,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。

注:pthread函数在调用失败时通常会返回错误码。

4、线程终止

如果进程中的任意线程调用了exit、_Exit、_exit,那么整个进程就会终止,类似的,如果默认的动作是终止进程,那么,发送到线程的信号就会终止整个进程。

单个线程可以通过3种方式退出:
(1)线程可以简单地从启动例程中返回,返回值是线程的退出码。
(2)线程可以被同一进程中的其他线程取消。
(3)线程调用pthread_exit。

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

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

#include <pthread.h>
int pthread_join(pthread_t thread,void **rval_ptr);
若成功,返回0;否则,返回错误编号

如果线程简单地从它的启动例程返回,rval_ptr就包含返回码。如果线程被取消,由rval_ptr指定的内存单元就设置为PTHREAD_CANCELED。
可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。
如果对线程的返回值并不感兴趣,那么可以把rval_ptr设置为NULL。在这种情况下,调用Pthread_join函数可以等待指定的线程终止,但并不获取线程的终止状态。

线程可以通过调用pthread_cancel函数来请求取消同一进程中的其他线程。

#include <pthread.h>
int pthread_cancel(pthread_t tid);
若成功,返回0;否则,返回错误编码。

注:pthread_cancel并不等待线程终止,它仅仅提出请求。

与进程在退出时可以用atexit函数安排退出类似,线程可以安排它退出时需要调用的函数(线程清理处理程序)。处理程序记录在栈中,也就是说,它们的执行顺序与它们注册时相反。

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

当线程执行以下动作时,清理函数rtn是由pthread_cleanup_push函数调度的,调用时只有一个参数arg:
- 调用pthread_exit时;
- 响应取消请求时;
- 用非零execute参数调用pthread_cleanup_pop时。
注:如果线程是通过从它的启动例程中返回而终止的话,它的清理处理程序就不会被调用。

pthread_cleanup_pop将删除上次pthread_cleanup_push调用建立的清理处理程序。

调用pthread_detach分离线程。

#include <pthread.h>
int pthread_detach(pthread_t tid);
若成功,返回0;否则,返回错误编号。

5、线程同步

当一个线程可以修改的变量,其他线程也可以读取或者修改的时候,我们就需要对这些线程进行同步,确保它们在访问变量的存储内容时不会访问到无效的值。

5.1、互斥量

互斥量从本质上说是一把锁,在访问共享资源前对互斥量进行设置(加锁),在访问完成后释放(解锁)互斥量。对互斥量进行加锁以后,任何其他视图再次对互斥量加锁的线程都会被阻塞知道当前线程释放该互斥锁。如果释放互斥量时有一个以上的线程阻塞,那么所有该锁上的阻塞线程都会编程可运行状态,第一个变为运行的线程就可以对互斥量加锁,其他线程就会看到互斥量依然是锁着的,智能回去再次等待它重新变为可用。

在使用互斥变量(pthread_mutex_t)以前,必须首先对它进行初始化,可以设置为常量PTHREAD_MUTEX_INITIALIZER或者调用pthread_mutex_init函数进行初始化。

#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);
若成功,返回0;否则,返回错误编号
#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);
若成功,返回0;否则,返回错误编号。
  • 如果互斥量已经上锁,调用线程将阻塞知道互斥量被解锁。
  • 如果线程不希望被阻塞,它可以使用pthread_mutex_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞直接返回0.

5.2、避免死锁

如果线程试图对同一个互斥量加锁两次,那么它自身就会陷入死锁状态,或两个进程都在互相请求另一个线程拥有的资源,所以这两个线程都无法向前运行,于是就产生死锁。
可以通过仔细控制互斥量加锁的顺序来避免死锁的发生。例如,在同时需要两个互斥量时,总是让它们以相同的顺序加锁,这样可以避免死锁。

5.3、函数pthread_mutex_timedlock

在达到超时时间值时,pthread_mutex_timedlock不会对互斥量进行加锁,而是返回错误码ETIMEDOUT。

#include <pthread.h>
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
                        const struct timespec *restrict tsptr);
若成功,返回0;否则,返回错误编码

注:超时指定愿意等待的绝对时间

5.4、读写锁

读写锁可以由3种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。
一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
读写锁在使用之前必须初始化,在释放它们底层的内存之前必须销毁。

#include <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);
若成功,返回0;否则,返回错误编号。

常量PTHREAD_RWLOCK_INITIALIZER可以对静态分配的读写锁进行初始化。

#include <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);
若成功,返回0;否则,返回错误编号。
#include <pthread.h>
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
若成功,返回0,否则,返回错误编号

可以获取锁时,这两个函数返回0.否则,它们返回错误EBUSY。

5.5、带有超时的读写锁

#include <pthread.h>
#include <time.h>
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock,
                          const struct timespec *restrict tsptr);        
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock,
                          const struct timespec *restrict tsptr); 
若成功,返回0;否则,返回错误编号。                                 

如果它们不能获取锁,那么超时到期时,这两个函数返回ETIMEDOUT。
注:超时指定的是绝对时间

5.6、条件变量

条件变量给多个线程提供了一个会合的场所。条件变量和互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身是由互斥量保护的。线程在改变条件状态之前必须首先锁住互斥量。
在使用条件变量之前,必须先对它进行初始化。

#inlcude <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,
            const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
若成功,返回0;否则,返回错误编号。

同理,条件变量也可由常量PTHREAD_COND_INITIALIZER初始化静态分配的条件变量。

我们使用pthread_cond_wait等待条件变量为真。如果在给定的时间内条件不能满足,那么会生成一个返回错误码的变量。

#include <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 tsptr);
若成功,返回0;否则,返回错误编号

传递给pthread_cond_wait的互斥量对条件进行保护。**调用者把锁住的互斥量传给函数,函数然后自动把调用线程放到等待条件的线程列表上,对互斥量解锁。**pthread_cond_wait返回时,互斥量再次被锁住。

对于pthread_con_timedwait,如果超时到期时条件还是没有出现,pthread_con_timedwait将重新获取互斥量,然后返回错误ETIMEDOUT。

有两个函数可以用于通知线程条件已经满足。

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);//唤醒一个等待该条件的线程
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒等待该条件的所有线程。

5.7、自旋锁

自旋锁与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等(自旋)阻塞状态。

#include <pthread.h>
int pthread_spin_init(pthread_spinlock_t *lock,int pshared);
int pthread_spin_destry(pthread_spinlock_t *lock);
若成功,返回0;否则,返回错误编码

pshared参数表示进程共享属性,表明自旋锁是如何获取的。如果它设为PTHREAD_PROCESS_SHARED,则自旋锁能被可以访问锁底层内存的线程获取,即使那些线程属于不同的进程。否则pshared参数设为PTHREAD_PROCESS_PRIVATE,自旋锁就只能初始化该锁的进程内部的线程所访问。

#include <pthread.h>
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lok);
int pthread_spin_unlock(pthread_spinlock_t *lock);
若成功,返回0;否则,返回错误编码。

pthread_spin_lock或pthread_spin_trylock对自旋锁进行加锁,前者在获取锁之前一直自旋,后者如果不能获取锁,就立即返回EBUSY错误。

5.8、屏障

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

#include <pthread.h>
int pthread_barrier_init(pthread_barrier_t *restrict barrier,
                    const pthread_barrierattr_t *restrict,attr,
                        unsigned int count);
int pthread_barrier_destroy(pthread_barrier_t *barrier);
若成功,返回0;否则,返回错误编码。

初始化屏障时,可以使用count参数指定,在允许所有线程继续运行之前,必须到达屏障的线程数目。

可以使用pthread_barrier_wait函数来表明,线程已完成工作,准备等所有其他线程赶上来。

#include <pthread.h>
int pthread_barrier_wait(pthread_barrier_t *barrier);
若成功,返回0或PTHREAD_BARRIER_SERIAL_THREAD;否则,返回错误编码

调用pthread_barrier_wait的线程在屏障计数未满足条件时,会进入休眠状态。如果该线程是最后一个调用pthread_barrier_wait的线程,就满足了屏障计数,所有的线程都被唤醒。
对于一个任意线程,pthread_barrier_wait函数返回了PTHREAD_BARRIER_SERIAL_THREAD。剩下的线程看到的返回值是0.这使得一个线程可以作为主线程,它可以工作在其他所有线程已完成的工作结果上。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值