文章目录
一、线程互斥
1.概念
二、线程互斥接口
1.互斥量的接口
初始化互斥量
互斥量的的加锁和解锁
销毁互斥量
2.互斥量的原理
三、线程的封装
四、锁的封装
五、死锁
1.死锁的概念
2.产生死锁的必要条件:
3.避免死锁:核心思想 破环死锁的4个必要条件的任意一个
总结
多线程中有一个全局变量,是被所有执行流共享的。线程中,大部分资源都会直接或间接共享。只要存在共享,就可能存在并发访问的问题,进而导致数据不一致问题。
一、线程互斥
1.概念
- 临界资源:多线程执行流共享的资源就叫做临界资源(对共享资源进行一定的保护)
- 临界区:任何一个线程,都有代码,访问临界资源。线程中的访问临界资源的代码称为临界区。
- 非临界区:线程中不访问临界资源的代码称为非临界区。
- 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
- 原子性:不会被任何调度机制打断的操作,该操作只有两种状态,要么完成,要么未完成
二、线程互斥接口
1.互斥量的接口
初始化互斥量
- 静态分配 (静态分配的锁不需要被销毁)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
2.动态分配
int pthread_mutex_init(phthread_mutex_t * restrict mutex, const pthread_mutexattr_t * restict attr);
参数:mutex 要初始化的互斥量
attr:null
互斥量的的加锁和解锁
int pthread_mutex_lock(pthread_mutex_t * mutex);
int pthread_mutex_unlock(pthread_mutex_t * mutex);
//成功返回0,失败返回错误码
调用pthread_lock时候,可能有两种情况:互斥量处于未锁状态,该函数将互斥量锁定,同时返回成功
- 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但是没有竞争到互斥量,那么pthread_lock将会阻塞(执行流被挂起),等待互斥量解锁。
销毁互斥量
不要销毁一个已经加锁的互斥量
已经销毁的互斥量,要确保后面不会有线程再尝试加锁
int pthread_mutex_destroy(pthread_mutex_t * mutex);
2.互斥量的原理
为了实现互斥锁的操作,大多数体系结构提供了swap或者exchange指令,该指令的作用是把寄存器和内存单元的数据进行交换。由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一个处理器上的交换指令执行时,另一个处理器的交换指令只能等待总线周期。
共享资源是要存储到内存中,mutex再内存中,起始mutex = 1,有两个线程A和B,调度器先执行线程A的工作,A将寄存器中的值先初始化为0,然后A将内存和cpu中的数据进行交换,此时寄存器中的值为1,内存中的值为0,A获得了这把锁。时间片到了,B开始执行,B将寄存器中的值改为0,自己去执行交换的时候内存里mutex = 0,此时线程被挂起。
三、线程的封装
class Thread
{
public:
typedef enum
{
NEW = 0,
RUNNING,
EXITED
}ThreadStatus;
typedef void(*func_t)(void *);
//构造
Thread(int num,func_t fun,void * args)
:_tid(0),_status(NEW),_func(func),_args(args)
{
char name[128];
snprintf(name,sizeof(name),"thread - % d",num);
_name = name;
}
int status() { return _status;}
std::string threadname() {return _name;}
pthread_t thread_id()
{
if(_status == RUNNING) return _tid;
else return 0;
}
static void * runHelper(void * args)
{
Thread * ts = (Thread * )args; // 拿到了当前对象
(*ts)();
return nullptr;
}
void operator()()
{
if(_func!= nullptr) _func(_args);
}
//创建线程
void run()
{
int n = pthread_create(&_tid,nullptr,runHelper,this);
if( n!= 0) exit(1);
_status = RUNNING;
}
void join()
{
if n = pthread_join(_tid,nullptr);
if(n!= 0) return ;
status = EXITED;
}
~Thread()
{}
private:
pthread_t _tid;
std::string _name;
func_t _func; //线程未来要执行的回调
void * args;
};
四、锁的封装
#pragma once
#include<iostream>
#include<pthread.h>
int tickets = 1000; //抢票 共享资源
pthread_mutex_mutex = PTHREAD_MUTEX_INITIALIZER;
class Mutex //自己不维护锁,由外部传入
{
public:
Mutex(pthread_mutex_t * mutex)
:_pmutex(mutex)
{}
void lock()
{
pthread_mutex_lock(_pmutex);
}
void unlock()
{
pthread_mutex_unlock(_pmutex);
}
~Mutex()
{}
private:
pthread_mutex_t * _pmutex;
};
class LockGuard
{ public:
LockGuard(pthread_mutex_t * mutex)
:_mutex(mutex)
{
_mutex.lock();
}
~LockGuard()
{
_mutex.unlock();
}
private:
Mutex _mutex;
};
void threadRoutine(void * args)
{
std::string message = static_cast<const char *>(args);
while(true)
{
LockGuard lockguard(&mutex);
if(ticktes > 0)
{
usleep(200);
cout<<message<<"get a ticket: "<<tickets --<<endl; //临界区
}
else
{
break;
}
}
//抢完票后续的动作放入用户数据库中
usleep(1000);
LockGuard lockguard(&mutex);
}
int main()
{
Thread t1(1, threadRoutine, (void *)"1");
Thread t2(2, threadRoutine, (void *)"2");
Thread t3(3, threadRoutine, (void *)"3");
Thread t4(4, threadRoutine, (void *)"4");
return 0;
}
五、死锁
多线程代码存在并发访问临界资源的问题,所以产生了加锁的策略。加锁之后可能导致死锁。
1.死锁的概念
2.产生死锁的必要条件:
1.互斥条件:一个资源每次只能被一个执行流使用
2.请求与保持:一个执行流因请求资源而阻塞,对已获得的资源保持不妨
3.环路等待(循环等待条件):若干执行流之间形成一种头尾相接的循环等待资源的关系
4.不剥夺条件:一个执行流已获得的资源,在未使用完之前,不能强行剥夺
3.避免死锁:核心思想 破环死锁的4个必要条件的任意一个
解决死锁问题:1.不加锁 2.主动释放锁 3.按照顺序申请锁 4.控制线程统一释放锁