本章主要介绍了线程,了解如何使用多线程在单进程环境中来执行多任务。由于多个线程共享其进程空间,所以必须采用同步的机制来保护数据的一致性。
一.线程的概念
典型的Unix系统都可以看成只有一个控制线程,一个进程在同一时刻只能做一件事。但有了多线程,我们可以设计成在同一时刻进程能做不止一件事,每个线程处理各自独立的任务。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。
二.线程标识
#include <pthread.h> int pthread_equal(pthread_t tid1,pthread_t tid2);//若相等则返回非0值,否则返回0 pthread_t pthread_self(void);//返回调用线程的线程ID
三、线程创建、终止与清理
int pthread_create(pthread_t *tid,const pthread_attr_t* attr, void* (*start)(void*),void* arg); //返回值:若成功则返回0,否则返回错误编号
单个线程可以通过下列三种方式退出,在不终止整个进程的情况下停止它的控制流.
- 线程只是从启动例程中返回,返回值是线程的退出码。
- 线程可以被同一进程中的其他线程取消。
- 线程调用pthread_exit。
但是这两个函数有一个限制,由于它们可以实现为宏,所以必须在线程相同的作用域中以匹配对的形式使用,pthread_cleanup_push的宏定义可包含字符{,在这种情况下对应的匹配字符}就要在pthread_cleanup_pop定义中出现。
int pthread_join(pthread_t tid,void ** ptr); //若成功则返回0,否则返回错误编号
调用线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或被取消。ptr将包含返回码。
如果线程采用pthread_self函数使其处于分离状态,pthread_join调用就会失败.返回EINVAL.
void pthread_cancel(pthread_t tid);//成功返回0,否则返回错误编号
线程可以安排它退出时的需要调用的函数,这与进程可以用atexit函数安排进程退出时需要调用的函数类似。线程可以建立多个清理处理程序,处理程序在栈中,也就是说他们的顺序与它们注册的顺序相反。
void pthread_cleanup_push(void (*rtn)(void*),void *arg); void pthread_cleanup_pop(int execute);
当线程执行以下动作时调用清理函数,调用参数为arg.
- 调用pthread_exit时
- 响应取消请求时
- 用非零execute参数调用pthread_cleanup_pop时
如果execute参数置为0,清理函数将不被调用。
四.线程同步
当多个线程共享相同内存时,为了保持数据共享的一致性,我们采取线程同步的机制。线程同步有四种方法:
1.互斥量
可以通过使用pthread的互斥接口保护数据,确保同一时间只有一个线程访问数据.互斥量(mutex)从本质上说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr); int pthread_mutex_destroy(pthread_mutex_t *mutex); 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_trylock尝试对互斥量进行加锁。如果调用pthread_mutex_trylock时互斥量处于未锁住的状态,那么pthread_mutex_trylock将锁住互斥量,不会出现阻塞并返回0.否则pthread_mutex_trylock就会失败,不能锁住互斥量,而返回EBUSY.
2.读写锁
读写锁与互斥锁类似,不过读写锁允许更高的并行性.读写锁有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
当读写锁时写加锁状态时,在这个锁被解锁前,所有试图对这个锁加锁的线程都会被阻塞。当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞到所有的线程释放读锁。
读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。
#include <pthread.h> int pthread_rwlock_init(pthread_rwlock_t *rwlock,const pthread_rwlockaddr_t* attr); int pthread_rwlock_destroy(pthread_rwlock_t* rwlock); //成功时返回0,否则返回错误编号 int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
3.条件变量
条件变量给多个线程提供了一个会合的场所,条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身是由互斥量保护的。线程在改变条件状态时必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁住互斥量以后才能计算条件。
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *attr); int pthread_cond_destroy(pthread_cond_t *cond); int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex); int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex_t *mutex, struct timespec* timeout); int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); //成功返回0,失败则返回错误编号
#include <pthread.h>struct msg* workq; pthread_cond_t qready=PTHREAD_COND_INITIALIZER; pthread_mutex_t qlock=PTHREAD_MUTEX_INITIALIZER; void process_msg(void){ struct msg* mp; for(;;){ pthread_mutex_lock(&qlock); while(qlock==NULL){ pthread_cond_wait(&qready,&qlock); } mp=workq; workq=mp->m_next; pthread_mutex_unlock(&qlock); } } void enqueue_msg(struct msg* mp){ pthread_mutex_lock(&qlock); mp->m_next=workq; workq=mp; pthread_mutex_unlock(&qlock); pthread_cond_signal(&qready); }