热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

关于线程安全的那些事

什么是线程安全?描述的是多个执行流之间对资源的访问操作的安全问题,线程安全就是线程之间对于资源的访问操作不会造成数据二义性1.线程不安全的现象编写了一个黄牛的抢票程序,黄牛就相当于


什么是线程安全?

描述的是多个执行流之间对资源的访问操作的安全问题,线程安全就是线程之间对于资源的访问操作不会造成数据二义性


1.线程不安全的现象

编写了一个黄牛的抢票程序,黄牛就相当于线程,票相当于临界资源(临界资源就是多个线程共同访问的资源)

#include
#include
#include


#define THREADCOUNT 4
int g_titcks = 100;
void* func(void* arg)
{
(void* ) arg;
while(1)
{
if( g_titcks > 0)
{
printf("i am work thread %p , i have titck %d\n",pthread_self(), g_titcks);
g_titcks--;
}
else break;
}
return NULL;
}
int main()
{
pthread_t tid[THREADCOUNT];
for(int i = 0; i {
int ret = pthread_create(&tid[i], NULL, func, NULL);
if(ret <0 )
{
perror("pthread_ create \n");
return -1;
}
}
for(int i = 0; i {
pthread_join(tid[i],NULL);
}
return 0;
}

规则:一张票只能被一个黄牛拿到

运行结果:


通过运行结果我们可以发现 有两个线程同时拿到了100这个票,这是不符合规则的,这也就是线程不安全的现象


2.线程不安全的原理

首先我们知道了线程不安全会导致程序结果出现二义性

举个栗子:

现在一个程序中有两个线程,线程A和线程B,并且有一个int类型的全局变量,值为10,线程A和线程B在各自的入口函数当中对全局变量进行++操作。

当线程A拥有cpu之后,对全局变量进行++操作,这个操作并非是原子操作,也就意味着线程A在执行++的过程中是有可能被打断的,假设线程A刚刚将全局变量的数值10读到cpu的寄存器中,就被切换出去了,这时候线程A的程序计数器当中保存下一条执行的指令就是对全局变量+1,上下文信息中保存寄存器的值也就是10,这两个东西在线程A再次拥有cpu时,恢复现场使用

这时候线程B拥有了cpu资源,对全局变量进行了++,并且将10加到了11,回写到内存中。

当线程A再次拥有cpu资源,恢复线程,继续往下执行,从寄存器中当中得到的值仍然是10,加完之后为11,回写到内存中也是11


3.如何解决线程不安全现象

互斥和同步


4.互斥

互斥就是保证在同一时间只能有一个线程对临界资源进行访问及操作确保线程安全

我们使用互斥锁来实现互斥,在使用互斥锁是要注意互斥锁本身也是一个资源,线程也需要先获取互斥锁从而在访问临界资源,多个线程想要保证互斥,必须都去获取互斥锁,否则就无法保证互斥


4.0互斥锁变量类型


pthread_mutex_t



4.1.互斥锁初始化

1.1 动态初始化


  int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);


 参数说明:

mutex:传入互斥锁的地址

attr:属性,一般传递NULL采用默认属性

1.2 静态初始化


pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;


PTHREAD_MUTEX_INITIALIZER :宏定义了一个结构体的值


4.2.加锁

1.1 阻塞的加锁接口


 int pthread_mutex_lock(pthread_mutex_t *mutex);


 mutex:传入互斥锁的地址

如果mutex中计数器值为1,则pthread_mutex_lock接口直接返回,表示加锁成功,同时mutex中的计数器的值会被置为0 

如果mutex中计数器值为0,则pthread_mutex_lock接口会阻塞在函数内部中,直到加锁成功

1.2 非阻塞加锁接口


 int pthread_mutex_trylock(pthread_mutex_t *mutex);


  mutex:传入互斥锁的地址

当mutex中的计数器值为1,则加锁成功直接返回 

当mutex中的计数器值为0,也会返回,但是这时候没有加锁成功,所以不能访问临界资源

1.3 带有超时时间的加锁接口 


int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
              const struct timespec *restrict abs_timeout); 


 mutex:传入互斥锁的地址,abs_timeout : 等待的时间

1.带有超时时间的加锁接口,当没有获取互斥锁时,会等待abs_timeout时间,在这段时间中加锁成功了,就直接返回,如果等待超时,就直接返回,但是表示加锁失败了,需要循环使用进行加锁

注意:非阻塞接口一般都是配合循环使用 


4.3.解锁


int pthread_mutex_unlock(pthread_mutex_t *mutex);


 mutex:传入互斥锁的地址

1.不管是使用哪个接口进行加锁的互斥锁都可以通过这个接口进行解锁

2.解锁的时候,会将互斥锁中的计时器的值置为1,表示其它线程可以获取这个互斥锁 


4.4.互斥锁的销毁


int pthread_mutex_destroy(pthread_mutex_t *mutex);


 mutex:传入互斥锁的地址

针对动态初始化的互斥锁进行销毁


4.5.使用互斥锁时应该注意的几点


  1.  在哪里初始化互斥锁
  2. 在哪里进行加锁
  3. 在哪进进行解锁
  4. 释放互斥锁

4.6.为什么互斥锁可以保证线程安全

线程在访问临界资源时,必须先获取到锁才能访问到临界资源,互斥锁的内部有一个0 / 1 计数器,当互斥锁内部的计数器为1时,表示线程可以获取到互斥锁,只有获取到互斥锁的线程才能访问临界资源,在获取到互斥锁后这个互斥锁中的计数器的值会被置为0,当互斥锁当中的计数器的值为0时,表示线程不能获取到这个互斥锁,就不能访问临界资源,保证了同一时间临界资源的唯一访问性,互斥锁本身也属于临界资源,但由于它当中计数器的值在变化的过程属于原子操作,也保证了同一时间临界资源的唯一访问性。


4.7.通过互斥实现线程安全

#include
#include
#include



#define THREADCOUNT 4
// 票
int g_titcks = 100;
//定义一个互斥锁‘
pthread_mutex_t g_lock;

void* func(void* arg)
{
(void* ) arg;
while(1)
{
//加锁
pthread_mutex_lock(&g_lock);
if( g_titcks > 0)
{
printf("i am work thread %p , i have titck %d\n",pthread_self(), g_titcks);
g_titcks--;
}
else
{
//解锁
pthread_mutex_unlock(&g_lock);
break;
}
//解锁
pthread_mutex_unlock(&g_lock);
}
return NULL;
}
int main()
{

pthread_t tid[THREADCOUNT];
//互斥锁初始化
pthread_mutex_init(&g_lock,NULL);
for(int i = 0; i {
int ret = pthread_create(&tid[i], NULL, func, NULL);
if(ret <0 )
{
perror("pthread_ create \n");
return -1;
}
}
for(int i = 0; i {
pthread_join(tid[i],NULL);
}
//互斥锁销毁
pthread_mutex_destroy(&g_lock);
return 0;
}



推荐阅读
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 如何在跨函数中使用内存?
    本文介绍了在跨函数中使用内存的方法,包括使用指针变量、动态分配内存和静态分配内存的区别。通过示例代码说明了如何正确地在不同函数中使用内存,并提醒程序员在使用动态分配内存时要手动释放内存,以防止内存泄漏。 ... [详细]
  • 本文介绍了C函数ispunct()的用法及示例代码。ispunct()函数用于检查传递的字符是否是标点符号,如果是标点符号则返回非零值,否则返回零。示例代码演示了如何使用ispunct()函数来判断字符是否为标点符号。 ... [详细]
  • 本文介绍了一个程序,可以输出1000内能被3整除且个位数为6的所有整数。程序使用了循环和条件判断语句来筛选符合条件的整数,并将其输出。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • 本文介绍了一个题目的解法,通过二分答案来解决问题,但困难在于如何进行检查。文章提供了一种逃逸方式,通过移动最慢的宿管来锁门时跑到更居中的位置,从而使所有合格的寝室都居中。文章还提到可以分开判断两边的情况,并使用前缀和的方式来求出在任意时刻能够到达宿管即将锁门的寝室的人数。最后,文章提到可以改成O(n)的直接枚举来解决问题。 ... [详细]
author-avatar
气质朱总_206
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有