Linux —— 线程互斥
1. 临界资源与临界区
- 临界资源: 指的是多个线程或进程共享的资源,例如全局变量、文件、数据库等。由于这些资源的共享,可能会导致数据不一致或程序崩溃。
- 临界区: 是指访问临界资源的代码段。 为了保护临界资源,必须控制对临界区的访问,确保在任何时刻只有一个线程或进程可以进入临界区。
2. 互斥的定义
互斥是一种同步机制,旨在确保同一时刻只有一个执行流(线程或者进程)可以进入临界区。
互斥通常通过互斥量(mutex)来实现。 互斥量是一种锁,线程或进程在访问临界资源之前需要获取这个锁,完成后释放它。
3. 原子性
原子性通俗讲就是指某个操作要么完全执行,要么就完全不执行,不会被其他的进程或线程打断。
原子性操作对于保证数据的一致性和安全性至关重要。比如:在抢票软件的抢票过程中,如果没有原子性则可能出现售出的票数大于总票数的情况。
4. 互斥量(Mutex)
- 互斥量的使用:
- 初始化: 只是用互斥量之前,必须对其进行初始化。可以使用
pthread_mutex_init
函数来进行初始化。 - 加锁: 线程或进程在进入临界区之前,需要调用
pthread_mutex_lock
来获取互斥量的锁。如果锁已经被其他的线程占用了,当前线程将被阻塞,知道锁被释放。 - 解锁: 完成对临界资源的访问之后,必须调用
pthread_mutex_unlock
来释放互斥量的锁。以允许其他线程访问临界区。 - 销毁: 在不再需要互斥量的时候,应该调用
pthread_mutex_destroy
来销毁互斥量,释放有关的资源。
- 初始化: 只是用互斥量之前,必须对其进行初始化。可以使用
5. 互斥的实现示例
下面简单使用自己封装的pthread库来进行演示:
//Thread.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
namespace ThreadMoudle
{
typedef void (*func_t)(const std::string &name); // 函数指针
class Thread
{
public:
void Excute()
{
std::cout << _name << " is running ..." << std::endl;
_isrunning = true;
_func(_name);
_isrunning = false;
}
public:
Thread(std::string name, func_t func)
: _name(name), _func(func)
{
std::cout << "create " << name << " done ..." << std::endl;
}
static void *ThreadRountine(void *args)
{
Thread *self = static_cast<Thread *>(args);
self->Excute();
return nullptr;
}
bool Start()
{
int n = ::pthread_create(&_tid, nullptr, ThreadRountine, this);
if (n != 0)
{
return false;
}
return true;
}
std::string Status()
{
if (_isrunning)
{
return "running...";
}
else
{
return "sleep...";
}
}
void Stop()
{
if (_isrunning)
{
::pthread_cancel(_tid);
_isrunning = false;
std::cout << _name << " Stop..." << std::endl;
}
}
void Join()
{
::pthread_join(_tid, nullptr);
std::cout << _name << " Joined..." << std::endl;
}
std::string Name()
{
return _name;
}
~Thread()
{
}
private:
std::string _name;
pthread_t _tid;
bool _isrunning;
func_t _func; // 回调函数
};
}
简单写一个抢票代码,总共有10000张票,创建出4个线程来同时抢票:
#include <iostream>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include "Thread.hpp"
using namespace ThreadMoudle;
static int ticket = 10000;
void route(const std::string &name)
{
while(true)
{
if(ticket > 0)
{
usleep(1000);
printf("%s get a ticket: %d \n ",name.c_str(),ticket);
ticket--;
}
else
{
break;
}
}
}
int main ()
{
Thread t1("thread-1", route);
Thread t2("thread-2", route);
Thread t3("thread-3", route);
Thread t4("thread-4", route);
t1.Start();
t2.Start();
t3.Start();
t4.Start();
t1.Join();
t2.Join();
t3.Join();
t4.Join();
return 0;
}
我们可以看到出现了抢到负数的情况。这是为什么呢?
- 这是因为在多线程环境中,操作系统会在多个线程之间进行切换和调度,每个线程都有其自己的程序计数器和寄存器,用于记录当前执行的位置和状态。操作系统会通过某种调度算法来决定哪个线程获得CPU时间片。
- 然而当多个线程访问同一个共享资源的时候,他们共同会读取和修改该资源。
假设有两个线程A和B,他们都要访问ticket
变量。线程A和B的执行过程如下:
- 线程A获取
ticket
的值,发现是1,同时线程B也获取ticket
的值,发现同意是2。- 线程A将
ticket
的值减 1 ,结果变为0。- 但是在线程B也将进行减操作的时候,此时ticket已经变为0了,再
--
就变成了负数。
在
- -
的实现中,并不是直接- -
的,而是分为三步,1. 重读数据, 2.- -
数据, 3.写回数据。
所以才会出现ticket
为负数的情况。
进行加锁处理:
//Thread.hpp
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
namespace ThreadMoudle
{
class ThreadDate
{
public:
ThreadDate(const std::string & name,pthread_mutex_t *lock)
:_name(name),_lock(lock)
{
}
public:
std::string _name;
pthread_mutex_t * _lock;
};
typedef void (*func_t)(ThreadDate* td); // 函数指针
class Thread
{
public:
void Excute()
{
std::cout << _name << " is running ..." << std::endl;
_isrunning = true;
_func(_td);
_isrunning = false;
}
public:
Thread(std::string name, func_t func,ThreadDate* td)
: _name(name), _func(func),_td(td)
{
std::cout << "create " << name << " done ..." << std::endl;
}
static void *ThreadRountine(void *args)
{
Thread *self = static_cast<Thread *>(args);
self->Excute();
return nullptr;
}
bool Start()
{
int n = ::pthread_create(&_tid, nullptr, ThreadRountine, this);
if (n != 0)
{
return false;
}
return true;
}
std::string Status()
{
if (_isrunning)
{
return "running...";
}
else
{
return "sleep...";
}
}
void Stop()
{
if (_isrunning)
{
::pthread_cancel(_tid);
_isrunning = false;
std::cout << _name << " Stop..." << std::endl;
}
}
void Join()
{
::pthread_join(_tid, nullptr);
std::cout << _name << " Joined..." << std::endl;
delete _td;
}
std::string Name()
{
return _name;
}
~Thread()
{
}
private:
std::string _name;
pthread_t _tid;
bool _isrunning;
func_t _func; // 回调函数
ThreadDate* _td;
};
}
//LockGuard.hpp
#pragma once
#include <pthread.h>
class LockGuard
{
public:
LockGuard(pthread_mutex_t * mutex)
:_mutex(mutex)
{
pthread_mutex_lock(_mutex);
}
~LockGuard()
{
pthread_mutex_unlock(_mutex);
}
private:
pthread_mutex_t * _mutex;
};
//main.cc
#include <iostream>
#include <vector>
#include <cstdio>
#include <unistd.h>
#include "Thread.hpp"
#include "LockGuard.hpp"
using namespace ThreadMoudle;
static int ticket = 10000;
// pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;
// void route(ThreadDate *td)
// {
// std::cout << td->_name << " : " << "mutex address: " << td->_lock << std::endl;
// sleep(1);
// while (true)
// {
// ptherad_mutex_lock(td->_lock);
// if (ticket > 0)
// {
// usleep(1000);
// printf("%s get a ticket: %d \n ", name.c_str(), ticket);
// ticket--;
// }
// else
// {
// break;
// }
// }
// }
void route(ThreadDate *td)
{
while(true)
{
LockGuard lockguard(td->_lock);
if(ticket > 0)
{
usleep(1000);
printf("who %s, get a tickdt: %d\n",td->_name.c_str(),ticket);
ticket--;
}
else
{
break;
}
}
}
static int threadnum = 4;
int main()
{
// Thread t1("thread-1", route);
// Thread t2("thread-2", route);
// Thread t3("thread-3", route);
// Thread t4("thread-4", route);
pthread_mutex_t mutex;
pthread_mutex_init(&mutex,nullptr);
std::vector<Thread> threads;
for (int i = 0; i < threadnum; i++)
{
std::string name = "thread" + std::to_string(i+1);
ThreadDate* td = new ThreadDate(name,&mutex);
threads.emplace_back(name,route,td);
}
for(auto &thread:threads)
{
thread.Start();
}
for(auto &thread:threads)
{
thread.Join();
}
pthread_mutex_destroy(&mutex);
return 0;
}
以上使用了POSIX线程(pthreads)模拟了C++的多线程模块,定义了一个
Thread
类来封装线程的管理,以及使用一个用于自动互斥锁和解锁的LockGuard
类,以确保线程的安全。
main函数中初始化一个互斥锁并且创建多个线程,这些线程执行一个共享的函数route
,该函数递减共享的票务计数器,同时确保使用LockGuard
的互斥。
每个线程都会打印其名称和票的编号,知道票数被抢光。