一个进程中的所有线程都可以访问该进程的组成部件。用同步机制防止多个线程查看到不一致的共享资源。
11.2线程概念
线程包含了表示进程内执行环境必须的信息,其中包括进程中标识线程的线程ID、一组寄存器、栈、调度优先级和策略、信号屏蔽字
、errno变量以及线程的私有数据。
进程的所有信息对该进程的所有线程都是共享的。
线程ID的数据类型是pthread_t数据类型,可以用下面的函数比较两个线程ID。
int pthread_equal( pthread_t tid1, pthread_t tid2);
线程可以通过调用pthread_self函数获得自身的线程ID。
#include <pthread.h>
pthread_t pthread_self( void )
11.4 线程创建
在传统UNIX进程模型中,一个进程只有一个控制线程。我们可以通过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)
当成功返回后,tidp指向的内存单元被设置为新创建线程的线程ID。
attr参数用于保存不同的线程属性。
下面的实例打印进程ID、新的线程ID、初始线程ID:
[root@localhost apue]# gcc page290.c -l pthread
[root@localhost apue]# ./a.out
main thread: pid 1696 tid 3086464704 (oxb7f7b6c0)
new thread: pid 1696 tid 3086461840 (oxb7f7ab90)
[root@localhost apue]# cat page290.c
#include "apue.h"
#include <pthread.h>
pthread_t ntid;
void printids(const char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid %u tid %u (ox%x)\n", s, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid);
}
void * thr_fn(void *arg)
{
printids("new thread: ");
return((void *)0);
}
int main(void)
{
int err;
err = pthread_create(&ntid, NULL,thr_fn, NULL);
if(err != 0)
err_quit("can't create thread:%s\n",strerror(err));
printids("main thread:");
sleep(1);
exit(0);
}
11.5线程终止
如果进程中任一线程调用exit、_Exit、_exit,那么整个进程就会终止。
线程可以调用pthread_exit来终止:
#include <pthread.h>
void pthread_exit(void *rual_ptr);
这个函数的参数是值是返回给启动例程的。
其他线程可以通过pthread_join来访问上面的参数的指针。
int pthread_join(pthread_t thread, void **rual_ptr)
[root@localhost apue]# gcc page292.c -l pthread
[root@localhost apue]# ./a.out
thread 1 returning
thread 2 exiting
thread 1 exit code 1
thread 2 exit code 2
[root@localhost apue]# cat page292.c
#include "apue.h"
#include <pthread.h>
void * thr_fn1(void *arg)
{
printf("thread 1 returning\n");
return ((void *)1);
}
void * thr_fn2(void *arg)
{
printf("thread 2 exiting\n");
pthread_exit((void *)2);
}
int main(void)
{
int err;
pthread_t tid1,tid2;
void *tret;
err = pthread_create(&tid1, NULL, thr_fn1,NULL);
if(err != 0)
err_quit("can't create thread 1:%s\n", strerror(err));
err = pthread_create(&tid2, NULL, thr_fn2,NULL);
if(err != 0)
err_quit("can't create thread 2:%s\n", strerror(err));
err = pthread_join(tid1, &tret);
if(err != 0)
err_quit("can't join with thread 1: %s\n", strerror(err));
printf("thread 1 exit code %d\n", (int)tret);
err = pthread_join(tid2, &tret);
if(err != 0)
err_quit("can't join with thread 2: %s\n", strerror(err));
printf("thread 2 exit code %d\n", (int)tret);
return 0;
}
可以通过下面函数来请求取消进程中的其他线程:
int pthread_cancle(pthread_t tid)
可以给线程安排线程清理处理程序:
#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *), void *arg)
void pthread_cleanup_pop(int execute)
可以用下面的函数让线程进入分离状态:
int pthread_detach( pthread_t tid)
如果一个线程没有进入分离状态,则可以用pthread_join来保存线程的终止状态。
11.6线程同步
线程同步可以通过互斥量、读写锁、条件变量来实现线程之间的同步机制。
当一个线程可以修改变量,而其他线程也可以读取或者修改这个变量的时候,就需要线程同步以确保他们访问的变量
是有效的值。
互斥量:本质上是一把锁,在访问共享资源前对互斥量进行加锁,在访问完以后释放互斥量上的锁。
互斥变量可以用pthread_mutex_t数据类型来表示,在使用互斥变量以前,必须首先对它进行初始化。
可以用下面两个函数对动态初始化的互斥变量初始化和释放:
#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)
如果使用默认的属性初始化互斥量,只需把attr变量设置为NULL。
可以用下面三个函数对互斥量加锁和解锁:
#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)
第二个和第一个的区别是:第二个如果不能上锁,不进入阻塞状态,第一个进入阻塞状态直到能上锁。
[root@localhost apue]# cat page300.c
#include <stdlib.h>
#include <pthread.h>
struct foo{
int f_count;
pthread_mutex_t f_lock;
};
struct foo * foo_alloc(void)
{
struct foo *fp;
if ((fp = malloc(sizeof(struct foo))) != NULL)
{
fp->f_count = 1;
if (pthread_mutex_init(&fp->f_lock, NULL) != 0)
{
free(fp);
return(NULL);
}
}
return (fp);
}
void foo_hold(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}
void foo_rele(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
if (--fp->f_count == 0)
{
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
}else
{
pthread_mutex_unlock(&fp->f_lock);
}
}
读写锁和互斥量类似,不过读写锁允许更高的并发性。
当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞。
读写锁非常适合对数据结构的读次数远大于写的情况。
可以用下面的函数用于读写锁的初始化和释放:
#include <phtread.h>
int pthread_rwlock_init( pthread_rwlock_t *restrict rwlock)
int pthread_rwlock_destroy( pthread_rwlock_t *rwlock)
可以用下面的函数用于读写锁的加锁和解锁:
#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)
[root@localhost apue]# cat page307.c
#include <stdlib.h>
#include <pthread.h>
struct job{
struct job *j_next;
struct job *j_prev;
pthread_t j_id;
};
struct queue{
struct job *q_head;
struct job *q_tail;
pthread_rwlock_t q_lock;
};
int queue_init(struct queue *qp)
{
int err;
qp->q_head = NULL;
qp->q_tail = NULL;
err = pthread_rwlock_init(&qp->q_lock, NULL);
if(err != 0)
return (err);
return (0);
}
void job_insert(struct queue *qp, struct job *jp)
{
pthread_rwlock_wrlock(&qp->q_lock);
jp->j_next = qp->q_head;
jp->j_prev = NULL;
if (qp->q_head != NULL)
qp->q_head->j_prev = jp;
else
qp->q_tail = jp;
qp->q_head = jp;
pthread_rwlock_unlock(&qp->q_lock);
}
void job_append(struct queue *qp, struct job *jp)
{
pthread_rwlock_wrlock(&qp->q_lock);
jp->j_next = NULL;
jp->j_prev = qp->q_tail;
if (qp->q_tail != NULL)
qp->q_tail->j_next =jp;
else
qp->q_head = jp;
qp->q_tail =jp;
pthread_rwlock_unlock("&qp->q_lock");
}
void job_remove(struct queue *qp, struct job *jp)
{
pthread_rwlock_wrlock(&qp->q_lock);
if(jp == qp->q_head)
{
qp->q_head = jp->j_next;
if (qp->q_tail == jp)
qp->q_tail = NULL;
}else if (jp == qp->q_tail)
{
qp->q_tail = qp->j_prev;
if (qp->q_head == jp)
qp->q_head == NULL;
}else
{
jp->j_prev->j_next = jp->j_next;
jp->j_next->j_prev = jp->j_prev;
}
pthread_rwlock_unlock(&qp->q_lock);
}
struct job * job_find(struct queue *qp, pthread_t id)
{
struct job *jp;
if(pthread_rwlock_rdlock(&qp->q_lock) != 0)
return (NULL);
for(jp = qp->q_head; jp != NULL; jp = jp->j_next)
if(pthread_equal(jp->j_id, id))
break;
pthread_rwlock_unlock(&qp->q_lock);
return (jp);
}
条件变量时线程可用的另一种同步机制,条件变量和互斥量一起使用,运行线程以无竞争的方式等待特定的条件发生。
条件变量在使用前必须用pthread_cond_init初始化.
#include <pthread.h>
int pthread_cond_init( pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);
int pthread_cond_destroy( pthread_cond_t *cond)
用下面的函数等待条件变量为真,如果在给定的时间内不嫩满足,生成一个代表出错码的返回变量。
#include <pthread.h>
int pthread_cond_wait( pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex)
函数中的第二个参数是一个互斥量,对条件进行保护。