一个简单案例理解为什么在多线程的应用中要使用锁

简介: 一个简单案例理解为什么在多线程的应用中要使用锁

需求:使用10个线程,同时对一个值count进行加一操作,每个线程对count加100000次,最终使得count=1000000

第一版代码:不加锁

lock.c

#include<stdio.h>
#include<pthread.h>
#define THREAD_COUNT 10
void *thread_callback(void *arg){
  int *pcount=(int*)arg;
  int i=0;
  while(i++ < 100000){
    (*pcount)++;
    usleep(1);//休眠一毫秒
  }
}
int main(){
  pthread_t threadid[THREAD_COUNT]={0};//定义多线程
  int i=0;
  int count=0;
  for(i=0;i<THREAD_COUNT;i++){
    pthread_create(&threadid[i],NULL,thread_callback,&count);
  }
    //每隔一秒打印一次count值
  for(i=0;i<100;i++){
    printf("count: %d\n",count);
    sleep(1);//休眠一秒
  }
  return 0; 
}

在Linux环境下编译执行:

编译:  gcc -o lock lock.c -pthread

执行: ./lock

运行结果:

在不加锁的情况之下,使用多线程无法满足需求,即count值无法加到1000000.

从代码分析唯一能改变count值得代码是:count++,源代码中为:(*pcount)++

count++转化为汇编语言为:

 

正常情况(预期情况):每一个线程的count++都执行完再执行另外一个线程的count++

eax:寄存器

 

不正常情况(实际情况):当前线程中count++没完全执行完就跳转到另外一个线程执行

 

线程在执行代码的过程中被打断,这样所带来的的结果就是两个线程中执行两次count++,但count实际值只加了1,使得结果不能达到我们所预期的1000000值.

解决方法:使用互斥锁,或自旋锁,或原子操作。

第二版代码:这里使用互斥锁(mutex)

#include<stdio.h>
#include<pthread.h>
#define THREAD_COUNT 10
pthread_mutex_t mutex;//定义一个锁
void *thread_callback(void *arg){
  int *pcount=(int*)arg;
  int i=0;
  while(i++ < 100000){
#if 0
    (*pcount)++;
#else
    pthread_mutex_lock(&mutex);//上锁
    (*pcount)++;
    pthread_mutex_unlock(&mutex);//解锁
#endif
    usleep(1);//休眠一毫秒
  }
}
int main(){
  pthread_t threadid[THREAD_COUNT]={0};//定义多线程
  pthread_mutex_init(&mutex,NULL);//初始化锁,NULL为系统默认
  int i=0;
  int count=0;
  for(i=0;i<THREAD_COUNT;i++){
    pthread_create(&threadid[i],NULL,thread_callback,&count);
  }
  for(i=0;i<100;i++){
    printf("count: %d\n",count);
    sleep(1);//休眠一秒
  }
  return 0; 
}

运行结果:

 

 

 

 

 

 

目录
相关文章
|
1月前
|
安全 Java 编译器
线程安全问题和锁
本文详细介绍了线程的状态及其转换,包括新建、就绪、等待、超时等待、阻塞和终止状态,并通过示例说明了各状态的特点。接着,文章深入探讨了线程安全问题,分析了多线程环境下变量修改引发的数据异常,并通过使用 `synchronized` 关键字和 `volatile` 解决内存可见性问题。最后,文章讲解了锁的概念,包括同步代码块、同步方法以及 `Lock` 接口,并讨论了死锁现象及其产生的原因与解决方案。
59 10
线程安全问题和锁
|
12天前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
30 2
|
2天前
|
运维 API 计算机视觉
深度解密协程锁、信号量以及线程锁的实现原理
深度解密协程锁、信号量以及线程锁的实现原理
8 1
|
26天前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
16天前
|
负载均衡 Java 调度
探索Python的并发编程:线程与进程的比较与应用
本文旨在深入探讨Python中的并发编程,重点比较线程与进程的异同、适用场景及实现方法。通过分析GIL对线程并发的影响,以及进程间通信的成本,我们将揭示何时选择线程或进程更为合理。同时,文章将提供实用的代码示例,帮助读者更好地理解并运用这些概念,以提升多任务处理的效率和性能。
|
17天前
|
Java 开发者
Java中的多线程基础与应用
【9月更文挑战第22天】在Java的世界中,多线程是一块基石,它支撑着现代并发编程的大厦。本文将深入浅出地介绍Java中多线程的基本概念、创建方法以及常见的应用场景,帮助读者理解并掌握这一核心技术。
|
21天前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
在Android开发中,为应对复杂应用场景和繁重计算任务,多线程与异步编程成为保证UI流畅性的关键。本文将介绍Android中的多线程基础,包括Thread、Handler、Looper、AsyncTask及ExecutorService等,并通过示例代码展示其实用性。AsyncTask适用于简单后台操作,而ExecutorService则能更好地管理复杂并发任务。合理运用这些技术,可显著提升应用性能和用户体验,避免内存泄漏和线程安全问题,确保UI更新顺畅。
51 5
|
1月前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
2月前
|
数据采集 存储 安全
如何确保Python Queue的线程和进程安全性:使用锁的技巧
本文探讨了在Python爬虫技术中使用锁来保障Queue(队列)的线程和进程安全性。通过分析`queue.Queue`及`multiprocessing.Queue`的基本线程与进程安全特性,文章指出在特定场景下使用锁的重要性。文中还提供了一个综合示例,该示例利用亿牛云爬虫代理服务、多线程技术和锁机制,实现了高效且安全的网页数据采集流程。示例涵盖了代理IP、User-Agent和Cookie的设置,以及如何使用BeautifulSoup解析HTML内容并将其保存为文档。通过这种方式,不仅提高了数据采集效率,还有效避免了并发环境下的数据竞争问题。
如何确保Python Queue的线程和进程安全性:使用锁的技巧
|
27天前
|
Java 调度 开发者
Java中的多线程基础及其应用
【9月更文挑战第13天】本文将深入探讨Java中的多线程概念,从基本理论到实际应用,带你一步步了解如何有效使用多线程来提升程序的性能。我们将通过实际代码示例,展示如何在Java中创建和管理线程,以及如何利用线程池优化资源管理。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧,帮助你更好地理解和应用多线程编程。