Linux下的 c 多线程与线程同步(二)

关于线程本人力推苏丙榅老师(曾经担任黑马讲师)的视频讲解,B站与老师自己博客都有老师的讲解,内容通俗易懂非常适合学习,本人线程学习皆从老师处学习。

https://subingwen.cn/linux/thread-sync/

上一章节讲述了线程的创建与使用,本章节主要讲述线程同步

 为什么要线程同步?

        举个栗子,相信年轻一的的小伙伴都有想过,假如我银行卡内有300块,我提到支付宝的时候同一时间在ATM里取300,那是不是就能赚他300? 很遗憾,银行可不会给你钻空子,此时就引出了线程同步的概念

线程同步的概念:

        很多小伙伴认为线程同步是执行的同步,实则不然;所谓线程同步,其实是多线程情况下对于内存中的共享资源进行访问修改的时候,使这个共享资源按顺序被修改,所谓的同步并不是多个线程同时对内存进行访问,而是按照先后顺序依次进行的。

其实使用原子变量也可以达到以上效果,而且不用开锁解锁,更加节省资源,有兴趣的小伙伴可以去了解一下,这里不过多赘述

1.1 同步方式


对于多个线程访问共享资源出现数据混乱的问题,需要进行线程同步。常用的线程同步方式有四种:互斥锁、读写锁、条件变量、信号量。所谓的共享资源就是多个线程共同访问的变量,这些变量通常为全局数据区变量或者堆区变量,这些变量对应的共享资源也被称之为临界资源。

1.2互斥锁

创建的锁对象中保存了当前这把锁的状态信息:锁定还是打开,如果是锁定状态还记录了给这把锁加锁的线程信息(线程 ID)。一个互斥锁变量只能被一个线程锁定,被锁定之后其他线程再对互斥锁变量加锁就会被阻塞,直到这把互斥锁被解锁,被阻塞的线程才能被解除阻塞。一般情况下,每一个共享资源对应一个把互斥锁,锁的个数和线程的个数无关。

// 初始化互斥锁
// restrict: 是一个关键字, 用来修饰指针, 只有这个关键字修饰的指针可以访问指向的内存地址, 其他指针是不行的
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
           const pthread_mutexattr_t *restrict attr);
// 释放互斥锁资源            
int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数:
mutex: 互斥锁变量的地址
attr: 互斥锁的属性,一般使用默认属性即可,这个参数指定为 NULL


加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);//给代码段加锁

线程会判断当前锁的状态:

        如果是上锁状态:线程会阻塞在此处   

        如果为未锁:线程加锁并记录锁状态,向下执行


尝试加锁

// 尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);

线程会判断当前锁的状态:

        如果是上锁状态:线程不会被阻塞,加锁失败直接返回错误号

        如果为未锁:线程加锁并记录锁状态,向下执行


解锁

// 对互斥锁解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

1.3读写锁

        读写锁相当于高级的互斥锁,就像svip与vip一样。

创建:

pthread_rwlock_t rwlock;

之所以称其为读写锁,是因为这把锁既可以锁定读操作,也可以锁定写操作。为了方便理解,可以大致认为在这把锁中记录了这些信息:

1.1 锁的状态:锁定 / 打开
1.2 锁定的是什么操作:读操作 / 写操作,使用读写锁锁定了读操作,需要先解锁才能去锁定写操         作,反之亦然。
1.3 哪个线程将这把锁锁上了

特点:1.使用读写锁的读锁锁定了临界区,线程对临界区的访问是并行的,读锁是共享的。

           2.使用读写锁的写锁锁定了临界区,线程对临界区的访问是串行的,写锁是独占的。

           3.使用读写锁分别对两个临界区加了读锁和写锁,两个线程要同时访问者两个临界区,访问写锁临界区的线程继续运行,访问读锁临界区的线程阻塞,因为写锁比读锁的优先级高。

加读锁:

// 在程序中对读写锁加读锁, 锁定的是读操作
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

加写锁:

// 在程序中对读写锁加写锁, 锁定的是写操作
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

解锁:

// 解锁, 不管锁定了读还是写都可用解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

1.4条件变量

一般情况下条件变量主要针对消费者模型,并且和互斥锁配合使用

  • 严格意义上来说,条件变量的主要作用不是处理线程同步,而是进行线程的阻塞。如果在多线程程序中只使用条件变量无法实现线程的同步,必须要配合互斥锁来使用。
  • 条件变量只有在满足指定条件下才会阻塞线程,如果条件不满足,多个线程可以同时进入临界区,同时读写临界资源,这种情况下还是会出现共享资源中数据的混乱。
pthread_cond_t cond;//创建条件变量
// 初始化
int pthread_cond_init(pthread_cond_t *restrict cond,
      const pthread_condattr_t *restrict attr);
// 销毁释放资源        
int pthread_cond_destroy(pthread_cond_t *cond);

参数

  • cond为对象地址
  • attr一般情况下置空,使用默认属性        

同步

        

// 线程阻塞函数, 哪个线程调用这个函数, 哪个线程就会被阻塞
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

若条件变量在此处阻塞,会自动打开传入的互斥锁,以便其他的线程正常运行,理论上来说可以使全部的消费者线程阻塞在此处,当生产者线程抢到cpu时间片后正常加锁执行后调用解除阻塞函数;使消费者线程正常执行,此时阻塞在此处的消费者线程会抢夺此处的互斥锁,如何正常执行,如果没抢到锁,则继续阻塞;

解除阻塞:

// 唤醒阻塞在条件变量上的线程, 至少有一个被解除阻塞
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒阻塞在条件变量上的线程, 被阻塞的线程全部解除阻塞
int pthread_cond_broadcast(pthread_cond_t *cond);

此文章为作者学习时的记录,如有缺陷多多包涵

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值