#include<iostream>
using namespace std;
#include<pthread.h>
#include<string>
#include<vector>
#include<unistd.h>
const int NUM=4;
class ThreadDate
{
public:
ThreadDate(string name)
{
_name=name;
}
string _name;
};
int Apples=100;
void* GetApple(void* args)
{
ThreadDate* td=static_cast<ThreadDate*>(args);
while(1)
{
if(Apples>0)
{
sleep(1);
cout<<td->_name<<"get a apple,apple number"<<--Apples<<endl;
}
else break;
}
return nullptr;
}
int main()
{
vector<pthread_t> tids;
vector<ThreadDate*> tds;
for(int i=0;i<NUM;i++)
{
string name="thread"+to_string(i);
ThreadDate* td=new ThreadDate(name);
tds.push_back(td);
pthread_t tid;
pthread_create(&tid,nullptr,GetApple,tds[i]);
tids.push_back(tid);
}
for(int i=0;i<NUM;i++)
{
pthread_join(tids[i],nullptr);
delete tds[i];
}
return 0;
}
现象:
产生该现象的原因:
若线程使用的数据是局部变量,变量的地址空间在线程栈空间内,变量归属单个线程,其他线程无法获得这种变量;但有些变量需要在线程间共享(共享变量),可以通过数据的共享,完成线程之间的交互。多个线程并发的操作共享变量就会带来一些问题
要解决上述分苹果的问题,需要做到三点:
代码必须有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
若多个线程同时要求执行临界区的代码,并且此时临界区没有线程在执行,那么只能允许一个线程进入该临界区
若线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区
这时就需要一把锁,Linux中提供的这把锁被称为互斥量
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
参数:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数mutex:需要销毁的互斥量的地址
返回值:互斥量销毁成功返回0,失败返回错误码
注意:
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数mutex:需要加锁的互斥量的地址
返回值:互斥量加锁成功返回0,失败返回错误码
注意:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数mutex:需要解锁的互斥量的地址
返回值:互斥量解锁成功返回0,失败返回错误码
#include<iostream>
using namespace std;
#include<pthread.h>
#include<string>
#include<vector>
#include<unistd.h>
const int NUM=4;
class ThreadDate
{
public:
ThreadDate(string name,pthread_mutex_t* lock)
{
_name=name;
_lock=lock;
}
public:
string _name;
pthread_mutex_t* _lock;
};
int Apples=100;
void* GetApple(void* args)
{
ThreadDate* td=static_cast<ThreadDate*>(args);
while(1)
{
pthread_mutex_lock(td->_lock);
if(Apples>0)
{
//sleep(1);
cout<<td->_name<<"get a apple,apple number"<<Apples--<<endl;
pthread_mutex_unlock(td->_lock);
}
else
{
pthread_mutex_unlock(td->_lock);
break;
}
}
return nullptr;
}
int main()
{
pthread_mutex_t lock;
pthread_mutex_init(&lock,nullptr);
vector<pthread_t> tids;
vector<ThreadDate*> tds;
for(int i=0;i<NUM;i++)
{
string name="thread"+to_string(i);
ThreadDate* td=new ThreadDate(name,&lock);
tds.push_back(td);
pthread_t tid;
pthread_create(&tid,nullptr,GetApple,tds[i]);
tids.push_back(tid);
}
for(int i=0;i<NUM;i++)
{
pthread_join(tids[i],nullptr);
delete tds[i];
}
pthread_mutex_destroy(&lock);
return 0;
}
写这个代码的时候出现了一个乌龙。写到这里复盘以下,顺便提一嘴,多线程写代码时考虑的是要多一点。
引入互斥量后,当一个线程申请到锁进入临界区时,在其他线程看来该线程只有两种状态,要么没有申请锁,要么锁已经释放了,因为只有这两种状态对其他线程才是有意义的。
例如,图中线程1进入临界区后,在线程2、3、4看来,线程1要么没有申请锁,要么线程1已经将锁释放了,因为只有这两种状态对线程2、3、4才是有意义的,当线程2、3、4检测到其他状态(线程1持有锁)时也就被阻塞了。此时对于线程2、3、4而言,线程1的整个操作过程是原子的
临界区内的线程可能被切换吗?
临界区内的线程是可能进行线程切换。但即便该线程被切走,其他线程也无法进入临界区进行资源访问,因为此时该线程是拿着锁被切走的,锁没有被释放也就意味着其他线程无法申请到锁,也就无法进入临界区进行资源访问了。
互斥锁是否需要被保护?
既然锁是临界资源,那么锁就必须被保护起来,但锁本身就是用来保护临界资源的,那锁又由谁来保护的呢?
锁实际上是自己保护自己的,只需要保证申请锁的过程是原子的,那么锁就是安全的
如何保证申请锁是原子的?
为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用就是把寄存器和内存单元的数据相交换。由于只有一条指令,保证了原子性。
lock和unlock的伪代码:
死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态
一个锁锁死
多个锁锁死
线程A申请锁资源的顺序为:锁1、锁2;线程B申请锁资源的顺序为:锁2、锁1
当线程A申请到锁1准备申请锁2时,线程B已申请到锁2准备申请锁1,这时两个线程都会因为申请锁失败而陷入阻塞,并且无法释放锁,进入死锁状态
产生死锁的条件:
同步: 在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,这就叫做同步
竞态条件: 因为时序问题,而导致程序异常,我们称之为竞态条件
条件变量是利用线程间共享的全局变量进行同步的一种机制,条件变量是用来描述某种资源是否就绪的一种数据化描述
条件变量主要包括两个动作:
条件变量通常需要配合互斥锁一起使用
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参数:
返回值:条件变量初始化成功返回0,失败返回错误码
使用pthread_cond_init()函数初始化条件的方式被称为动态分配,还可以使用静态分配进行初始化,即下面这种方式:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_destroy(pthread_cond_t *cond);
参数cond:需要销毁的条件变量的地址
返回值:条件变量销毁成功返回0,失败返回错误码
注意:使用PTHREAD_COND_INITIALIZER初始化的条件变量不需要销毁
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
参数:
返回值:函数调用成功返回0,失败返回错误码
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
参数cond:唤醒在cond条件变量下等待的线程
返回值:函数调用成功返回0,失败返回错误码
#include<iostream>
using namespace std;
#include<pthread.h>
#include<string>
#include<vector>
#include<unistd.h>
const int NUM=4;
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
class ThreadDate
{
public:
ThreadDate(string name,pthread_mutex_t* lock)
{
_name=name;
_lock=lock;
}
public:
string _name;
pthread_mutex_t* _lock;
};
int Apples=10;
int flag=NUM;
void* GetApple(void* args)
{
ThreadDate* td=static_cast<ThreadDate*>(args);
while(1)
{
pthread_mutex_lock(td->_lock);
pthread_cond_wait(&cond,td->_lock);//线程在等待队列的时候会自动释放锁
if(Apples>0)
{
cout<<td->_name<<"get a apple,apple number"<<Apples--<<endl;
pthread_mutex_unlock(td->_lock);
}
else
{
pthread_mutex_unlock(td->_lock);
break;
}
}
cout<<td->_name<<" "<<"quit!"<<endl;
pthread_mutex_lock(td->_lock);
pthread_cond_wait(&cond,td->_lock);
flag--;
pthread_mutex_unlock(td->_lock);
return nullptr;
}
int main()
{
pthread_mutex_t lock;
pthread_mutex_init(&lock,nullptr);
vector<pthread_t> tids;
vector<ThreadDate*> tds;
for(int i=0;i<NUM;i++)
{
string name="thread"+to_string(i);
ThreadDate* td=new ThreadDate(name,&lock);
tds.push_back(td);
pthread_t tid;
pthread_create(&tid,nullptr,GetApple,tds[i]);
tids.push_back(tid);
}
sleep(3);
while(flag)
{
pthread_cond_signal(&cond);
sleep(1);
}
for(int i=0;i<NUM;i++)
{
pthread_join(tids[i],nullptr);
delete tds[i];
}
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
return 0;
}