Makefile中添加
EXTRALIBS = -pthread
打印线程ID
#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(); /* 获取自己的线程ID */
printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid,
(unsigned int)tid, (unsigned int)tid);
}
void *thr_fn(void *args)
{
printids("new thread: ");
return ((void *)0);
}
int main(void)
{
int err;
/*
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(start_rtn)(void), void *restrict arg);
返回:0--成功, 错误编号--失败。
tidp:保存新创建线程的线程ID
attr:定制线程属性,NULL表示创建默认属性的线程
start_rtn:线程要运行的函数
arg:start_rtn的参数,当参数不止一个时,放入一个结构中
*/
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);
}
两线程的进程ID相同,但线程ID不同。
获得线程退出状态
#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");
/*
void pthread_exit(void *rval_ptr);
pthread_join可访问rval_ptr
*/
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));
/*
int pthread_join(pthread_t thread, void **rval_ptr);
返回:0--成功, 错误编号--失败。
调用线程一直阻塞,直到指定线程调用pthread_exit,从启动例程中返回或被取消。
如果线程只是从它的启动例程返回,rval_ptr将包含返回码
如果线程被取消,由rval_ptr指定的内存单元就设置为PTHREAD_CANCELED
如果对线程返回值不感兴趣,可设置为NULL
*/
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);
exit(0);
}
可见,当线程调用pthread_exit或从启动例程返回时,其他线程可通过pthread_join获得其退出状态。
pthread_exit参数的不正确使用
将自动变量(分配在栈上)作为pthread_exit的参数时出问题。
pthread_exit参数所使用的内存在调用者完成调用后必须仍然是有效的,否则会出现无线或非法内存访问。
如果线程在自己的栈上分配了一个结构然后把指向该结构的指针传给pthread_exit,当调用pthread_join的线程试图使用该结构时,这个栈有可能已经被撤销,这块内存也另作它用。
为解决这个问题,可使用全局结构,或者调用malloc函数分配结构。
#include "apue.h"
#include <pthread.h>
struct foo
{
int a, b, c, d;
};
void printfoo(const char *s, const struct foo *fp)
{
printf(s);
printf(" structure at 0x%x\n", (unsigned)fp);
printf(" foo.a = %d\n", fp->a);
printf(" foo.b = %d\n", fp->b);
printf(" foo.c = %d\n", fp->c);
printf(" foo.d = %d\n", fp->d);
}
void *thr_fn1(void *arg)
{
/* 在自己的栈上分配了一个结构然后把指向该结构的指针传给pthread_exit */
struct foo foo = {1, 2, 3, 4};
printfoo("thread 1:\n", &foo);
pthread_exit((void *)&foo);
}
void *thr_fn2(void *arg)
{
printf("thread 2: ID is %d\n", pthread_self());
pthread_exit((void *)0);
}
int main(void)
{
int err;
pthread_t tid1, tid2;
struct foo *fp;
err = pthread_create(&tid1, NULL, thr_fn1, NULL);
if(err != 0)
err_quit("can't create thread 1: %s\n", strerror(err));
err = pthread_join(tid1, (void*)&fp);
if(err != 0)
err_quit("can't join with thread 1: %s\n", strerror(err));
sleep(1);
printf("parent starting second thread\n");
err = pthread_create(&tid2, NULL, thr_fn2, NULL);
if(err != 0)
err_quit("can't create thread 2: %s\n", strerror(err));
sleep(1);
printfoo("parent:\n", fp);
exit(0);
}
第二个线程的栈会覆盖第一个线程的栈。
线程清理处理程序
#include "apue.h"
#include <pthread.h>
void cleanup(void *arg)
{
printf("cleanup: %s\n", (char *)arg);
}
void *thr_fn1(void *arg)
{
printf("thread 1 start\n");
/*
void pthread_cleanup_push(void (*trn)(void *), void *arg);
rtn:线程退出时要调用的函数
arg: rtn的参数
当发生以下情况时,调用清理函数:
1、调用pthread_exit
2、相应取消请求
3、用非0参数调用pthread_cleanup_pop
清理处理函数顺序与安装顺序相反
*/
pthread_cleanup_push(cleanup, "thread 1 first handler");
pthread_cleanup_push(cleanup, "thread 2 second handler");
printf("thread 1 push complete\n");
if(arg)
return ((void *)1);
/*
void pthread_cleanup_pop(int execute);
删除上次pthread_cleanup_push调用建立的清理处理程序
*/
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return ((void *)1);
}
void *thr_fn2(void *arg)
{
printf("thread 2 start\n");
pthread_cleanup_push(cleanup, "thread 2 first handler");
pthread_cleanup_push(cleanup, "thread 2 second handler");
printf("thread 2 push complete\n");
if(arg)
pthread_exit((void*)2);
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
pthread_exit((void*)2);
}
int main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;
err = pthread_create(&tid1, NULL, thr_fn1, (void*)1);
if(err != 0)
err_quit("can't create therad 1: %s\n", strerror(err));
err = pthread_create(&tid2, NULL, thr_fn2, (void*)2);
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);
exit(0);
}
只有第二个线程的调用了清理函数,所以如果线程从启动例程中返回,则清理函数不被调用。
使用互斥量保护数据结构
#include <stdlib.h>
#include <pthread.h>
struct foo
{
int f_count;
pthread_mutex_t f_lock;
};
/* 将引用计数初始化为1时不需要加锁,因为分配线程时唯一引用该对象的线程 */
struct foo *foo_alloc(void)
{
struct foo *fp;
if((fp = malloc(sizeof(struct foo))) != NULL)
{
fp->f_count = 1;
/*
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutex_t *restrict attr);
attr:互斥锁的属性,NULL表示默认属性。
*/
if(pthread_mutex_init(&fp->f_lock, NULL) != 0)
{
free(fp);
return (NULL);
}
}
return (fp);
}
/* 在使用对象前,需对该对象的引用计数加1 */
void foo_hold(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}
/* 当对象使用完毕时,需对引用计数减1。
当最后一个引用被释放时,对象所占的内存空间被释放。*/
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);
}
使用两个互斥量
当同时需要两个互斥量时,总是让它们以相同的顺序加锁,可避免死锁。
第一个互斥量保护包含foo结构的包含散列表,第二个互斥量保护foo
#include <stdlib.h>
#include <pthread.h>
#define NHASH 29
#define HASH(fp) (((unsigned long)fp)%NHASH)
struct foo *fh[NHASH];
pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;
struct foo
{
int f_count;
pthread_mutex_t f_lock;
struct foo *f_next;
int f_id;
/* ... more stuff here */
};
/* allocate the object
先锁住散列列表锁,然后将新的结构加入散列列表。
在解锁散列列表锁前,先锁住新结构中的互斥量,完成初始化,防止其他线程改变该新结构。*/
struct foo *foo_alloc(void)
{
struct foo *fp;
int idx;
if((fp = malloc(sizeof(struct foo))) != NULL)
{
fp->f_count = 1;
if(pthread_mutex_init(&fp->f_lock, NULL) != 0)
{
free(fp);
return (NULL);
}
idx = HASH(fp);
pthread_mutex_lock(&hashlock);
fp->f_next = fh[idx];
fh[idx] = fp->f_next;
pthread_mutex_lock(&fp->f_lock);
/* continue initialization ... */
pthread_mutex_unlock(&fp->f_lock);
}
return (fp);
}
/* add a reference to the object */
void foo_hold(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}
/* find an existing object
先锁住散列列表锁,然后锁定foo结构中的互斥量,并将引用计数+1 */
struct foo *foo_find(int id)
{
struct foo *fp;
int idx;
idx = HASH(fp);
pthread_mutex_lock(&hashlock);
for(fp=fh[idx]; fp != NULL; fp = fp->f_next)
{
if(fp->f_id == id)
{
foo_hold(fp);
break;
}
}
pthread_mutex_unlock(&hashlock);
return (fp);
}
/* release a reference to the object
如果是最后一个引用,需从散列表中删除该结构,则先对结构互斥量解锁,
然后才能获得散列表锁。然后重新获取结构互斥量。
从上一次获得结构互斥量后可能处于被阻塞状态,需重新检查条件,
判断是否还需要释放该结构。在我们为满足锁顺序而阻塞时,其他线程发现
了该结构并对其引用计数加1,则只需简单地对引用计数减一,然后对所有东西
解锁后返回。*/
void foo_rele(struct foo *fp)
{
struct foo *tfp;
int idx;
pthread_mutex_lock(&fp->f_lock);
if(fp->f_count == 1) /* last reference */
{
pthread_mutex_unlock(&fp->f_lock);
pthread_lock(&hashlock);
pthread_lock(&fp->f_lock);
/* need to recheck the condition */
if(fp->f_count != 1)
{
fp->f_count--;
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_unlock(&hashlock);
return;
}
/* remove from list */
idx = HASH(fp);
tfp = fh[idx];
if(tfp == fp)
fh[idx] = fp->f_next;
else
{
while(tfp->f_next != fp)
tfp = tfp->f_next;
tfp->f_next = fp->f_next;
}
pthread_mutex_unlock(&hashlock);
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
}
else
{
fp->f_count--;
pthread_mutex_unlock(&fp->f_lock);
}
}
简化的加锁、减锁
用散列表锁保护结构引用计数,可简化。结构互斥量保护foo中的其他东西。
两种用途使用相同的锁时,围绕散列表和引用计数锁的排序问题就随之不见了。
#include <stdlib.h>
#include <pthread.h>
#define NHASH 29
#define HASH(fp) (((unsigned long)fp)%NHASH)
struct foo *fh[NHASH];
pthread_mutex_t hashlock = PTHREAD_MUTEX_INITIALIZER;
struct foo
{
int f_count; /* protected by hashlock */
pthread_mutex_t f_lock;
struct foo *f_next; /* proctected by hashlock */
int f_id;
/* ... more stuff here ... */
};
struct foo *foo_alloc(void)
{
struct foo *fp;
int idx;
if((fp = malloc(sizeof(struct foo))) != NULL)
{
fp->f_count = 1;
if(pthread_mutex_init(&fp->f_lock, NULL) != 0)
{
free(fp);
return (NULL);
}
idx = HASH(fp);
pthread_mutex_lock(&hashlock);
fp->f_next = fh[idx];
fh[idx] = fp->f_next;
pthread_mutex_lock(&fp->f_lock);
pthread_mutex_unlock(&hashlock);
/* ... continue initialization ... */
}
return (fp);
}
void foo_hold(struct foo *fp)
{
pthread_mutex_lock(&hashlock);
fp->f_count++;
pthread_mutex_unlock(&hashlock);
}
struct foo *foo_find(int id)
{
struct foo *fp;
int idx;
idx = HASH(fp);
pthread_mutex_lock(&hashlock);
for(fp = fh[idx]; fp != NULL; fp = fp->f_next)
{
if(fp->f_id == id)
{
fp->f_count++;
break;
}
}
pthread_mutex_unlock(&hashlock);
return (fp);
}
void foo_rele(struct foo *fp)
{
struct foo *tfp;
int idx;
pthread_mutex_lock(&hashlock);
if(--fp->f_count == 0)
{
idx = HASH(fp);
tfp = fh[idx];
if(tfp == fp)
{
fh[idx] = fp->f_next;
}
else
{
while(tfp->f_next != fp)
tfp = tfp->f_next;
tfp->f_next = fp->f_next;
}
pthread_mutex_unlock(&hashlock);
pthread_mutex_destroy(&fp->f_lock);
free(fp);
}
else
{
pthread_mutex_unlock(&hashlock);
}
}
使用读写锁
写锁时,所有试图加锁的线程都会被阻塞。
读锁时,试图获取读锁的线程可访问,而写锁的线程必须阻塞,直到所有线程释放读锁。
读锁时,如果有线程试图获取写锁,会阻塞随后的读锁请求,可避免写线程饥饿。
从队列中删除或增加作业时,用写锁模式的读写锁;搜索队列时,用读锁模式的读写锁。
工作线程只能取队列中相应ID的作业,所以不需要额外的锁保护作业结构。
#include <stdlib.h>
#include <pthread.h>
struct job
{
struct job *j_next;
struct job *j_prev;
pthread_t j_id; /* tells which thread handles this job */
/* ... more stuff here */
};
struct queue
{
struct job *q_head;
struct job *q_tail;
pthread_rwlock_t q_lock;
};
/* Initialize a queue */
int queue_int(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);
/* ... continue initialization ... */
return (0);
}
/* Insert a job at the head of the queue 写锁 */
void job_insert(struct queue *qp, struct job *jp)
{
pthread_rw_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; /* list was empty */
qp->q_head = jp;
pthread_rwlock_unlock(&qp->q_lock);
}
/* Append a job on the tail of the queue 写锁 */
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; /* list was empty */
qp->q_tail = jp;
pthread_rwlock_unlock(&qp->q_lock);
}
/* Remove the given job from a queue 写锁 */
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 = jp->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);
}
/* Find a job for the given thread ID 读锁 */
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);
}
使用条件变量
使用条件变量和互斥量对线程同步
#include <pthread.h>
struct msg
{
struct msg *m_next;
/* ... more stuff here ... */
};
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(workq == NULL)
{
/*
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
互斥量mutex对条件进行保护,调用者把锁住的互斥量传给函数。
函数把调用线程放到等待条件的线程列表上,然后对互斥量解锁,这两个操作是原子操作。
pthread_cond_wait返回时,互斥量再次被锁住。
消息队列无消息,等待。
*/
pthread_cond_wait(&qready, &qlock);
}
mp = workq;
workq = mp->m_next;
pthread_mutex_unlock(&qlock);
/* now process the message mp */
}
}
void enqueu_msg(struct msg *mp)
{
pthread_mutex_lock(&qlock);
mp->m_next = workq;
workq = mp;
pthread_mutex_unlock(&qlock);
/*
int pthread_cond_signal(pthread_cond_t *cond);
改变条件状态后,向线程发送信号
workq != NULL,产生了消息,唤醒线程
*/
pthread_cond_signal(&qready);
}