(文章目录)
前言
线程互斥是指在多线程并发执行时,为避免多个线程访问共享资源时发生冲突而采取的一种机制。本篇文章我们就这个问题来了解一下什么叫线程互斥,又如何解决线程互斥的问题。
一、导致问题产生的原因和解决方法
如果多个线程同时访问同一共享资源,可能会导致数据不一致、资源竞争和死锁等问题。
为了避免这些问题,可以使用互斥锁(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();
}
总结
这篇文章讲解了线程的互斥和线程的死锁,并给出了线程死锁的解决方法。
标签:临界,互斥,死锁,线程,QMutex,多线程,资源 From: https://blog.51cto.com/u_16153875/6455190