1线程与进程
一个UNIX进程可以理解为一个线程加上地址空间、文件描述符和其他数据。线程和进程的区别在于多个线程可以共享一个地址空间,而做不同的事情。
2线程标识
头文件 | #include <pthread.h> |
函数原型 | int pthread_equal(pthread_t tid1, pthread_t tid2); |
参数 | tid1:线程标识1 tid2:线程标识2 |
返回 | 若相等则返回非0值,否则返回0 |
功能 | 对两个线程ID进行比较 |
头文件 | include <pthread.h> |
函数原型 | pthread_t pthread_self(void); |
参数 | void |
返回 | 返回调用线程的线程ID |
功能 | 获得自身线程ID |
3线程创建
头文件 | 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:线程属性 start_rtn:新创建的线程从start_rtn函数的地址开始运行 arg:start_rtn的指针参数 |
返回 | 若成功则返回0,否则返回错误编号 |
功能 | 创建线程 |
线程创建时并不能保证哪个线程会先运行:是新创建的线程还是调用线程。新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的未决信号集被清除。
4线程终止
单个线程可以通过下列三种方式退出,在不终止整个进程的情况下停止它的控制流:
(1)线程只是从启动例程中返回,返回值是线程的退出码:start_rtn中return
(2)线程可以被同一进程中的其他线程取消:pthread_cancel
(3)线程调用pthread_exit
头文件 | include <pthread.h> |
函数原型 | void pthread_exit(void *rval_ptr); |
参数 | rval_ptr:保存线程退出后返回的值 |
返回 | void |
功能 | 线程终止 |
rval_ptr是无类型指针,进程中的其他线程可以通过调用pthread_join函数访问该指针。
头文件 | include <pthread.h> |
函数原型 | int pthread_join(pthread_t thread, void **rval_ptr); |
参数 | thread:指定的线程ID rval_ptr:获取线程退出时的返回值 |
返回 | 若成功返回0,否则返回错误编码 |
功能 | 等待一个线程的结束 |
调用pthread_join后,调用线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。
可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL
头文件 | include <pthread.h> |
函数原型 | int pthread_cancel(pthread_t tid); |
参数 | tid:线程ID |
返回 | 若成功返回0,否则返回错误编码 |
功能 | 请求取消同一进程中的其他线程。注意:pthread_cancel并不等待线程终止 |
线程可以安排它退出时需要调用的函数,这样的函数称为线程清理处理程序。线程可以建立多个清理处理程序。处理程序记录在栈中,也就是说它们的执行顺序与它们注册时的顺序相反。
头文件 | include <pthread.h> |
函数原型 | void pthread_cleanup_push(void (*rtn)(void *), void *arg); void pthread_cleanup_pop(int execute); |
参数 | rtn:线程清理函数 arg:参数 execute:表示执行到pthread_cleanup_pop时是否在弹出清理函数的同时执行该函数,若execute为0,清理函数将不被执行;若为非0,则执行。 |
返回 | void |
功能 | 线程注册清理函数。Pthread_cleanup_push和pthread_cleanup_pop可以实现为宏,所以在使用时需要配对出现。 |
头文件 | include <pthread.h> |
函数原型 | int pthread_detach(pthread_t tid); |
参数 | tid:线程ID |
返回 | 若成功返回0,否则返回错误编号 |
功能 | 使线程进入分离状态 |
在默认情况下,线程的终止状态会保存到对该线程调用pthread_join,如果线程已经处于分离状态,线程的底层存储资源可以在线程终止时立即被收回。当线程被分离时,并不能用pthread_join函数等待它的终止状态。对分离状态的线程进行pthread_join的调用会产生失败,返回EINVAL.
进程原语与线程原语的比较:
进程原语 | 线程原语 | 描述 |
fork exit waitpid atexit getpid abort | pthread_create pthread_exit pthread_join pthread_cleanup_push/pop pthread_self pthread_cancel | 创建新的控制流 从现有的控制流中退出 从控制流中得到退出状态 注册在退出控制流时调用的函数 获取控制流的ID 请求控制流的非正常退出 |
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
void printids(const char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid);
return ;
}
void *thr_fn1(void *arg)
{
printids("thread 1:");
return ((void *)1); /*线程从启动例程中返回*/
}
void *thr_fn2(void *arg)
{
printids("thread 2:");
pthread_exit((void *)2); /*线程调用pthread_exit*/
}
int main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;
/*创建线程1*/
err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);
if (err != 0)
{
printf("can't create thread 1: %s\n", strerror(err));
return -1;
}
/*创建线程2*/
err = pthread_create(&tid2, NULL, thr_fn2, NULL);
if (err != 0)
{
printf("can't create thread 2: %s\n", strerror(err));
return -1;
}
/*调用线程阻塞,直到线程1退出*/
err = pthread_join(tid1, &tret);
if (err != 0)
{
printf("can't join with thread 1: %s\n", strerror(err));
return -1;
}
printf("thread 1 exit code %d\n", (int)tret);
/*调用线程阻塞,直到线程2退出*/
err = pthread_join(tid2, &tret);
if (err != 0)
{
printf("can't join with thread 1: %s\n", strerror(err));
return -1;
}
printf("thread 2 exit code %d\n", (int)tret);
printids("main thread: ");
exit(0);
}
[root]# gcc thread.c -pthread
[root]# ./a.out
thread 1: pid 2628 tid 1113676096 (0x42615940)
thread 2: pid 2628 tid 1124165952 (0x43016940)
thread 1 exit code 1
thread 2 exit code 2
main thread: pid 2628 tid 3926034592 (0xea0288a0)
[root]#
5线程同步
5.1互斥量
互斥量从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。
互斥变量的初始化:
#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_unlock(pthread_mutex_t *mutex);//解锁 int pthread_mutex_trylock(pthread_mutex_t *mutex);//尝试加锁,如果互斥量已上锁,调用线程返回失败返回EBUSY而不会阻塞 返回值:若成功返回0,否则返回错误编号 |
死锁的概念:
各并发进程互相等待对方所拥有的资源,且这些并发进程在得到对方的资源之前不会释放自己所拥有的资源。从而造成大家都想得到资源而又都得不到资源,各并发进程不能继续向前推进的状态。
死锁的分类:
【1】自死锁
如果一个执行线程试图去获得一个自己已经持有的锁,它将不得不等待锁被释放,但因为它正在忙着等待这个锁,所以自己永远也不会有机会是否锁,最终结果就是死锁。
【2】ABBA死锁
线程1持有A锁,并试图获得B锁;线程2持有B锁,并试图获得A锁。两个线程都
在相互请求另一个线程拥有的资源,所以这两个线程都无法向前运行,于是就产生死锁。
避免死锁:
1)按顺序加锁。
2)防止发生饥饿。
3)不要重复请求同一个锁。
4)设计应力求简单
/*使用两个互斥量*/
#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;/*全局互斥量,用于保护全局列表fh*/
struct foo
{
int f_count;
pthread_mutex_t f_lock; /*结构内互斥量,用于保护结构内的f_count变量*/
struct foo *f_next; /*该变量由全局互斥量hashlock保护*/
int f_id;
/*... more stuff here ...*/
};
/*分配foo对象*/
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); /*将fp的地址值转为无符号长整型再模NHASH*/
pthread_mutex_lock(&hashlock);
fp->f_next = fh[idx]; /*首次使用时,fh[idx]的值为NULL。将已经存在的fh[idx]链表链接在新节点fp之后*/
fh[idx] = fp;/*=fp非fp->f_next*/ /*新生成的fp作为fh[idx]的首节点。所有哈希值为idx的fp都挂在fh[idx]的链表上*/
pthread_mutex_lock(&fp->f_lock);
pthread_mutex_unlock(&hashlock);
/* ... continue initialization ... */
pthread_mutex_unlock(&fp->f_lock);
}
return fp;
}
/*增加foo对象的引用计数*/
void foo_hold(struct foo *fp)
{
pthread_mutex_lock(&fp->f_lock);
fp->f_count++;
pthread_mutex_unlock(&fp->f_lock);
}
/*查找已经存在的foo对象*/
struct foo *foo_find(int id)
{
struct foo *fp;
int idx;
idx = HASH(fp);
/*foo_find函数内的加锁顺序: 先hashlock锁,再fp->f_lock锁*/
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;
}
/*释放foo对象的引用*/
void foo_rele(struct foo *fp)
{
struct foo *tfp;
int idx;
pthread_mutex_lock(&fp->f_lock);
if (fp->f_count == 1)/*最后一个引用*/
{
/*fp对象在全局散列列表中,要从中删除fp对象时,需要用到hashlock锁*/
/*
为满足加锁顺序: 先hashlock后fp->f_lock,需要先将f_lock解锁。如果此处不
先解锁fp->f_lock而是直接加锁hashlock,则其他线程可能在调用foo_find已经
持有hashlock锁而在等待fp->f_lock锁。此时本线程持有fp->f_lock锁且尝试持有
hashlock锁,则可能产生死锁。故而,当有多把锁时,需要保证锁顺序一致。
*/
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_lock(&hashlock);
/*此处加锁fp->f_lock可能要等待别的线程调用foo_hold加锁增加引用计数*/
pthread_mutex_lock(&fp->f_lock);
/*重新持有fp->f_lock锁时f_count可能被别的线程增1,故而需重新检查条件,判断是否需要释放结构*/
if (fp->conut != 1)
{
fp->f_count--;
pthread_mutex_unlock(&fp->f_lock);
pthread_mutex_unlock(&hashlock);
return ;
}
/*fh[idx]的链表中移除fp*/
idx = HASH(fp);
tfp = fh[idx];
if (tfp == fp)
{
/*fp刚好是fh[idx]的首节点*/
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);
}
}
上例中用了两个互斥量,为避免死锁,遵循了按顺序加锁的规则。但如此加、解锁太过复杂。故而需要重新审视原来的设计,力求简单。如可以使用散列列表锁hashlock来保护数据结构foo中的引用计数f_count,结构互斥量f_lock用于保护foo结构中的其他成员,如此便可使事情大大简化。
5.2读写锁
读写锁与互斥量类似,不过读写锁允许更高的并行性。
读写锁也叫共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。
读写锁有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。
当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都被阻塞。
当读写锁是读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是如果线程希望以写模式对此锁进行加锁,它必须阻塞直到所有的线程释放读锁。
一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。
读写锁非常适合于对数据结构读的次数远大于写的情况。
读写锁初始化:
#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,否则返回错误编号 |
读写锁加解锁:
#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 <stdlib.h>
#include <pthread.h>
struct job
{
struct job *j_next;
struct job *j_prev;
pthread_t j_id;
/* ... more stuff here ... */
};
struct queue
{
struct job *q_head;
struct job *q_tail;
pthread_rwlock_t q_lock;
};
/*Initialize a queue*/
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;
}
/* continue initialization ... */
return 0;
}
/*Insert a job at the head of the queue*/
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);
}
/* 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;
}
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;
}
5.3条件变量
条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件。
条件变量初始化:
#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); int pthread_cond_timewait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict timeout); |
通知线程条件已满足:
#include <pthread.h> int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); |