文章目录
Linux线程互斥
进程线程间的互斥相关背景概念
- 临界资源:多线程执行流共享的资源就叫做临界资源
- 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
- 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
- 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完 成
互斥量mutex
- 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个 线程,其他线程无法获得这种变量。
- 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之 间的交互。
- 多个线程并发的操作共享变量,会带来一些问题。
例如:
//操作共享变量会有问题的售票系统代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
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, (void *)"thread 1");
pthread_create(&t2, NULL, route, (void *)"thread 2");
pthread_create(&t3, NULL, route, (void *)"thread 3");
pthread_create(&t4, NULL, route, (void *)"thread 4");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
}
该程序运行可能会产生如下结果:
造成此中情况的原因:
- if 语句判断条件为真以后,代码可以并发的切换到其他线程
- ticket-- 操作本身就不是一个原子操作
- usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
要想解决多线程的数据不一致问题,就需要做到以下几点:
- 代码必须要有互斥行为,当一个线程进入临界区执行代码时,不允许其他线程进入该临界区。
- 如果有多个线程同时请求执行临界区代码,并且临界区没有线程在执行代码,那么只允许一个线程进入该临界区。
- 如果线程不在临界区中执行代码,那么该线程不能阻止其他线程进入临界区。
其实做到上面三点只需要一把互斥锁( mutex ),将锁看作一个通行证,持有锁的线程才能进入临界区中执行代码,其他线程不持有锁,无法进入该临界区。
加锁本质就是让共享资源临界资源化,多个线程串行访问共享资源,从而保护共享资源的安全。
互斥锁本质上就是一个类(class pthread_mutex_t),可以构造对象pthread_mutex_t mutx,mutx就是互斥锁对象。
加锁可以让共享资源临界资源化,从而保护共享资源的安全,让多个线程串行访问共享资源。
mutex 接口
同创建线程一样,锁也需要创建,POSIX提供了锁的变量类型,如上面代码所示,其中mutext是互斥量
使用互斥量需要包含头文件:
#include<pthread.h>
初始化互斥量
初始化互斥量有两种方法:
-
方法1,静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER //初始化全局锁
-
方法2,动态分配
pthread_mutex_init
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); //初始化局部锁 //例: int main() { pthread_mutex_t mutex; pthread_mutex_init(&mutex,nullptr); }
参数:
- mutex:要初始化的互斥量
- attr :NULL
销毁互斥量
销毁互斥量需要注意:
- 使用 PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁
- 不要销毁一个已经加锁的互斥量
- 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
pthread_mutex_destroy
int pthread_mutex_destroy(pthread_mutex_t *mutex)
//例:
//pthread_mutex_destroy(&mutex);
加锁和解锁
pthread_mutex_lock
int pthread_mutex_lock(pthread_mutex_t *mutex);
//例:
//pthread_mutex_lock(&mutex);
调用 pthread_ lock 时,可能会遇到以下情况:
- 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
- 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量, 那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。
pthread_mutex_unlock
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//例:
//pthread_mutex_unlock(&mutex);
加锁解锁的原理
c/c++中加加和减减的操作并不是原子的,所以会导致多线程数据不一致的问题。
而为了能让加锁过程是原子的,在大多数体系结构了,都提供了swap或者xchange汇编指令,通过一条汇编指令来保证加锁的原子性。
pthread_mutex_lock(mutex);
//代码片段(原子的)
pthread_mutex_unlock(mutex);
在加锁与解锁间,就能保证中间的区域为临界区,里面的操作都是原子的
使用示例
再次模拟抢票系统
#include <iostream>
#include <unistd.h>
#include <vector>
#include "Thread.hpp"
using std::vector;
int ticket = 1000; // 多线程可以直接访问
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 初始化全局锁,也可以是局部的
void GetTicket(pthread_mutex_t* mutex)
{
while (1)
{
pthread_mutex_lock(mutex); // 加锁
// 在同一时刻,只允许一个线程申请锁成功! 多个线程申请锁失败,失败的线程在mutex上阻塞等待
if (ticket > 0)
{ // 在加锁的时候,线程可能被切换,但是线程在切换时带走了[钥匙],别的进程也无法进行修改
usleep(1000); // 1000微秒
cout << " get a ticket: " << ticket-- << endl;
pthread_mutex_unlock(mutex); // 解锁
}
else
{
pthread_mutex_unlock(mutex); // 解锁
break;
}
}
// 模拟抢票,实际情况还有很多后续代码
}
// 应用方的文件
string GetThreadName()
{
static int number = 1;
char name[64];
snprintf(name, sizeof name, "Thread-%d", number++);
return name;
}
void Print(int NUM)
{
while (NUM--)
{
cout << "hello world" << endl;
sleep(1);
}
}
int main()
{
pthread_mutex_t mutex; //创建局部锁
pthread_mutex_init(&mutex,nullptr); //初始化局部锁
string name1 = GetThreadName();
Thread<pthread_mutex_t*> t1(GetTicket, name1, &mutex);
string name2 = GetThreadName();
Thread<pthread_mutex_t*> t2(GetTicket, name2, &mutex);
string name3 = GetThreadName();
Thread<pthread_mutex_t*> t3(GetTicket, name3, &mutex);
string name4 = GetThreadName();
Thread<pthread_mutex_t*> t4(GetTicket, name4, &mutex);
t1.Start();
t2.Start();
t3.Start();
t4.Start();
t1.Join();
t2.Join();
t3.Join();
t4.Join();
pthread_mutex_destroy(&mutex);
}
#pragma once
#include<pthread.h>
class Mutex
{
pthread_mutex_t *_mutex;
public:
Mutex(pthread_mutex_t *__mutex):_mutex(__mutex)
{
}
~Mutex()
{
}
void Lock()
{
pthread_mutex_lock(_mutex);
}
void Unlock()
{
pthread_mutex_unlock(_mutex);
}
};
class LockGuard
{
Mutex _Mutex;
public:
LockGuard(pthread_mutex_t* __mutex)
:_Mutex(__mutex)
{
_Mutex.Lock();
}
~LockGuard()
{
_Mutex.Unlock();
}
};
标签:加锁,Linux,互斥,临界,mutex,pthread,线程
From: https://blog.csdn.net/qq_72982923/article/details/136974852