QT多线程(线程互斥)

简介: QT多线程(线程互斥)

前言

线程互斥是指在多线程并发执行时,为避免多个线程访问共享资源时发生冲突而采取的一种机制。本篇文章我们就这个问题来了解一下什么叫线程互斥,又如何解决线程互斥的问题。


一、导致问题产生的原因和解决方法

如果多个线程同时访问同一共享资源,可能会导致数据不一致、资源竞争和死锁等问题。

为了避免这些问题,可以使用互斥锁(Mutex)来保护共享资源。互斥锁是一种同步机制,用于控制多个线程对共享资源的访问。当一个线程获得了互斥锁,其他线程就无法获得该锁,直到该线程释放互斥锁为止。


二、同时访问一个临界资源带来的问题

下面我们编写一个示例程序来带大家详细的看一下同时访问一个临界资源带来的问题。

下面的代码定义了一个临时变量g_i,同时创建了一个Mythread线程,在代码中让Mythread和主线程去访问这个临界资源让这个变量增加。

static int g_i = 0;//临界资源
class Mythread : public QThread
{
protected:
    void run()
    {
        for(int i = 0; i < 5; i++)
        {
            g_i++;
            qDebug() << "Mythread g_i :" << g_i;
            sleep(1);   //休眠1s
        }
    }
};
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Mythread t1;
    t1.start();
    for(int i = 0; i < 5; i++)
    {
        g_i++;
        qDebug() << "Main Thread g_i :" << g_i;
        QThread::sleep(1);   //休眠1s
    }
    return a.exec();
}

运行结果:

可以看到这个打印结果看起来是非常奇怪的。这就是因为两个线程同时访问了一个变量导致的问题,那么下面使用锁来解决这个问题。

三、QMutex线程锁

QMutex是Qt框架中提供的互斥锁类,用于保护共享资源以避免多个线程同时访问同一共享资源导致的竞争问题。

QMutex的使用非常简单,基本步骤如下:

1.创建QMutex对象

QMutex mutex;

2.在访问共享资源的代码段前加锁

mutex.lock();
// Access shared resource

3.在访问共享资源的代码段后解锁

// Access shared resource
mutex.unlock();

使用线程锁解决上述的问题:

当进行访问或者使用临界资源时需要对其进行上锁操作,当使用结束后再解锁,这样就可以避免竞争同一个临界资源带来的问题。

static QMutex g_mutex;
static int g_i = 0;//临界资源
class Mythread : public QThread
{
protected:
    void run()
    {
        for(int i = 0; i < 5; i++)
        {
            g_mutex.lock();
            g_i++;
            qDebug() << "Mythread g_i :" << g_i;
            g_mutex.unlock();
            sleep(1);   //休眠1s
        }
    }
};
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Mythread t1;
    t1.start();
    for(int i = 0; i < 5; i++)
    {
        g_mutex.lock();
        g_i++;
        qDebug() << "Main Thread g_i :" << g_i;
        g_mutex.unlock();
        QThread::sleep(1);   //休眠1s
    }
    return a.exec();
}

运行结果:

4.线程死锁

线程死锁(Deadlock)是指两个或多个线程在执行过程中因争夺资源而造成的一种互相等待的现象,导致所有线程都被阻塞,无法继续执行。

在多线程编程中,线程死锁是一个非常常见的问题,一旦发生死锁,程序将永远无法继续执行下去,通常需要手动结束程序。线程死锁的发生通常由于多个线程之间互相等待对方释放资源,从而导致所有线程都无法执行下去。

线程死锁往往是由于以下几个因素引起的:

1.互斥:多个线程同时访问共享资源,但只能有一个线程占用该资源,其它线程必须等待。

2.不可抢占:资源在被一个线程占用时,不能被其它线程强制抢占。

3.持有和等待:一个线程持有一个资源且正在等待另外一个线程释放它所持有的资源。

4.环路等待:一组线程互相等待,并且每个线程都在等待另一个线程释放资源。

这里给出一个死锁的例子:

static QMutex g_mutex1;
static QMutex g_mutex2;
static int g_i = 0;//临界资源
static int g_i1 = 0;//临界资源
class Mythread : public QThread
{
protected:
    void run()
    {
        for(int i = 0; i < 5; i++)
        {
            g_mutex1.lock();
            g_mutex2.lock();
            g_i++;
            g_i1++;
            qDebug() << "Mythread g_i :" << g_i;
            g_mutex2.unlock();
            g_mutex1.lock();
            sleep(1);   //休眠1s
        }
    }
};
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    Mythread t1;
    t1.start();
    for(int i = 0; i < 5; i++)
    {
        g_mutex2.lock();
        g_mutex1.lock();
        g_i++;
        g_i1++;
        qDebug() << "Main Thread g_i :" << g_i;
        g_mutex1.unlock();
        g_mutex2.unlock();
        QThread::sleep(1);   //休眠1s
    }
    return a.exec();
}

运行结果:

这里可以看到两个线程分别只运行了一次就卡死了。

程序产生死锁的原因:

主线程和被创建出来的线程开始运行并获取线程锁,主线程获取线程锁2,被创建出的线程获取线程锁1。

当被创建出的线程想获取线程锁2时会发现无法获取线程锁2,因为此时线程锁2被主线程获取了,当主线程想获取线程锁1时也是同样的道理,所有这就导致了线程的死锁。

假设线程1先获取了g_mutex1这个互斥锁,线程2先获取了g_mutex2这个互斥锁。然后线程1又试图获取g_mutex2这个互斥锁,此时它会一直等待线程2释放该互斥锁;同时,线程2也试图获取g_mutex1这个互斥锁,由于该锁已经被线程1占用,线程2也一直等待。这样,线程1和线程2就互相等待对方释放锁,导致死锁。


5.解决死锁的方法

给每一个临界资源都分配一个编号。

给每一个线程锁都分配一个编号。

一个线程锁对应一个临界资源。

每一个线程按照顺序获取线程锁。

解决代码:

QMutex g_mutex_1;
QMutex g_mutex_2;
class ThreadA : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            g_mutex_1.lock();
            qDebug() << objectName() << "get m1";
            g_mutex_2.lock();
            qDebug() << objectName() << "get m2";
            qDebug() << objectName() << "do work ...";
            g_mutex_2.unlock();
            g_mutex_1.unlock();
            sleep(1);
        }
    }
};
class ThreadB : public QThread
{
protected:
    void run()
    {
        while( true )
        {
            g_mutex_1.lock();
            qDebug() << objectName() << "get m2";
            g_mutex_2.lock();
            qDebug() << objectName() << "get m1";
            qDebug() << objectName() << "do work ...";
            g_mutex_2.unlock();
            g_mutex_1.unlock();
            sleep(1);
        }
    }
};
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    ThreadA ta;
    ThreadB tb;
    ta.setObjectName("ta");
    tb.setObjectName("tb");
    ta.start();
    tb.start();
    return a.exec();
}

总结

这篇文章讲解了线程的互斥和线程的死锁,并给出了线程死锁的解决方法。

相关文章
|
28天前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
61 1
|
2月前
|
供应链 安全 NoSQL
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
48 6
|
3月前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
51 3
|
3月前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
34 2
|
3月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
55 2
|
3月前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
58 1
|
3月前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
66 1
|
2月前
|
数据采集 Java Python
爬取小说资源的Python实践:从单线程到多线程的效率飞跃
本文介绍了一种使用Python从笔趣阁网站爬取小说内容的方法,并通过引入多线程技术大幅提高了下载效率。文章首先概述了环境准备,包括所需安装的库,然后详细描述了爬虫程序的设计与实现过程,包括发送HTTP请求、解析HTML文档、提取章节链接及多线程下载等步骤。最后,强调了性能优化的重要性,并提醒读者遵守相关法律法规。
82 0
|
3月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
71 1
|
3月前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
58 1

热门文章

最新文章

推荐镜像

更多