Linux系统编程-(pthread)线程通信(互斥锁)

简介: 这篇文章介绍Linux下线程同步与互斥机制--互斥锁,在多线程并发的时候,都会出现多个消费者取数据的情况,这种时候数据都需要进行保护,比如: 火车票售票系统、汽车票售票系统一样,总票数是固定的,但是购票的终端非常多。

这篇文章介绍Linux下线程同步与互斥机制--互斥锁,在多线程并发的时候,都会出现多个消费者取数据的情况,这种时候数据都需要进行保护,比如: 火车票售票系统、汽车票售票系统一样,总票数是固定的,但是购票的终端非常多。

互斥锁就是用来保护某一个资源不能同时被2个或者2个以上的线程使用。

为什么需要加锁?
就是因为多个线程共用进程的资源,要访问的是公共区间时(全局变量),当一个线程访问的时候,需要加上锁以防止另外的线程对它进行访问,以实现资源的独占。在一个时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程才能够对共享资源进行操作。若其他线程希望上锁一个已经上锁了的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。

## 1. 互斥锁介绍

在编程中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。

Linux系统下定义了一套专门用于线程互斥的mutex函数。

mutex 是一种简单的加锁的方法来控制对共享资源的存取,这个互斥锁只有两种状态(上锁和解锁),可以把互斥锁看作某种意义上的全局变量。

总结: 互斥锁可以保护某个资源同时只能被一个线程所使用。

2. 互斥锁相关的函数

#include <pthread.h>
//销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex); 
//初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
//上锁: 阻塞方式
int pthread_mutex_lock(pthread_mutex_t *mutex);
//上锁: 非阻塞方式
int pthread_mutex_trylock(pthread_mutex_t *mutex);
//解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);

说明:  对于Linux下的信号量/读写锁文件进行编译,需要在编译选项中指明-D_GNU_SOURCE
否则用gcc编译就会出现
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP未声明(在此函数内第一次使用) 这样的提示。
例如: $ gcc app.c -lpthread -D_GNU_SOURCE

2.1 初始化互斥锁

头文件
#include <pthread.h>
定义函数
int pthread_mutex_init( pthread_mutex_t *mutex, const pthread_mutex_attr_t* attr );
函数说明
该函数初始化一个互斥体变量,如果参数attr 为NULL,则互斥体变量mutex 使用默认的属性。

2.2 销毁互斥锁

头文件
#include <pthread.h>
定义函数
int pthread_mutex_destroy ( pthread_mutex_t *mutex );
函数说明
该函数用来释放分配给参数mutex 的资源。
返回值
调用成功时返回值为 0, 否则返回一个非0 的错误代码。

2.3 上锁

头文件
#include <pthread.h>
定义函数
int pthread_mutex_lock( pthread_mutex_t *mutex );
函数说明
该函数用来锁住互斥体变量。如果参数mutex 所指的互斥体已经被锁住了,那么发出调用的线程将被阻塞直到其他线程对mutex 解锁。
如果上锁成功,将返回0。

2.4 尝试上锁-非阻塞

表头文件
#include <pthread.h>
定义函数
int pthread_mutex_trylock( pthread_t *mutex );
函数说明
该函数用来锁住mutex 所指定的互斥体,但不阻塞。
返回值
如果该互斥体已经被上锁,该调用不会阻塞等待,而会返回一个错误代码。
如果上锁成功,将返回0.

2.5 解锁

头文件
#include <pthread.h>
定义函数
int pthread_mutex_unlock( pthread_mutex_t *mutex );
函数说明
该函数用来对一个互斥体解锁。如果当前线程拥有参数mutex 所指定的互斥体,该调用将该互斥体解锁。
如果解锁成功,将返回0.
说明: 对同一个锁多次解锁没有叠加效果,如果锁是上锁状态,那么多次解锁也只有一次有效。

3. 互斥锁框架运用模型

pthread_mutex_t mutex;
void 线程1(void)
{
    while(1)
    {
         //上锁
        pthread_mutex_lock(&mutex);
        .....主体代码......
        //解锁
        pthread_mutex_unlock(&mutex);
    }
}
void 线程2(void)
{
    while(1)
    {
         //上锁
        pthread_mutex_lock(&mutex);
        .....主体代码......
        //解锁
        pthread_mutex_unlock(&mutex);
    }
}

int main(void)
{
    //初始化互斥锁
    pthread_mutex_init(&mutex,NULL);
    .....主体代码......
    //销毁互斥锁
    pthread_mutex_destroy(&mutex);
}

4. 案例代码: 对公共函数上锁保护

下面代码是两个线程同时调用了一个打印函数,分别打印: “123” “456”。

void print(char *p)
{
    while(*p!='\0')
    {
        printf("%c",*p++);
        sleep(1);
    }
}

void *thread1_func(void *arg)
{
    print("123\n");
}

void *thread2_func(void *arg)
{
    print("456\n");
}

如果不保护,默认的打印结果:

[wbyq@wbyq linux-share-dir]$ ./a.out   412536  

预期的结果应该是打印123\456连续在一起的,对于这种情况,就可以加锁进行保护。

上锁的示例代码:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <pthread.h>

pthread_mutex_t mutex;

void print(char *p)
{
    while(*p!='\0')
    {
        printf("%c",*p++);
        sleep(1);
    }
}

/*
线程工作函数
*/
void *thread_work_func(void *dev)
{
    while(1)
    {
        //上锁
        pthread_mutex_lock(&mutex);
        print("123\n");
        //解锁
        pthread_mutex_unlock(&mutex);
        usleep(1000*10);
    }
}

/*
线程工作函数
*/
void *thread_work_func2(void *dev)
{
    //上锁
    pthread_mutex_lock(&mutex);
    print("456\n");
    //解锁
    pthread_mutex_unlock(&mutex);
    usleep(1000*10);
}

int main(int argc,char **argv)
{   
    //初始化互斥锁
    pthread_mutex_init(&mutex,NULL);

    /*1. 创建子线程1*/
    pthread_t thread_id;
    if(pthread_create(&thread_id,NULL,thread_work_func,NULL)!=0)
    {
        printf("子线程1创建失败.\n");
        return -1;
    }
    /*2. 创建子线程2*/
    pthread_t thread_id2;
    if(pthread_create(&thread_id2,NULL,thread_work_func2,NULL)!=0)
    {
        printf("子线程2创建失败.\n");
        return -1;
    }

    /*3. 等待线程的介绍*/
    pthread_join(thread_id,NULL);
    pthread_join(thread_id2,NULL);

    //销毁互斥锁
    pthread_mutex_destroy(&mutex);
    return 0;
}

5. 案例代码: 模拟火车票售卖系统(保护同一个全局变量)

下面代码模拟一个火车票售卖系统,此处不加锁,可能会出现卖出负数票的情况。

#include <stdio.h>
#include <pthread.h>
int cnt = 121; //火车票,公共资源(全局)
void* pthread1(void* args)
{
    while(ticketcount > 0)
    {
        printf("窗口A开始售票,门票是:%d\n",cnt);
        sleep(2);
        cnt--;
        printf("窗口A售票结束,最后一张车票是:%d\n",cnt);
    }
}

void* pthread2(void* args)
{
    while(cnt > 0)
    {
        printf("窗口B开始售票,车票是:%d\n",cnt);
        sleep(2);
        cnt--;
        printf("窗口B售票结束,最后一张车票是:%d\n",cnt);
    }
}
int main()
{
    pthread_t pthid1 = 0;
    pthread_t pthid2 = 0;
    pthread_create(&pthid1,NULL,pthread1,NULL);
    pthread_create(&pthid2,NULL,pthread2,NULL);
    pthread_join(pthid1,NULL);
    pthread_join(pthid2,NULL);
    return 0;
}

加锁之后的火车售票系统

#include <stdio.h>
#include <pthread.h>
int cnt = 121;
pthread_mutex_t lock;
void* pthread1(void* args)
{
    while(1)
    {
        pthread_mutex_lock(&lock); //因为要访问全局的共享变量,所以就要加锁
        if(cnt > 0) //如果有票
        {
            printf("窗口A开始售票,车票是:%d\n",cnt);
            sleep(2);
            cnt--;
            printf("窗口A售票结束,最后一张车票是:%d\n",cnt);
        }
        else
        {
            pthread_mutex_unlock(&lock);
            pthread_exit(NULL);
        }
        pthread_mutex_unlock(&lock);
        sleep(1); //要放到锁的外面,让另一个有时间锁
    }
}
void* pthread2(void* args)
{
    while(1)
    {
        pthread_mutex_lock(&lock);
        if(cnt>0)
        {
            printf("窗口B开始售票--车票是:%d\n",cnt);
            sleep(2);
            cnt--;
            printf("窗口B售票结束,最后一张车票是:%d\n",cnt);
        }
        else
        {
            pthread_mutex_unlock(&lock);
            pthread_exit(NULL);
        }
        pthread_mutex_unlock(&lock);
        sleep(1);
    }
}
int main()
{
    pthread_t pthid1 = 0;
    pthread_t pthid2 = 0;
    //初始化锁
    pthread_mutex_init(&lock,NULL); 
    //创建线程
    pthread_create(&pthid1,NULL,pthread1,NULL);
    pthread_create(&pthid2,NULL,pthread2,NULL);
     //等待线程结束
    pthread_join(pthid1,NULL);
    pthread_join(pthid2,NULL);
     //销毁锁
    pthread_mutex_destroy(&lock);
    return 0;
}
目录
相关文章
|
1月前
|
消息中间件 Linux
Linux中的System V通信标准--共享内存、消息队列以及信号量
希望本文能帮助您更好地理解和应用System V IPC机制,构建高效的Linux应用程序。
135 48
|
1月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
57 17
|
1月前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
62 26
|
1月前
|
消息中间件 Linux C++
c++ linux通过实现独立进程之间的通信和传递字符串 demo
的进程间通信机制,适用于父子进程之间的数据传输。希望本文能帮助您更好地理解和应用Linux管道,提升开发效率。 在实际开发中,除了管道,还可以根据具体需求选择消息队列、共享内存、套接字等其他进程间通信方
70 16
|
3月前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
318 2
|
4月前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
4月前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
3月前
|
缓存 Java 调度
多线程编程核心:上下文切换深度解析
在现代计算机系统中,多线程编程已成为提高程序性能和响应速度的关键技术。然而,多线程编程中一个不可避免的概念就是上下文切换(Context Switching)。本文将深入探讨上下文切换的概念、原因、影响以及优化策略,帮助你在工作和学习中深入理解这一技术干货。
80 10
|
3月前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
3月前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
84 3