下面我们通过实现一个简单的多线程抢票逻辑,来学习一下有关多线程的知识以及一些重要事项。
// 操作共享变量会有问题的售票系统代码
#include
#include
#include
#include
#include
int ticket = 100;
void *route(void *arg)
{
char *id = (char*)arg;
while ( 1 )
{
if ( ticket > 0 )
{
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
}
else
{
break;
}
}
}
int main( void )
{
pthread_t t1, t2, t3, t4;
pthread_create(&t1, NULL, route, "thread 1");
pthread_create(&t2, NULL, route, "thread 2");
pthread_create(&t3, NULL, route, "thread 3");
pthread_create(&t4, NULL, route, "thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_join(t4, NULL);
return 0;
}
执行结果:
一次执行结果:
thread 4 sells ticket:100
...
thread 4 sells ticket:1
thread 2 sells ticket:0
thread 1 sells ticket:-1
thread 3 sells ticket:-2
从运行结果可以看出,这个抢票逻辑是有问题的,因为票数为0了还在竞争墙皮安排,那么为什么可能无法获得正确的争取结果呢?
取出ticket--部分的汇编代码
objdump -d a.out > test.objdump
152 40064b: 8b 05 e3 04 20 00 mov 0x2004e3(%rip),%eax # 600b34 <ticket>
153 400651: 83 e8 01 sub $0x1,%eax
154 400654: 89 05 da 04 20 00 mov %eax,0x2004da(%rip) # 600b34 <ticket>
– 操作并不是原子操作,而是对应三条汇编指令:
要解决以上问题,需要做到三点:
要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。
初始化互斥量有两种方法:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
2.方法2,动态分配:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t
*restrict attr);
参数:
mutex:要初始化的互斥量
attr:NULL
销毁互斥量需要注意:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);//加锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);//解锁
返回值:成功返回0,失败返回错误号
调用 pthread_ lock 时,可能会遇到以下情况:
关于锁(互斥量)的函数接口学完之后,我们来一起改进上面的多线程抢票代码:
#include
#include
#include
#include
using namespace std;
//临界资源
int tickets = 1000;//全局变量,共享对象
//如果将锁定义成全局,则可以采用宏函数的方式对锁进行初始化
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
class TData
{
public:
TData(const string& name,pthread_mutex_t* mutex)
:_name(name)
,_pmutex(mutex)
{}
~TData()
{}
public:
string _name;//线程名称
pthread_mutex_t* _pmutex;//锁指针
};
void* threadRoutine(void* args)
{
TData* td = static_cast<TData*>(args);
while(true)
{
//对临界区进行加锁
pthread_mutex_lock(td->_pmutex);
if(tickets > 0)
{
//在睡眠的这2000us期间,就会导致多线程争先访问临界区
usleep(2000);
cout << td->_name << "get a ticket: " << tickets-- << endl;//临界区
pthread_mutex_unlock(td->_pmutex);
}
else
{
pthread_mutex_unlock(td->_pmutex);
break;
}
//抢完一张票之后,还有后序动作
//usleep(13);
}
return nullptr;
}
int main()
{
pthread_mutex_t mutex;//定义锁
pthread_mutex_init(&mutex,nullptr);//初始化锁
pthread_t tids[4];
int n = sizeof(tids)/sizeof(tids[0]);
for(int i=0;i<n;i++)
{
char name[64];
snprintf(name,64,"thread-%d",i+1);
TData* td = new TData(name,&mutex);
//创建线程
pthread_create(tids+i,nullptr,threadRoutine,td);
}
for(int i=0;i<n;i++)
{
//等待线程
pthread_join(tids[i],nullptr);
}
//销毁锁
pthread_mutex_destroy(&mutex);
return 0;
}
makefile文件:
threadTest:mythread.cc
g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
rm -f threadTest
运行结果:现在的结果就没有出现数据不一致,抢到只剩0张票还再强的问题了。
细节: