5. 多线程并发锁

简介: 5. 多线程并发锁

本文介绍了多线程并发下为了避免临界资源被抢占而出现的错误,引入了锁和原子操作 来解决。


一、问题分析

创建10个线程,每个线程实现往总进程加1万个数。则总进程会达到10万

#include<stdio.h>
#include <unistd.h>
#include<pthread.h>
#define THREAD_COUNT    10
void *thread_callback(void* arg){
    int *pcount=(int *)arg;
    int i=0;
    while(i++ <10000){
        (*pcount)++;
        //usleep(1)是一个系统调用函数,它的作用是让当前进程暂停执行一微秒(即等待一微秒
        usleep(1);
    }
}
int main(){
    pthread_t threadid[THREAD_COUNT]={0};
    int i=0;
    int count=0;
    //创建10个线程
    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(n)是一个系统调用函数,它的作用是让当前进程暂停执行n秒钟(即等待n秒钟)
        sleep(1);
    }
}

但实现过程中,会发现最后达不到100万。这种现象的原因在于count是一个临界资源,即多个线程共用一个变量。我们分析一下count++的汇编指令

mov count,eax;
inc eax;
mov eax,count;

实际中却可能出现如下异常情况

解决办法是加锁,即打包锁住count++的三个汇编指令,不可分开,其他线程不可抢占。


二、 锁和原子操作

把count的三条汇编指令变成一条指令,就不需要加锁。这种称为原子操作。


原子操作是指在并发环境下对共享资源进行访问或修改时,能够保证该操作具有不可分割性和独占性的一种操作方式。通常情况下,原子操作是由硬件级别提供支持的,可以确保在执行过程中不会被中断或者其他线程干扰,从而避免了竞态条件等问题的发生。

#include<stdio.h>
#include <unistd.h>
#include<pthread.h>
#define THREAD_COUNT    10
pthread_mutex_t mutex;
pthread_spinlock_t spinlock;
//对value加上add
int inc(int *value,int add){
    int old;
    //__asm__是GCC的内嵌汇编语言指令
    __asm__ volatile(
        "lock;xaddl %2,%1;"// %2的值+%1的值,赋给%1;同时,在执行该指令期间,CPU会对总线进行锁定(lock),以确保整个操作是原子性地完成。
        : "=a" (old)
        : "m" (*value),"a"(add)
        : "cc","memory"
    );
    return old;
}
void *thread_callback(void* arg){
    int *pcount=(int *)arg;
    int i=0;
    while(i++ <10000){
#if 0
    (*pcount)++;
#elif 0
    pthread_mutex_lock(&mutex);
    (*pcount)++;
    pthread_mutex_unlock(&mutex);
#elif 0
    pthread_spin_lock(&spinlock);
    (*pcount)++;
    pthread_spin_unlock(&spinlock);
#else
    inc(pcount,1);
#endif
    usleep(1);
    }
}
int main(){
    pthread_t threadid[THREAD_COUNT]={0};
    pthread_mutex_init(&mutex,NULL);//初始化mutex
    pthread_spin_init(&spinlock,PTHREAD_PROCESS_SHARED);//初始化spinlock,进程之间共享
    int i=0;
    int count=0;
    //创建10个线程
    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(n)是一个系统调用函数,它的作用是让当前进程暂停执行n秒钟(即等待n秒钟)
        sleep(1);
    }
}


三、相关知识点补充

1、创建进程

pthread_t是一个线程标识符,它可以唯一地标识一个线程。在多线程编程中,我们通常使用pthread_create()函数来创建一个新的线程,并且该函数会返回一个pthread_t类型的值,以便我们在后续代码中对这个新线程进行操作。


pthread_create()是一个函数,用于创建一个新的线程。其原型如下:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);

该函数接受四个参数:

  • thread:指向pthread_t类型变量的指针,用于存储新线程的标识符。
  • attr:指向pthread_attr_t类型变量的指针,用于设置新线程的属性(如栈大小、优先级等)。如果为NULL,则使用默认属性。
  • start_routine:指向新线程要执行的函数的指针。该函数必须返回void*类型,并且只能有一个参数,即对应于该函数调用时传递给它的参数。
  • arg: 传递给start_routine函数的参数。

例如,下面是一个简单的例子,演示了如何使用pthread_create()来创建一个新线程:

#include <pthread.h>
#include <stdio.h>
void* thread_func(void *arg) {
    printf("New thread created\n");
}
int main() {
    pthread_t tid;
    int ret = pthread_create(&tid, NULL, thread_func, NULL);
    if (ret != 0) {
        printf("Error creating thread\n");
        return -1;
    }
    // Wait for the new thread to complete
    pthread_join(tid, NULL);
    return 0;
}

在上述代码中,我们定义了一个名为“thread_func”的函数作为新线程要执行的内容。在主函数中,我们调用了pthread_create()函数来创建一个新线程,并将该函数的返回值存储到变量tid中。然后,我们通过调用pthread_join()函数等待新线程执行完毕。在新线程内部,我们只是简单地打印一条消息以表明它已经被创建成功了。


2,条件编译指令

#if 0
    (*pcount)++;
#else 
    pthread_mutex_lock(&mutex);
    (*pcount)++;
    pthread_mutex_unlock(&mutex);
#endif

这是一个预处理器指令,用于在编译时控制代码的执行。如果 #if 0 部分是注释掉的代码或者不需要执行的代码,则可以使用 #if 0 来暂时禁用它们,以免浪费系统资源和时间。类似地,#elif 0 可以用来替代一部分被注释掉的代码或者不需要执行的代码,而 #else 则是当所有上面的条件都不满足时要执行的语句块。


使用条件编译指令的好处是可以在编译时根据预处理器定义的宏来控制代码的执行,而不需要手动注释或取消注释一大段代码。这种方法在调试和测试代码时非常有用,因为可以轻松地开启或关闭某些特定功能的实现,从而简化了代码的维护和调试。

目录
相关文章
|
2月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
239 0
|
3月前
|
安全 Java 编译器
线程安全问题和锁
本文详细介绍了线程的状态及其转换,包括新建、就绪、等待、超时等待、阻塞和终止状态,并通过示例说明了各状态的特点。接着,文章深入探讨了线程安全问题,分析了多线程环境下变量修改引发的数据异常,并通过使用 `synchronized` 关键字和 `volatile` 解决内存可见性问题。最后,文章讲解了锁的概念,包括同步代码块、同步方法以及 `Lock` 接口,并讨论了死锁现象及其产生的原因与解决方案。
98 10
线程安全问题和锁
|
3月前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
50 2
|
1月前
|
安全
List并发线程安全问题
【10月更文挑战第21天】`List` 并发线程安全问题是多线程编程中一个非常重要的问题,需要我们认真对待和处理。只有通过不断地学习和实践,我们才能更好地掌握多线程编程的技巧和方法,提高程序的性能和稳定性。
203 59
|
2天前
|
Java 关系型数据库 MySQL
【JavaEE“多线程进阶”】——各种“锁”大总结
乐/悲观锁,轻/重量级锁,自旋锁,挂起等待锁,普通互斥锁,读写锁,公不公平锁,可不可重入锁,synchronized加锁三阶段过程,锁消除,锁粗化
|
1月前
|
供应链 安全 NoSQL
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
39 6
|
1月前
|
安全 Java
线程安全的艺术:确保并发程序的正确性
在多线程环境中,确保线程安全是编程中的一个核心挑战。线程安全问题可能导致数据不一致、程序崩溃甚至安全漏洞。本文将分享如何确保线程安全,探讨不同的技术策略和最佳实践。
41 6
|
1月前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
59 6
|
1月前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
1月前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####