C++中关于互斥量的全面认知

目录
  • 互斥量(保护对共享变量的访问)
    • 1.概念
    • 2.状态
    • 3.特点
  • 互斥量的分配
    • 1.静态分配
    • 2.动态分配
  • 加锁和解锁互斥量
    • 1.创建互斥锁
    • 2.初始化互斥锁
    • 3.获取互斥锁
    • 4.阻塞调用
    • 5.非阻塞调用
    • 6.超时调用
    • 7.释放互斥锁
    • 8.销毁线程锁
  • 互斥量的死锁

互斥量(保护对共享变量的访问)

1.概念

互斥(mutex)是防止同时访问共享资源的程序对象。

为避免线程更新共享变量时所出现问题,必须使用互斥量( mutex 是 mutual exclusion 的 缩写)来确保同时仅有一个线程可以访问某项共享资源。 即就是 使用互斥量来实现原子访问操作

2.状态

已锁定( locked)和未锁定( unlocked)。任何时候,至多只有一个线程可以锁定该互斥量。试图对已经锁定的某一互斥量再次加锁,将可能阻塞线程或者报错失败,具体取决于加锁时使用的方法

3.特点

一旦线程锁定互斥量,随即成为该互斥量的所有者。只有所有者才能给互斥量解锁。因为所有权的关系,有时会使用术语获取( acquire)和释放( release)来替代加锁和解锁。

互斥量的分配

互斥量既可以像静态变量那样分配,也可以在运行时动态创建

1.静态分配

互斥量是属于 pthread_mutex_t 类型的变量

在使用之前必须对其初始化。

pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER

2.动态分配

#include<pthread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

参数 mutex 指定函数执行初始化操作的目标互斥量。

参数 attr 是指向 pthread_mutexattr_t 类型对象的指针,该对象在函数调用之前已经过了初始化处理,用于定义互斥量的属性。若将 attr 参数置为 NULL,则该互斥量的各种属性会取默认值。

注:

  • 初始化一个业已初始化的互斥量将导致未定义的行为
  • 动态分配于堆中的互斥量。例如,动态创建针对某一结构的链表,表中每个结构都包含一个 pthread_mutex_t 类型的字段来存放互斥量,借以保护对该结构的访问。
  • 互斥量是在栈中分配的自动变量。
  • 初始化经由静态分配,且不使用默认属性的互斥量。

加锁和解锁互斥量

初始化之后,互斥量处于未锁定状态。函数 pthread_mutex_lock()可以锁定某一互斥量,而函数 pthread_mutex_unlock()则可以将一个互斥量解锁。

函数原型

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);

1.创建互斥锁

pthread_mutex_t mtx;

互斥锁的类型是 pthread_mutex_t ,所以定义一个变量就是创建了一个互斥锁:

2.初始化互斥锁

//第二个参数为 NULL,互斥锁的属性会设置为默认属性
pthread_mutex_init(&mtx, NULL);

3.获取互斥锁

在进行互斥操作的时候, 应该先"拿到锁"再执行需要互斥的操作,否则可能会导致多个线程都需要访问的数据结果不一致。

4.阻塞调用

pthread_mutex_lock(&mtx);

5.非阻塞调用

如果锁被占用就不用,如果没被占用那就用, 可以使用 pthread_mutex_trylock() 函数。 用法和pthread_mutex_lock() 用法类似,不过当请求的锁正在被占用的时候, 不会进入阻塞状态,而是立刻返回,并返回一个错误代码 EBUSY,意思是说, 有其它线程正在使用这个锁。

int err = pthread_mutex_trylock(&mtx);
if(0 != err) {
    if(EBUSY == err) {
        //The mutex could not be acquired because it was already locked.
    }
}

6.超时调用

如果不想不断的调用 pthread_mutex_trylock() 来测试互斥锁是否可用, 而是想阻塞调用,但是增加一个超时时间, 用pthread_mutex_timedlock() 解决, 其调用方式如下:

struct timespec abs_timeout;
abs_timeout.tv_sec = time(NULL) + 1;
abs_timeout.tv_nsec = 0;
int err = pthread_mutex_timedlock(&mtx, &abs_timeout);
if(0 != err) {
    if(ETIMEDOUT == err) {
        //The mutex could not be locked before the specified timeout expired.
    }
}

阻塞等待,但是只等待一秒钟,后如果还没拿到锁的话, 那就返回,并返回一个错误代码 ETIMEDOUT,意思是超时了。

其中 timespec 定义在头文件 time.h 中,其定义如下

struct timespec
{
    __time_t tv_sec;        /* Seconds.  */
    long int tv_nsec;       /* Nanoseconds.  */
};

这个函数里面的时间,是绝对时间,所以这里用 time() 函数返回的时间增加了 1 秒

7.释放互斥锁

用完互斥锁,一定要记得释放,下一个想要获得这个锁的线程, 只能去等。

释放互斥锁比较简单,使用 pthread_mutex_unlock() 即可:

pthread_mutex_unlock(&mtx);

8.销毁线程锁

pthread_mutex_destroy(&mtx)

一个被销毁的线程锁可以被 pthread_mutex_init() 再次初始化。对被销毁的线程锁进行其它操作,其结果是未定义的。

对一个处于已初始化但未锁定状态的线程锁进行销毁是安全的。尽量避免对一个处于锁定状态的线程锁进行销毁操作。

互斥量的死锁

当超过一个线程加锁同一组互斥量时,就有可能发生死锁。

例,每个线程都成功地锁住一个互斥量,接着试图对已为另一线程锁定的互斥量加锁。

两个线程将无限期等待

有两种解决方法

1.当多个线程对一组互斥量操作时,总是应该以相同顺序对该组互斥量进行锁定,如果两个线程总是先锁定 mutex1 再锁定 mutex2,死锁就不会出现

2.使用频率较低,就是“尝试一下,然后恢复”,在这种方案中,线程先使用函数pthread_mutex_lock()锁定第 1 个互斥量,然后使用函数pthread_mutex_trylock()来锁定其余互斥量。如果任一pthread_mutex_trylock()调用失败(返回 EBUSY),那么该线程将释放所有 互斥量,也许经过一段时间间隔,从头再试

注:

  • 对共享资源操作前一定要获得锁。
  • 完成操作以后一定要释放锁。
  • 尽量短时间地占用锁。
  • 如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。
  • 线程错误返回时应该释放它所获得的锁。

例子

保护fp指向文件中数的累加正常进行

static pthread_mutex_t mut=PTHREAD_MUTEX_INITIALIZER;
void *thr_prime(void *p)
{
    FILE *fp;
    char linebuf[linesize];
    fp =fopen(fname,"r+");   //多个线程之间相撞拿到 同一个fp 开始覆盖写操作
    if(fp == NULL)
    {
        perror("fopen");
        exit(-1);
    }
    //加锁
    pthread_mutex_lock(&mut);
    fgets(linebuf,linesize,fp);
    fseek(fp,0,SEEK_SET);
    fprintf(fp,"%d\n",atoi(linebuf)+1);
    //解锁
    pthread_mutex_unlock(&mut);
    fclose(fp);
    pthread_exit(NULL);
}
int main()
{
    int err,i;
    pthread_t tid[thrnum];
    //main 线程 进行创建线程
    for(i=0 ; i<=thrnum ;i++)
    {
        err =pthread_create(tid + i ,NULL,thr_prime,NULL);
        if(err)
        {
            fprintf(stderr,"pthread_creat():%s\n",strerror(err));
            exit(1);
        }
    }
    //为线程收尸
    for(i =0 ; i<=thrnum ;i++)
    {
        pthread_join(tid[i],NULL);
    }
    pthread_mutex_destroy(&mut);
    exit(0);
}

到此这篇关于C++中关于互斥量的全面认知的文章就介绍到这了,更多相关C++互斥量内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 浅谈c++11线程的互斥量

    为什么需要互斥量 在多任务操作系统中,同时运行的多个任务可能都需要使用同一种资源.这个过程有点类似于,公司部门里,我在使用着打印机打印东西的同时(还没有打印完),别人刚好也在此刻使用打印机打印东西,如果不做任何处理的话,打印出来的东西肯定是错乱的. #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <chrono> #include <thread>

  • C++详解多线程中的线程同步与互斥量

    目录 线程同步 互斥量 线程同步 /* 使用多线程实现买票的案例. 有3个窗口,一共是100张票. */ #include <stdio.h> #include <pthread.h> #include <unistd.h> // 全局变量,所有的线程都共享这一份资源. int tickets = 100; void * sellticket(void * arg) { // 卖票 while(tickets > 0) { usleep(6000); //微秒 p

  • C++ 多线程之互斥量(mutex)详解

    目录 std::mutex std::recursive_mutex std::time_mutex std::recursive_timed_mutex std::shared_mutex std::shared_timed_mutex 总结 C++ 11中的互斥量,声明在 <mutex> 头文件中,互斥量的使用可以在各种方面,比较常用在对共享数据的读写上,如果有多个线程同时读写一个数据,那么想要保证多线程安全,就必须对共享变量的读写进行保护(上锁),从而保证线程安全. 互斥量主要有四中类型

  • C++中关于互斥量的全面认知

    目录 互斥量(保护对共享变量的访问) 1.概念 2.状态 3.特点 互斥量的分配 1.静态分配 2.动态分配 加锁和解锁互斥量 1.创建互斥锁 2.初始化互斥锁 3.获取互斥锁 4.阻塞调用 5.非阻塞调用 6.超时调用 7.释放互斥锁 8.销毁线程锁 互斥量的死锁 互斥量(保护对共享变量的访问) 1.概念 互斥(mutex)是防止同时访问共享资源的程序对象. 为避免线程更新共享变量时所出现问题,必须使用互斥量( mutex 是 mutual exclusion 的 缩写)来确保同时仅有一个线程

  • Linux多线程使用互斥量同步线程

    本文将会给出互斥量的详细解说,并用一个互斥量解决上一篇文章中,要使用两个信号量才能解决的只有子线程结束了对输入的处理和统计后,主线程才能继续执行的问题. 一.什么是互斥量 互斥量是另一种用于多线程中的同步访问方法,它允许程序锁住某个对象,使得每次只能有一个线程访问它.为了控制对关键代码的访问,必须在进入这段代码之前锁住一个互斥量,然后在完成操作之后解锁. 二.互斥量的函数的使用 它们的定义与使用信号量的函数非常相似,它们的定义如下: #include <pthread.h> int pthre

  • c# mutex互斥量的深入解析

    互斥锁(Mutex) 互斥锁是一个互斥的同步对象,意味着同一时间有且仅有一个线程可以获取它. 互斥锁可适用于一个共享资源每次只能被一个线程访问的情况  函数: //创建一个处于未获取状态的互斥锁 Public Mutex(); //如果owned为true,互斥锁的初始状态就是被主线程所获取,否则处于未获取状态 Public Mutex(bool owned); 如果要获取一个互斥锁.应调用互斥锁上的WaitOne()方法,该方法继承于Thread.WaitHandle类 它处于等到状态直至所调

  • Linux线程管理必备:解析互斥量与条件变量的详解

    做过稍微大一点项目的人都知道,力求程序的稳定性和调度的方便,使用了大量的线程,几乎每个模块都有一个专门的线程处理函数.而互斥量与条件变量在线程管理中必不可少,任务间的调度几乎都是由互斥量与条件变量控制.互斥量的实现与进程中的信号量(无名信号量)是类似的,当然,信号量也可以用于线程,区别在于初始化的时候,其本质都是P/V操作.编译时,记得加上-lpthread或-lrt哦. 有关进程间通信(消息队列)见:进程间通信之深入消息队列的详解 一.互斥量 1. 初始化与销毁: 对于静态分配的互斥量, 可以

  • C++详细讲解互斥量与lock_guard类模板及死锁

    目录 互斥量的基本概念 互斥量的使用 lock_guard类模板 死锁 lock与lock_guard的使用 保护共享数据,操作时,用代码把共享数据锁住.操作数据.解锁 其他想操作共享数据的线程必须等待解锁.锁定住.操作.解锁 互斥量的基本概念 互斥量是个类对象,理解成一把锁,多个线程尝试使用lock()成员函数来枷锁这个锁,是有一个线程可以锁成功,成功的标志是返回 如果没有锁成功,那么流程卡在lock这里不断尝试去锁 互斥量的使用 #include <iostream> #include &

  • GoLang中的互斥锁Mutex和读写锁RWMutex使用教程

    目录 一.竞态条件与临界区和同步工具 (1)竞态条件 (2)临界区 (3)同步工具 二.互斥量 三.使用互斥锁的注意事项 (1)使用互斥锁的注意事项 (2)使用defer语句解锁 (3)sync.Mutex是值类型 四.读写锁与互斥锁的异同 (1)读/写互斥锁 (2)读写锁规则 (3)解锁读写锁 一.竞态条件与临界区和同步工具 (1)竞态条件 一旦数据被多个线程共享,那么就会产生冲突和争用的情况,这种情况被称为竞态条件.这往往会破坏数据的一致性. 同步的用途有两个,一个是避免多线程在同一时刻操作

  • 详解java中的互斥锁信号量和多线程等待机制

    互斥锁和信号量都是操作系统中为并发编程设计基本概念,互斥锁和信号量的概念上的不同在于,对于同一个资源,互斥锁只有0和1 的概念,而信号量不止于此.也就是说,信号量可以使资源同时被多个线程访问,而互斥锁同时只能被一个线程访问 互斥锁在java中的实现就是 ReetranLock , 在访问一个同步资源时,它的对象需要通过方法 tryLock() 获得这个锁,如果失败,返回 false,成功返回true.根据返回的信息来判断是否要访问这个被同步的资源.看下面的例子 public class Reen

随机推荐