Linux----多线程全局变量互斥访问的几种机制简述

写在前面:
为什么写这篇文章,本人开始对这三个量的理解非常之艰辛,啃了很久的《现代操作系统》,也看了很多博客,最后略有体会。这篇文章,是基于那本砖头书以及一些博客,加个人总结理解,希望对各位小伙伴有所帮助。(主要是怕我后面自己忘记了*-*)
何为互斥访问?
简而言之,当两个进程对一块共享内存区进行访问的时候,在A进入内存区的时候,B是不可以也不允许进入的,因为这样会引起不必要的混乱。那么如何防止这样的“抢进内存区”现象发生,我们需要互斥。

机制1 互斥锁:
我们知道进程间通信一个简单的解决办法就是使用互斥锁,即存在一把锁,当进程A要进入临界区的时候先测试这把锁,若果锁为0表示可以进出,那么A进入临界区,并且把锁变成1,告知别的进程无法进入,得等待(忙等待)。当A结束时,则退出临界区,并且把锁置为0,表示可以进入,那么这个时候B就可以“安全”的进入临界区了。

忙等待:
何为忙等待,简单地说就是指两个进程共享区域进行操作的时候,当A进程进入临界区的时候,由于竞争的存在那么,我们不可以让B进程进入临界区,必须等到A退出才可以进入。但是B得知道A何时退出,所以最直白的方式就是不断地询问A是否离开临界区。

这样子B进程就是处于忙等待的状态,也称自旋锁,很显然这是非常占用CPU资源的,所以我们需要避免。那么很容易让我们想到的就是阻塞B,从而让它不再轮询,占用CPU资源,故将其挂起,等到A出来后,给个信号让B进入临界区即可。也就是下文要说的(sleep和wakeup两个系统调用)

机制2 信号量:
信号量本身并不是POSIX IPC的机制之一,而是用于进程间同步于互斥的工具,作用类似于互斥锁,但是要复杂得多。对于共享存储区而言,信号量与互斥锁都可以用来进行互斥与同步,当我们把信号量设置为1时,就可以把它当做一个事实上的互斥锁。

  • 信号量是为了解决同步问题

  • 互斥量是为了解决互斥问题

    信号量是一个整数,为了方便,我们把信号量计作S,信号量有两个原语,即P操作和V操作,一定要记住P操作和V操作都是原子操作,到底如何实现P、V操作,这是操作系统需要考虑的,同时需要硬件/CPU指令集支持,下文会详细介绍有关内容

P操作:S减1;
若S减1后仍大于或等于零,则进程继续执行;
若S减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转进程调度

V操作:S加1;
若相加结果大于零,则进程继续执行;
若相加结果小于或等于零,则从该信号的等待队列中唤醒一等待进程,然后再返回原进程继续执行或转进程调度
有一点很重要,信号量的操作只应该由内核去进行

例子:当信号量最大值为1

如果有三个线程/进程 A、B、C,都想要执行P操作,又因为P操作是原子的,那么ABC都执行了P(且没有进程执行了V操作),那信号量S将会是-2,而且后执行P操作的两个现场全部都要被加入到sleep进程队列中去
当成功执行P操作而且没有被挂起的进程执行了V操作之后,S变成了-1,这时候V操作还需要去sleep队列中去唤醒某个进程,至于到底唤醒哪一个,这取决于操作系统进程的调度方式了
然后重复上面的第二步直到S等于1
仔细看下上面的步骤,这不就是锁吗?

根据这个例子,你可能更容易理解这句话:

S大于等于零时代表可供并发进程使用的资源实体数,当S小于零时则表示正在等待使用临界区的进程数。

这个例子其实也就是很多人所说的,当信号量最大值为1(或者说不需要信号量的计数能力时),这种简化了功能的信号量就被称为互斥量。因为没有了技术能力,互斥量只有两种状态0/1。需要注意的是,这种信号量只是互斥量的一种实现方式,在概念上来讲,二者并没有直接的关系(或许从一开始互斥量是从信号量演化而来的,但是后来互斥量被单独拉成一个概念),互斥量有很多种实现方式(因为互斥量很简单)。

机制3 条件变量

其实没有概念,条件变量就是linux提供的一个编程接口,又有一些人喜欢把条件变量叫做条件锁,其实说的都是一种东西。条件变量其实就是解决了一个问题:一个进程等待某个变量成立,另外一个线程对这个变量进行修改,而这个问题必须避免发生竞态,所以往往必须要使用一个mutex,从而使得等待和修改都在同一个互斥量的临界区里。这样说可能很多人理解还是有偏差的,我们直接列出来Linux提供的接口,我们只看最关键的两个接口函数

int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex); 使当前进程休眠,并且会释放这个mutex,并且在被唤醒时重新获取mutex
int pthread_cond_signal(pthread_cond_t *cond); 唤醒等待对应条件变量的进程

条件变量经常和互斥量在一起使用,这种模式会让一个线程锁住一个互斥量,然后当它不能获得它期待的结果是等待一个条件变量。最后由另一个线程向它发送信号,是它可以继续运行。
给个消费者-生产者的代码吧,基于条件变量和互斥锁

//此处使用的是pthread的条件变量与互斥量,完美实现生产者-消费者的问题
#include "pthread.h"
#include "stdio.h"
#include "stdlib.h"
#include "unistd.h"

#define NLOOP 20
#define BUFFSIZE 10 //定义缓冲区为大小20

struct buf_t
{
    int     b_buf[BUFFSIZE];
    int     b_nitems;
    int     b_nextget;
    int     b_nextput;
    pthread_mutex_t  b_mutex; //互斥量
    pthread_cond_t   b_cond_consumer;//消费者的条件变量
    pthread_cond_t   b_cond_producer;//生产者的条件变量
}buf_t;

 void       *produce_loop(void *);
 void       *consume_loop(void *);

 int main(int argc, char **argv)
 {
    int				n;
    pthread_t		tidA, tidB;

    printf("main, addr(n_in_stack) = 0x%x, addr(buf_t) = 0x%x, addr(produce_loop) = 0x%x\n", &n, &buf_t, &produce_loop);
    
    pthread_create(&tidA, NULL, &produce_loop, NULL);
    pthread_create(&tidB, NULL, &consume_loop, NULL);

    /* wait for both threads to terminate */
    pthread_join(tidA, NULL);
    pthread_join(tidB, NULL);
    exit(0);
}
void produce(struct buf_t *bptr, int val)       /* 定义生产者的线程函数 */
{
    pthread_mutex_lock(&bptr->b_mutex);         /* down锁住缓冲区,互斥量mutex */
    /* wait if buffer is fu11 */
    while(bptr->b_nitems >= BUFFSIZE)
        pthread_cond_wait(&bptr->b_cond_producer, &bptr->b_mutex);      /* 由于缓冲区满了,等待条件变量的到来 */
    
    printf ("produce %d\n", val);
    bptr->b_buf[bptr->b_nextput] = val;
    if (++bptr->b_nextput >= BUFFSIZE)
        bptr->b_nextput = 0;
    bptr->b_nitems++;

    /*Signal consumer*/
    pthread_cond_signal(&bptr->b_cond_consumer);    /* 缓冲区满,发送信号给消费者,告知他来缓冲区取数据 */
    pthread_mutex_unlock(&bptr->b_mutex);           /* up释放缓冲区,互斥量mutex */
}
int consume(struct buf_t *bptr)                 /* 定义消费者的线程函数 */
{
    int         val;

    pthread_mutex_lock(&bptr->b_mutex);         /* down锁住缓冲区,互斥量mutex */
    while (bptr->b_nitems <= 0)
        pthread_cond_wait(&bptr->b_cond_consumer, &bptr->b_mutex);      /* 由于缓冲区空了,等待条件变量的到来 */

    val = bptr->b_buf[bptr->b_nextget];
    printf("consume %d\n", val);
    if (++bptr->b_nextget >= BUFFSIZE)
        bptr->b_nextget = 0;
    bptr->b_nitems--;

    pthread_cond_signal(&bptr->b_cond_producer);    /* 缓冲区空,发送信号给生产者,告知他来缓冲区生产数据 */
    pthread_mutex_unlock(&bptr->b_mutex);           /* up释放缓冲区,互斥量mutex */
    return(val);
}

void * produce_loop(void *vptr)
{
    int i;
    printf("produce_loop thread, addr(stack) = %x\n", &i);
    for(i = 0; i < NLOOP; i++){
        produce(&buf_t, i);
}
    return (NULL);
}

void * consume_loop(void *vptr)
{
    int         i, val;
    printf("consume_loop thread, addr(stack) = %x\n", &i);
    for(i = 0; i < NLOOP; i++){
        val = consume(&buf_t);
}
    return (NULL);
}
//非本人原创,本人只是加入注释和转载,这得谢谢我的指导老师
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: 多线程中的互斥访问是指多个线程同时访问共享资源时,需要通过一定的机制来保证同一时间只有一个线程可以访问该资源,以避免数据的不一致性和错误。常见的互斥访问机制包括锁、信号量、互斥量等。在Java中,可以使用synchronized关键字和Lock接口来实现互斥访问。 ### 回答2: 多线程是现代程序设计的常见技术,能有效地提高程序性能和用户体验。在多线程编程中,为了保证数据安全和避免竞争,需要使用互斥访问技术来同步多个线程访问。本文将介绍互斥访问实现方式和使用场景。 互斥访问是指在多线程环境下,为了保证共享数据的正确性和一致性,需要使用锁来限制同时只有一个线程可以访问共享数据。在Java语言中,提供了synchronized关键字和Lock接口来实现互斥访问。其中,synchronized关键字是Java语言内部实现的一种语法糖,用于简化锁的使用,而Lock接口则提供了更加灵活和功能丰富的锁实现。 在Java中,使用synchronized实现互斥访问的方式很简单,只需要在多个线程访问共享数据的方法或代码块前添加synchronized关键字即可。例如: public synchronized void add(int value) { count += value; } 这样,在多线程环境下,只有一个线程可以执行add方法,其它线程需要等待执行权。一旦当前线程执行完毕,锁会被释放,其它线程就可以继续争取执行权。 除了synchronized关键字,Java还提供了ReentrantLock实现互斥访问。ReentrantLock是一种可重入的锁,可以允许同一个线程多次获得锁,也可以设置锁的公平性,避免线程饥饿。使用ReentrantLock实现互斥访问的方式如下: public class Counter { private final ReentrantLock lock = new ReentrantLock(); private int count = 0; public void add(int value) { lock.lock(); try { count += value; } finally { lock.unlock(); } } } 在代码中,使用ReentrantLock的lock方法获取锁,并在finally块中使用unlock方法释放锁。这样,就可以保证多个线程同步访问共享数据。 互斥访问最常见的应用场景是对共享数据的读写操作,例如多线程修改同一个列表或缓存。使用互斥访问可以保证线程安全,避免数据损坏和因竞争而产生的异常。 总之,互斥访问是Java多线程中的关键技术,实现方式有多种,开发者需要根据实际情况选择最适合的方式。在多线程编程中,务必注意线程安全和数据一致性,避免因竞争而产生的数据异常。 ### 回答3: 在多线程编程中,有时候会出现多个线程同时访问同一共享资源的情况,这就可能会导致数据的不一致或者出错。为了解决这个问题,就需要使用互斥访问技术来控制对共享资源的访问互斥访问技术主要有两种:一种是使用机制,即在对共享资源的访问上加锁,保证同一时间只有一个线程能够访问该资源;另一种是使用信号量机制,即在对共享资源的访问上设置一个资源数目,在访问前必须先获取资源,访问后再释放资源。 在Java中,使用互斥访问可以通过synchronized关键字实现。synchronized关键字用于修饰方法或者代码块,保证同一时间只有一个线程能够访问该方法或者代码块对应的资源。 例如,对于以下共享资源的访问: public class SharedResource { private int count = 0; public void increment() { //多个线程同时访问这个方法会导致count的值不准确 count++; } } 可以使用synchronized关键字来实现互斥访问: public class SharedResource { private int count = 0; public synchronized void increment() { count++; } } 这样,多个线程同时访问increment()方法时,只有一个线程能够获取到锁,执行完该方法后释放锁,其他线程才能获取到锁进行访问。 需要注意的是,互斥访问虽然可以保证数据的一致性和完整性,但也会降低程序的并发性能,因此需要在权衡性能和正确性的基础上,选择合适的互斥访问方式。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值