Linux多线程编程:同步与互斥机制深度解析
在多线程编程中,线程间的同步与互斥是保证数据安全和程序正确性的核心。本文以Linux环境为例,深入剖析线程互斥与同步的原理、常见问题及解决方案,涵盖互斥量、条件变量、信号量、生产者消费者模型等关键内容,并结合Java、Python、C++、JavaScript、Go等语言对比,帮助读者构建扎实的并发编程基础。
1. 线程互斥:为什么需要互斥量?当多个线程同时访问共享资源(如全局变量)时,由于操作的非原子性,可能导致数据不一致。例如,对变量a--的操作并非一条CPU指令,而是分为拷贝、计算、写回三步。若两个线程同时拷贝a=0进行计算,结果均为-1,而非预期的-2。这就是典型的竞态条件。
关键概念:
临界资源:一次只能由一个线程访问的资源(如共享变量)。临界区:线程中访问临界资源的代码段。互斥:确保任何时刻只有一个线程进入临界区。原子性:不可被中断的操作,只有完成和未开始两种状态。在Java、Python、C++中,局部变量(如栈内变量)是线程私有的,不存在安全问题。但全局或静态变量需要互斥量(Mutex)来保护。初始化互斥量:
//静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
//动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const
pthread_mutexattr_t *restrict attr);
//参数:
//mutex:要初始化的互斥量
//attr:NULL
⚠️ 注意:使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量无需销毁;不要销毁已加锁的互斥量;已销毁的互斥量应确保后续无线程尝试加锁。
销毁互斥量:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
2. 互斥量的正确使用与常见误区互斥量加锁和解锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号
调用pthread_mutex_lock时:
若互斥量未锁,则锁定并返回成功。若已锁或竞争失败,线程阻塞等待解锁。然而,即使加了锁,也可能出现问题。例如,判断条件a>0时,若最后一次a=1已在计算中,其他线程可能误入临界区。正确的做法是将判断与操作都纳入临界区:
#include
#include
#include
using namespace std;
int a=10000000;
pthread_mutex_t _mutex;
void* fun(void*s){
while(true){
pthread_mutex_lock(&_mutex);
if(a>0){
--a;
}
else{
pthread_mutex_unlock(&_mutex);
break;
}
pthread_mutex_unlock(&_mutex);
}
return nullptr;
}
int main(){
pthread_mutex_init(&_mutex,nullptr);
vector ids;
for(int x=0;x<10;++x){
pthread_t id;
pthread_create(&id,nullptr,fun,nullptr);
ids.emplace_back(move(id));
}
for(int x=0;x<10;++x){
pthread_join(ids[x],nullptr);
}
cout<<"a:"<
在C++11中,标准库已封装std::mutex,但理解底层原理(如POSIX互斥量)有助于排查复杂问题。
3. 线程同步:条件变量与竞态条件互斥解决了资源竞争,但无法控制线程执行顺序。例如,消费者访问空队列时,应等待生产者添加数据,而非轮询。此时需要条件变量实现同步。
同步:在保证数据安全的前提下,让线程按特定顺序访问资源,避免饥饿。
条件变量函数:
初始化:int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t
*restrict attr);
//参数:
//cond:要初始化的条件变量
//attr:NULL销毁:int pthread_cond_destroy(pthread_cond_t *cond)等待:int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict
mutex);
//参数:
//cond:要在这个条件变量上等待
//mutex:互斥量,后⾯详细解释唤醒:int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);底层原理:条件变量内部维护一个等待队列。调用pthread_cond_wait时,线程释放锁、加入队列并阻塞;被唤醒后重新竞争锁。示例代码:
pthread_mutex_t _mutex;
pthread_cond_t _cond;
#include
void* test(void*str){
string name =to_string(reinterpret_cast(str));
while(true){
pthread_mutex_lock(&_mutex);
pthread_cond_wait(&_cond,&_mutex);
cout<<"name:"< ids;
for (long long x = 0; x < 10; ++x)
{
pthread_t id;
pthread_create(&id, nullptr, test,(void*)x);
ids.emplace_back(move(id));
}
for(int x=0;x<100;++x){
pthread_cond_signal(&_cond);
}
for (int x = 0; x < 10; ++x)
{
pthread_join(ids[x], nullptr);
}
cout << "a:" << a << endl;
return 0;
}
⚠️ 虚假唤醒:即使无信号,线程也可能从等待中返回(POSIX标准允许)。因此,应使用while循环包裹条件判断,而非if:
#include
#include
pthread_mutex_t _mutex;
pthread_cond_t _cond;
size_t num=0;
std::atomic flat =true;
#include
void* test(void*str){
string name =to_string(reinterpret_cast(str));
while(true){
pthread_mutex_lock(&_mutex);
if(flat){
pthread_cond_wait(&_cond,&_mutex);
}
if(!flat){
pthread_mutex_unlock(&_mutex);
break;
}
cout<<"name:"<
int main()
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond,nullptr);
vector ids;
for (long long x = 0; x < 100; ++x)
{
pthread_t id;
pthread_create(&id, nullptr, test,(void*)x);
ids.emplace_back(move(id));
}
int loop=0;
for(int x=0;x<50;++x){
++loop;
pthread_cond_signal(&_cond);
this_thread::sleep_for(chrono::milliseconds(2));
}
cout<
4. 生产者消费者模型:从阻塞队列到环形队列生产者消费者模型通过中间缓冲区解耦生产与消费,提升系统吞吐量。经典实现:
阻塞队列:使用条件变量控制队列满/空状态。环形队列:使用信号量控制元素数量。阻塞队列实现:
#include
#include
#include
#include
#include
#include
#include
#include
#include
class lockGuard
{
public:
lockGuard(pthread_mutex_t &x) : _mutex(x)
{
pthread_mutex_lock(&_mutex);
}
~lockGuard()
{
pthread_mutex_unlock(&_mutex);
}
private:
pthread_mutex_t &_mutex;
};
template
class blockQueue
{
public:
blockQueue(size_t size) : _capacity(size)
{
pthread_cond_init(&_productor, nullptr);
pthread_cond_init(&_consumer, nullptr);
pthread_mutex_init(&_mutex, nullptr);
}
~blockQueue(){
pthread_cond_destroy(&_productor);
pthread_cond_destroy(&_consumer);
pthread_mutex_destroy(&_mutex);
}
void push(const T &val)
{
lockGuard k(_mutex);
while (_queue.size() >= _capacity && _isrunning)
{
pthread_cond_wait(&_productor, &_mutex);
}
if (_queue.size() < _capacity && _isrunning)
{
_queue.emplace(val);
pthread_cond_signal(&_consumer);
}
}
T pop()
{
T ret=-1;
lockGuard k(_mutex);
while (_queue.empty() && _isrunning)
{
pthread_cond_wait(&_consumer, &_mutex);
}
if (!_queue.empty() && _isrunning)
{
ret = std::move(_queue.front());
_queue.pop();
pthread_cond_signal(&_productor);
}
return ret;
}
size_t size()
{
lockGuard k(_mutex);
return _queue.size();
}
void stop()
{
lockGuard k(_mutex);
_isrunning = false;
pthread_cond_broadcast(&_consumer);
pthread_cond_broadcast(&_productor);
}
void run()
{
lockGuard k(_mutex);
_isrunning = true;
pthread_cond_broadcast(&_consumer);
pthread_cond_broadcast(&_productor);
}
bool isrunning()
{
lockGuard k(_mutex);
return _isrunning;
}
private:
std::queue _queue;
pthread_cond_t _productor, _consumer;
pthread_mutex_t _mutex;
size_t _capacity;
bool _isrunning = true;
};
pthread_mutex_t print_lock = PTHREAD_MUTEX_INITIALIZER;
void print(const std::string &x)
{
lockGuard k(print_lock);
std::cout << x << std::endl;
}
blockQueue bq(10);
void *Pop(void *)
{
while (bq.isrunning())
{
int a = bq.pop();
std::string buf("获取到");
buf += std::to_string(a);
print(buf);
print(std::to_string(bq.size()));
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
return nullptr;
}
void *Push(void *)
{
while (bq.isrunning())
{
int rad = rand() % 100;
std::string buf("放入");
buf += std::to_string(rad);
print(buf);
bq.push(rad);
print(std::to_string(bq.size()));
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
return nullptr;
}
int main()
{
srand((unsigned int)time(nullptr));
std::vector threads;
for (int x = 0; x < 5; ++x)
{
pthread_t t;
pthread_create(&t, nullptr, Pop, nullptr);
threads.emplace_back(t);
}
for (int x = 0; x < 5; ++x)
{
pthread_t t;
pthread_create(&t, nullptr, Push, nullptr);
threads.emplace_back(t);
}
std::this_thread::sleep_for(std::chrono::seconds(3));
bq.stop();
for (int x = 0; x < 10; ++x)
{
pthread_join(threads[x], nullptr);
}
return 0;
}
信号量是计数型同步原语,P操作(等待)减1,V操作(发布)加1,为0时阻塞。POSIX信号量函数:
初始化:#include
int sem_init(sem_t *sem, int pshared, unsigned int value);
//参数:
//pshared:0表⽰线程间共享,⾮零表⽰进程间共享
//value:信号量初始值销毁:int sem_destroy(sem_t *sem);等待://功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem);//P()发布://功能:发布信号量,表⽰资源使⽤完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()环形队列实现(数组模拟):
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
class lockGuard
{
public:
lockGuard(pthread_mutex_t &x) : _mutex(x)
{
pthread_mutex_lock(&_mutex);
}
~lockGuard()
{
pthread_mutex_unlock(&_mutex);
}
private:
pthread_mutex_t &_mutex;
};
pthread_mutex_t print_lock = PTHREAD_MUTEX_INITIALIZER;
void print(const std::string &x)
{
lockGuard k(print_lock);
std::cout << x << std::endl;
}
template
class ringQueue{
public:
ringQueue(size_t size)
:_vec(size,T()),
_capacity(size),
_productorPos(0),
_consumerPos(0)
{
sem_init(&_lastNum,0,0);
sem_init(&_emtpyNum,0,size);
pthread_mutex_init(&_consumerLock,nullptr);
pthread_mutex_init(&_productorLock,nullptr);
}
~ringQueue(){
sem_destroy(&_lastNum);
sem_destroy(&_emtpyNum);
pthread_mutex_destroy(&_consumerLock);
pthread_mutex_destroy(&_productorLock);
}
void push(const T&val){
sem_wait(&_emtpyNum);
lockGuard k(_productorLock);
_vec[_productorPos]=val;
_productorPos=(_productorPos+1)%_capacity;
sem_post(&_lastNum);
}
T pop(){
sem_wait(&_lastNum);
lockGuard k(_consumerLock);
T ret = move(_vec[_consumerPos]);
_vec[_consumerPos]=0;
_consumerPos=(_consumerPos+1)%_capacity;
sem_post(&_emtpyNum);
return ret;
}
private:
size_t _capacity;
size_t _productorPos;
size_t _consumerPos;
sem_t _lastNum;
sem_t _emtpyNum;
vector_vec;
pthread_mutex_t _consumerLock;
pthread_mutex_t _productorLock;
};
int main(){
std::srand(static_cast(std::time(nullptr)));
ringQueue ring(10);
vectorthreads;
for(int x=0;x<5;++x){
thread t([&](){
while(true){
int rd=std::rand()%100 +1;
string buf("插入:");
buf+=to_string(rd);
print(buf);
ring.push(rd);
this_thread::sleep_for(chrono::milliseconds(1000));
}
});
threads.emplace_back(move(t));
}
for(int x=0;x<5;++x){
thread t([&](){
while(true){
string buf("获取:");
buf+=to_string(ring.pop());
print(buf);
this_thread::sleep_for(chrono::milliseconds(1000));
}
});
threads.emplace_back(move(t));
}
for(int x=0;x<10;++x)threads[x].join();
return 0;
}
注意:sem_post必须在写入数据后调用,否则消费者可能读到垃圾数据。
5. 线程安全、可重入与死锁线程安全指多线程访问共享资源时,通过锁等机制保证正确性。可重入指同一函数被多个执行流调用,即使前一次未完成,也不会产生异常。两者并非等价:
线程安全不一定可重入(例如锁内被信号中断,导致死锁)。可重入不一定线程安全(未保护临界资源)。死锁的四个必要条件:
互斥条件:资源一次只能被一个线程使用。请求与保持:线程持有资源并等待其他资源。不剥夺:资源不能被强制剥夺。循环等待:多个线程形成环形等待链。避免死锁的策略:按固定顺序加锁、使用超时锁、避免嵌套锁等。
6. 其他锁机制与语言对比除互斥量外,常见锁类型:
悲观锁:假设数据会被修改,直接加锁。乐观锁:操作后对比版本号,避免ABA问题。读写锁:允许多个读线程,写线程独占。自旋锁:忙等待,适合短临界区。递归锁:允许同一线程多次加锁。超时锁:等待超时后放弃。不同语言对锁的封装:
Java:synchronized、ReentrantLock、ReadWriteLock。Python:threading.Lock、threading.RLock。C++:std::mutex、std::shared_mutex。Go:sync.Mutex、sync.RWMutex。JavaScript:单线程模型,但Web Workers使用Atomics对象。[AFFILIATE_SLOT_1] 如果你正在寻找深入学习并发编程的书籍或课程,这里有一个精心挑选的资源列表。
7. 总结线程同步与互斥是多线程编程的基石。通过互斥量保护临界区,条件变量控制执行顺序,信号量管理资源计数,开发者可以构建高效、安全的并发程序。理解死锁、虚假唤醒等陷阱,并合理选择锁类型,是进阶的关键。
[AFFILIATE_SLOT_2] 想获取更多技术干货?订阅我们的技术博客,定期推送高质量文章。