第十一章 线程之锁、读写锁、条件变量、屏障

函数    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);

    以下是利用锁来保护队列,多个工作线程获取主线程分配给它们的作业:

#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函数将所有数据整合到一起。



自旋锁
    与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等阻塞状态。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 互斥锁:对于一个写操作,可以使用互斥锁来阻止其他线程读取同一个变量或者资源。读写锁:如果多个线程同时访问一个变量或资源,可以使用读写锁来保证线程安全。条件变量:当某线程需要等待另一个线程完成某任务时,可以使用条件变量来实现。信号量:可以使用信号量来控制同时访问的线程数量,从而保证线程安全。屏障:当多个线程需要在某个时刻同步执行时,可以使用屏障来阻止其他线程继续执行,直到所有线程都到达屏障。 ### 回答2: 互斥锁、读写锁条件变量、信号量、屏障是操作系统中常用的同步原语,用于多线程或多进程之间的协调和同步。下面举几个例子来说明它们的应用: 1. 互斥锁: 互斥锁是最常见且基础的同步原语,用来保护临界区,确保同一时间只有一个线程可以访问共享资源。例如,在多线程编程中,多个线程需要访问共享的全局变量,我们可以使用互斥锁来保证线程的互斥访问。 2. 读写锁读写锁是一种特殊的锁,它分为读锁和写锁。多个线程可以同时获取读锁,但只有一个线程可以获取写锁。读写锁适用于读多写少的场景,可以提高读操作的并发性。例如,在一个文件缓存系统中,多个线程可以同时读取缓存的文件内容,但只有一个线程可以写入缓存。 3. 条件变量条件变量用于线程间的等待和通知机制,能够在满足特定条件时唤醒等待的线程。例如,在生产者-消费者模型中,生产者需要等待缓冲区不满时才能继续生产,消费者需要等待缓冲区不空时才能继续消费。条件变量可以通过等待和通知的方式实现线程的同步。 4. 信号量: 信号量用于控制对临界资源的访问数量。可以将信号量看作是一个计数器,当资源被占用时,计数器减1,当资源被释放时,计数器加1。例如,在操作系统中,可以使用信号量来限制一个资源的并发访问数量,比如限制同时访问数据库的连接数量。 5. 屏障屏障用于控制多个线程在某个点上的同步,即在该点前的线程必须等待所有线程都到达该点才能继续执行。例如,在一个并行计算任务中,可能需要多个线程在某个阶段完成计算后再进入下一个阶段,这时可以使用屏障来同步各个线程的执行。 ### 回答3: 互斥锁是一种保护共享资源的锁,在同一时刻只允许一个线程对共享资源进行操作。例如,当多个线程同时访问一个共享计数器时,互斥锁可以保证每次只有一个线程能够增加或减少计数器的值,避免了竞态条件的发生。 读写锁是一种更高级别的锁,允许多个线程同时读取共享资源,但只允许一个线程进行写操作。例如,在一个并发读写文件的场景中,读写锁可以保证多个线程可以同时读取文件的内容,但只允许一个线程进行写入操作。 条件变量是一种用于线程间通信的机制,可以通知等待的线程某个特定的条件已经满足。例如,在一个生产者-消费者模型中,当缓冲区满时,生产者线程可以通过条件变量通知消费者线程可以消费数据了。 信号量是一种用于控制多个线程并发访问共享资源的机制。例如,当有限数量的资源需要在多个线程间共享时,可以使用信号量来限制资源的并发访问数量,从而避免资源的过度竞争。 屏障用于线程同步,可以让多个线程在某个特定的点上等待,直到所有线程都到达该点才能继续执行。例如,当多个线程并行执行任务,但需要等待所有线程都完成自己的任务后再进行下一步操作时,可以使用屏障来实现线程间的同步。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值