目录
什么是线程安全
概念
线程安全是指在多线程环境下,对共享资源的访问会导致数据不一致或者出现其他异常情况。线程安全通常是通过同步机制来实现的,常见的同步机制包括互斥锁、条件变量、信号量等。
Linux下线程安全的实现
常见线程安全的实现方法
- 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
- 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临界区。
- 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。
Linux线程互斥
背景概念
- 临界资源:多线程执行流共享的资源就叫做临界资源
- 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
- 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
- 原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成
互斥量
- 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
- 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
- 多个线程并发的操作共享变量,会带来一些问题。
要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。
锁
互斥量创建和初始化
#include<pthread.h>
//创建互斥量
pthread_mutex_t mutex;
//互斥量静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
//动态分配
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict
attr);
//互斥量销毁
//使用PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
//不要销毁一个已经加锁的互斥量
//已经销毁的互斥量,要确保后面不会有线程再尝试加锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//加锁,如果当前互斥量已经被其他线程锁定,则调用线程会被阻塞,直到互斥量被解锁。一旦成功获取锁,调用线程就可以访问被保护的资源。
int pthread_mutex_lock(pthread_mutex_t *mutex);
//该函数用于尝试非阻塞地锁定一个互斥量。如果当前互斥量已经被其他线程锁定,则调用线程不会被阻塞,而是立即返回一个失败的值,通常为 EBUSY 错误码(表示资源忙)。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
返回值
如果创建成功则返回0,失败则返回错误码
//解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
通过加锁实现线程安全
线程安全示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>
int ticket = 100;
pthread_mutex_t mutex;
void *route(void *arg)
{
char *id = (char *)arg;
while (1)
{
//加锁
pthread_mutex_lock(&mutex);
if (ticket > 0)
{
usleep(1000);
printf("%s sells ticket:%d\n", id, ticket);
ticket--;
//解锁
pthread_mutex_unlock(&mutex);
// sched_yield(); 放弃CPU
}
else
{
pthread_mutex_unlock(&mutex);
break;
}
}
}
int main(void)
{
pthread_t t1, t2, t3, t4;
pthread_mutex_init(&mutex, NULL);
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);
pthread_mutex_destroy(&mutex);
}
线程不安全的情况
- 不保护共享变量的函数
- 函数状态随着被调用,状态发生变化的函数
- 返回指向静态变量指针的函数
- 调用线程不安全函数的函数
常见的线程安全的情况
- 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
- 类或者接口对于线程来说都是原子操作
- 多个线程之间的切换不会导致该接口的执行结果存在二义性