函数 pthread_mutex_timelock
可以给线程指定一个超时时间。 当试图获得一个已经加锁的互斥量时,阻塞等待达到超时时间后返回错误码 ETIMEOUT
读写锁
与互斥量相似,但是允许更高的并行性
互斥量: 要么是锁住,要么没锁住,而且只有一个线程可以对其加锁
读写锁: 有3种状态,即 读模式下的加锁状态, 写模式下的加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是有多个线程可以同时占有读模式的读写锁
可能各系统具体实现不同。但当读写锁处于读模式锁的状态,这时有一个线程试图以写模式获取锁时,读写锁通常会阻塞随后的读模式锁请求。因为这样就可以避免读模式锁长期占用,而等待的写模式锁长期得不到满足。
读写锁在使用前必须 pthread_rwlock_init(*rwlock, *attr)
在释放内存前必须 pthread_rwlock_destroy(*rwlock)
在读模式锁定读写锁: pthread_rwlock_rdlock(*rwlock);
写 : pthread_rwlock_wrlock(*rwlock);
不管如何锁的,用此来解锁: pthread_rwlock_unlock(*rwlock);
以下是利用锁来保护队列,多个工作线程获取主线程分配给它们的作业:
整个过程也只是简单的对读写锁操作,并不复杂,权当是对队列的复习了
带有超时的读写锁
pthread_rwlock_timedrdlock(*rwlock, struct timespec *tsptr);
pthread_rwlock_timedwrlock(*rwlock, struct timespec *tsptr);
避免陷入永久的阻塞状态。
条件变量
是另一种同步机制, 给多个线程提供了一个会合的场所。
条件变量与互斥变量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身是互斥量保护的。【线程在改变条件状态之前必须首先锁住互斥量。】
若是静态的,初始化可以用 PTHREAD_COND_INITIALIZER
若是动态,则需要使用 pthread_cond_init函数对其初始化,并且在释放之前用 pthread_cond_destroy对其进行反初始化
函数 pthread_cond_init(*cond, *attr);
pthread_cond_destroy(*cond);
函数 pthread_cond_wait(*cond, *mutex)
pthread_cond_timedwait(*cond, *mutex,struct timespec *tsptr)
等待条件变为真。
这里注意一下,以上的每一个超时函数,最后一个参数指的是一个绝对数。即需要把当前时间加上你所期盼的超时时间再转换为 tiemspev结构。
这里提供一个函数:
函数 pthread_cond_signal(*cond);
至少能唤醒一个等待该条件的线程
pthread_cond_broadcast(*cond);
唤醒等待该条件的所有线程
结果为:
上面这个程序虽然不是本书上的,但我觉得更适合放在这里。 程序的大意是用两个线程数出两个文件中的单词数量,最后汇总。
可以看到,这里使用一个mailbox变量作为两个线程的最终归属,即通过条件变量最后都会合到主线程。
屏障:
是用户协调多个线程并行工作的同步机制。
屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后从该点继续执行。
pthread_join 就是一种屏障,允许一个线程等待,直到另一个线程退出。
但是屏障对象的概念更广,它们允许任意数量的线程等待,直到所有的线程完成处理工作,而线程不需要退出。所有线程达到屏障后可以接着工作
函数 pthread_barrier_init(pthread_barrier_t *barrier, *attr, int count)
对屏障进行初始化
函数 pthread_barrier_destroy(*barrier)
反初始化
函数 pthread_barrier_wait(*barrier)
函数可用来表明线程已完成工作,准备等待所有其他线程赶上来。调用此函数的线程在屏障计数(即init中的count)未满足条件时,会进入休眠状态。 如果该线程是最后一个调用此函数的线程,就满足了屏障计数,所有线程都被唤醒。
一旦达到屏障计数值,而且线程处于非阻塞状态,屏障可以被重用。
书上举了个例子:
1、大致是对一个数组进行排序,且数组的长度为8000000.
2、主函数先调用 pthread_barrier_init(*barrier, 9);表明要等待9个线程(主函数加其他8个)
3、主函数创建8个线程,分别对1000000个数据进行排序。
4、每个线程的启动例程返回前都调用 pthread_barrier_wait 函数,主函数在创建完8个线程后也调用此函数。
5、等大家都结束后,主函数继续调用 merge函数将所有数据整合到一起。
自旋锁
与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等阻塞状态。
可以给线程指定一个超时时间。 当试图获得一个已经加锁的互斥量时,阻塞等待达到超时时间后返回错误码 ETIMEOUT
读写锁
与互斥量相似,但是允许更高的并行性
互斥量: 要么是锁住,要么没锁住,而且只有一个线程可以对其加锁
读写锁: 有3种状态,即 读模式下的加锁状态, 写模式下的加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是有多个线程可以同时占有读模式的读写锁
可能各系统具体实现不同。但当读写锁处于读模式锁的状态,这时有一个线程试图以写模式获取锁时,读写锁通常会阻塞随后的读模式锁请求。因为这样就可以避免读模式锁长期占用,而等待的写模式锁长期得不到满足。
读写锁在使用前必须 pthread_rwlock_init(*rwlock, *attr)
在释放内存前必须 pthread_rwlock_destroy(*rwlock)
在读模式锁定读写锁: pthread_rwlock_rdlock(*rwlock);
写 : pthread_rwlock_wrlock(*rwlock);
不管如何锁的,用此来解锁: pthread_rwlock_unlock(*rwlock);
以下是利用锁来保护队列,多个工作线程获取主线程分配给它们的作业:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
struct job{
struct job *j_prev;
struct job *j_next;
pthread_t j_id; // used to tell which thread handles this job
//job details
};
struct queue{
struct job *q_head;
struct job *q_tail;
pthread_rwlock_t q_lock;
};
int queue_init(struct queue *q)
{
q->q_head = q->q_tail = NULL;
if(pthread_rwlock_init(&q->q_lock,NULL) != 0)
return -1;
return 0;
}
//insert this at the head of the queue
void queue_insert(struct queue *q, struct job *j)
{
pthread_rwlock_wrlock(&q->q_lock);
j->j_prev = NULL;
j->j_next = q->q_head;
if(q->q_head != NULL)
q->q_head->j_prev = j;
else
q->q_tail = j;
q->q_head = j;
pthread_rwlock_unlock(&q->q_lock);
}
//insert this at the end of the queue
void queue_append(struct queue *q, struct job *j)
{
pthread_rwlock_wrlock(&q->q_lock);
j->j_next = NULL;
j->j_prev = q->q_tail;
if(q->q_tail != NULL)
q->q_tail->j_next = j;
else
q->q_head = j;
q->q_tail = j;
pthread_rwlock_unlock(&q->q_lock);
}
//remove some node from the queue
void queue_remove(struct queue *q, struct job *j)
{
pthread_rwlock_wrlock(&q->q_lock);
if(j == q->q_head)
{
q->q_head = j->j_next;
if(j == q->q_tail)
q->q_tail = NULL;
else
q->q_head->j_prev = NULL;
}
else if(j == q->q_tail)
{
q->q_tail = j->j_prev;
q->q_tail->j_next = NULL;
}
else
{
j->j_prev->j_next = j->j_next;
j->j_next->j_prev = j->j_prev;
}
pthread_rwlock_unlock(&q->q_lock);
}
//find a job
struct job *queue_find(struct queue *q, pthread_t id)
{
if(pthread_rwlock_rdlock(&q->q_lock) != 0) //可能会因为被一个在等待的对写锁的请求而拒绝这个读锁请求
return NULL;
struct job *temp;
for(temp=q->q_head; temp != NULL; temp=temp->j_next)
if(temp->j_id == id)
break;
pthread_rwlock_unlock(&q->q_lock);
return temp;
}
整个过程也只是简单的对读写锁操作,并不复杂,权当是对队列的复习了
带有超时的读写锁
pthread_rwlock_timedrdlock(*rwlock, struct timespec *tsptr);
pthread_rwlock_timedwrlock(*rwlock, struct timespec *tsptr);
避免陷入永久的阻塞状态。
条件变量
是另一种同步机制, 给多个线程提供了一个会合的场所。
条件变量与互斥变量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身是互斥量保护的。【线程在改变条件状态之前必须首先锁住互斥量。】
若是静态的,初始化可以用 PTHREAD_COND_INITIALIZER
若是动态,则需要使用 pthread_cond_init函数对其初始化,并且在释放之前用 pthread_cond_destroy对其进行反初始化
函数 pthread_cond_init(*cond, *attr);
pthread_cond_destroy(*cond);
函数 pthread_cond_wait(*cond, *mutex)
pthread_cond_timedwait(*cond, *mutex,struct timespec *tsptr)
等待条件变为真。
传递给pthread_cond_wait的互斥量对条件进行保护。调用者把锁住的互斥量传给函数,函数然后自动把调用线程放到等待条件的的线程列表上,对互斥量解锁,这就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的时间通道,这样线程就不会错过条件的任何变化。 pthread_cond_wait返回时,互斥锁再次被锁住。
这里补充之前所学内容:
使用条件变量。 即 pthread_cond_wait(flag,mutex)使线程挂起直到另一个线程通过条件变量发出消息。该函数总是和互斥锁放在一起使用。
Step 1 ---此函数先自动释放指定的锁(!!!)
Step 2 ---然后等待条件变量的变化(处于阻塞状态) 如果在调用此函数之前锁mutex没有被锁住,函数执行的结果是不确定的。
Step 3 ---在返回原调用函数之前,此函数自动将指定的互斥量重新锁住(!!!)。
这里注意一下,以上的每一个超时函数,最后一个参数指的是一个绝对数。即需要把当前时间加上你所期盼的超时时间再转换为 tiemspev结构。
这里提供一个函数:
void maketimeout(struct timespec*tsp, int sec)
{
struct timeval now;
gettimeofday(&now, NULL);
tsp->tv_sec = now.tv_sec;
tsp->tv_nsec = npw.tv_usec * 1000;
tsp->tv_sec += sec;
}
函数 pthread_cond_signal(*cond);
至少能唤醒一个等待该条件的线程
pthread_cond_broadcast(*cond);
唤醒等待该条件的所有线程
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
struct arg_set { /* two values in one arg*/
int number;
char *fname; /* file to examine */
int count; /* number of words */
};
struct arg_set *mailbox = NULL;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t flag = PTHREAD_COND_INITIALIZER;
main(int ac, char *av[])
{
pthread_t t1, t2; /* two threads */
struct arg_set args1, args2; /* two argsets */
void *count_words(void *);
int reports_in = 0;
int total_words = 0;
if ( ac != 3 ){
printf("usage: %s file1 file2\n", av[0]);
exit(1);
}
pthread_mutex_lock(&lock); /* lock the report box now */
args1.fname = av[1];
args1.count = 0;
args1.number = 1;
pthread_create(&t1, NULL, count_words, (void *) &args1);
args2.fname = av[2];
args2.count = 0;
args2.number = 2;
pthread_create(&t2, NULL, count_words, (void *) &args2);
while( reports_in < 2 ){
printf("MAIN: waiting for flag to go up\n");
pthread_cond_wait(&flag, &lock); /* wait for notify */
printf("MAIN: Wow! flag was raised, I have the lock\n");
printf("%7d: %s\n", mailbox->count, mailbox->fname);
total_words += mailbox->count;
if ( mailbox == &args1)
pthread_join(t1,NULL);
if ( mailbox == &args2)
pthread_join(t2,NULL);
mailbox = NULL;
pthread_cond_signal(&flag); /* announce state change */
reports_in++;
}
printf("%7d: total words\n", total_words);
}
void *count_words(void *a)
{
struct arg_set *args = a; /* cast arg back to correct type */
FILE *fp;
int c, prevc = '\0';
if ( (fp = fopen(args->fname, "r")) != NULL ){
while( ( c = getc(fp)) != EOF ){
if ( !isalnum(c) && isalnum(prevc) )
args->count++;
prevc = c;
}
fclose(fp);
} else
perror(args->fname);
printf("%d COUNT: waiting to get lock\n",args->number);
pthread_mutex_lock(&lock); /* get the mailbox */
printf("%d COUNT: have lock, storing data\n",args->number);
if ( mailbox != NULL ){
printf("%d COUNT: oops..mailbox not empty. wait for signal\n",args->number);
pthread_cond_wait(&flag,&lock);
}
mailbox = args; /* put ptr to our args there */
printf("%d COUNT: raising flag\n",args->number);
pthread_cond_signal(&flag); /* raise the flag */
printf("%d COUNT: unlocking box\n",args->number);
pthread_mutex_unlock(&lock); /* release the mailbox */
return NULL;
}
结果为:
$ ./TC test test2
MAIN: waiting for flag to go up
1 COUNT: waiting to get lock
2 COUNT: waiting to get lock
1 COUNT: have lock, storing data
1 COUNT: raising flag
1 COUNT: unlocking box
MAIN: Wow! flag was raised, I have the lock
4: test
MAIN: waiting for flag to go up
2 COUNT: have lock, storing data
2 COUNT: raising flag
2 COUNT: unlocking box
MAIN: Wow! flag was raised, I have the lock
628: test2
632: total words
$
上面这个程序虽然不是本书上的,但我觉得更适合放在这里。 程序的大意是用两个线程数出两个文件中的单词数量,最后汇总。
可以看到,这里使用一个mailbox变量作为两个线程的最终归属,即通过条件变量最后都会合到主线程。
屏障:
是用户协调多个线程并行工作的同步机制。
屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后从该点继续执行。
pthread_join 就是一种屏障,允许一个线程等待,直到另一个线程退出。
但是屏障对象的概念更广,它们允许任意数量的线程等待,直到所有的线程完成处理工作,而线程不需要退出。所有线程达到屏障后可以接着工作
函数 pthread_barrier_init(pthread_barrier_t *barrier, *attr, int count)
对屏障进行初始化
函数 pthread_barrier_destroy(*barrier)
反初始化
函数 pthread_barrier_wait(*barrier)
函数可用来表明线程已完成工作,准备等待所有其他线程赶上来。调用此函数的线程在屏障计数(即init中的count)未满足条件时,会进入休眠状态。 如果该线程是最后一个调用此函数的线程,就满足了屏障计数,所有线程都被唤醒。
一旦达到屏障计数值,而且线程处于非阻塞状态,屏障可以被重用。
书上举了个例子:
1、大致是对一个数组进行排序,且数组的长度为8000000.
2、主函数先调用 pthread_barrier_init(*barrier, 9);表明要等待9个线程(主函数加其他8个)
3、主函数创建8个线程,分别对1000000个数据进行排序。
4、每个线程的启动例程返回前都调用 pthread_barrier_wait 函数,主函数在创建完8个线程后也调用此函数。
5、等大家都结束后,主函数继续调用 merge函数将所有数据整合到一起。
自旋锁
与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等阻塞状态。