首页 > 系统相关 >Linux线程互斥

Linux线程互斥

时间:2024-03-23 20:59:19浏览次数:22  
标签:加锁 Linux 互斥 临界 mutex pthread 线程

文章目录

Linux线程互斥

进程线程间的互斥相关背景概念

  • 临界资源:多线程执行流共享的资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
  • 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完 成

互斥量mutex

  • 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个 线程,其他线程无法获得这种变量。
  • 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之 间的交互。
  • 多个线程并发的操作共享变量,会带来一些问题。

例如:

//操作共享变量会有问题的售票系统代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
		
int ticket = 100;
void *route(void *arg)
{
    char *id = (char *)arg;
    while (1)
    {
        if (ticket > 0)
        {
            usleep(1000);
            printf("%s sells ticket:%d\n", id, ticket);
            ticket--;
        }
        else
        {
            break;
        }
    }
}
int main(void)
{
    pthread_t t1, t2, t3, t4;
    pthread_create(&t1, NULL, route, (void *)"thread 1");
    pthread_create(&t2, NULL, route, (void *)"thread 2");
    pthread_create(&t3, NULL, route, (void *)"thread 3");
    pthread_create(&t4, NULL, route, (void *)"thread 4");
    
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
}

该程序运行可能会产生如下结果:

在这里插入图片描述

造成此中情况的原因

  • if 语句判断条件为真以后,代码可以并发的切换到其他线程
  • ticket-- 操作本身就不是一个原子操作
  • usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段

要想解决多线程的数据不一致问题,就需要做到以下几点:

  • 代码必须要有互斥行为,当一个线程进入临界区执行代码时,不允许其他线程进入该临界区。
  • 如果有多个线程同时请求执行临界区代码,并且临界区没有线程在执行代码,那么只允许一个线程进入该临界区。
  • 如果线程不在临界区中执行代码,那么该线程不能阻止其他线程进入临界区。

其实做到上面三点只需要一把互斥锁( mutex ),将锁看作一个通行证,持有锁的线程才能进入临界区中执行代码,其他线程不持有锁,无法进入该临界区。

加锁本质就是让共享资源临界资源化,多个线程串行访问共享资源,从而保护共享资源的安全。

互斥锁本质上就是一个类(class pthread_mutex_t),可以构造对象pthread_mutex_t mutx,mutx就是互斥锁对象。

加锁可以让共享资源临界资源化,从而保护共享资源的安全,让多个线程串行访问共享资源。

mutex 接口

同创建线程一样,锁也需要创建,POSIX提供了锁的变量类型,如上面代码所示,其中mutext是互斥量

使用互斥量需要包含头文件:

#include<pthread.h>
初始化互斥量

初始化互斥量有两种方法:

  • 方法1,静态分配

    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
    //初始化全局锁
    
  • 方法2,动态分配

    pthread_mutex_init

    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict 
    attr); 
    //初始化局部锁
    
    //例:
    int main()
    {
        pthread_mutex_t mutex;
    	pthread_mutex_init(&mutex,nullptr);
    }
    

    参数

    • mutex:要初始化的互斥量
    • attr :NULL
销毁互斥量

销毁互斥量需要注意:

  • 使用 PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁

pthread_mutex_destroy

int pthread_mutex_destroy(pthread_mutex_t *mutex)
//例:
//pthread_mutex_destroy(&mutex);
加锁和解锁

pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);
//例:
//pthread_mutex_lock(&mutex);

调用 pthread_ lock 时,可能会遇到以下情况:

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量, 那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

pthread_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex);
//例:
//pthread_mutex_unlock(&mutex);

加锁解锁的原理

c/c++中加加和减减的操作并不是原子的,所以会导致多线程数据不一致的问题。

而为了能让加锁过程是原子的,在大多数体系结构了,都提供了swap或者xchange汇编指令,通过一条汇编指令来保证加锁的原子性。

pthread_mutex_lock(mutex);
//代码片段(原子的)
pthread_mutex_unlock(mutex);

在加锁与解锁间,就能保证中间的区域为临界区,里面的操作都是原子的

使用示例

再次模拟抢票系统

#include <iostream>
#include <unistd.h>
#include <vector>

#include "Thread.hpp"

using std::vector;

int ticket = 1000;                                 // 多线程可以直接访问
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 初始化全局锁,也可以是局部的

void GetTicket(pthread_mutex_t* mutex)
{
    while (1)
    {
        pthread_mutex_lock(mutex); // 加锁
        // 在同一时刻,只允许一个线程申请锁成功! 多个线程申请锁失败,失败的线程在mutex上阻塞等待
        if (ticket > 0)
        {                 // 在加锁的时候,线程可能被切换,但是线程在切换时带走了[钥匙],别的进程也无法进行修改
            usleep(1000); // 1000微秒
            cout << " get a ticket: " << ticket-- << endl;
            pthread_mutex_unlock(mutex); // 解锁
        }
        else
        {
            pthread_mutex_unlock(mutex); // 解锁
            break;
        }
    }

    // 模拟抢票,实际情况还有很多后续代码
}

// 应用方的文件
string GetThreadName()
{
    static int number = 1;
    char name[64];
    snprintf(name, sizeof name, "Thread-%d", number++);
    return name;
}

void Print(int NUM)
{
    while (NUM--)
    {
        cout << "hello world" << endl;
        sleep(1);
    }
}

int main()
{
    pthread_mutex_t mutex;             //创建局部锁
    pthread_mutex_init(&mutex,nullptr); //初始化局部锁

    string name1 = GetThreadName();
    Thread<pthread_mutex_t*> t1(GetTicket, name1, &mutex);

    string name2 = GetThreadName();
    Thread<pthread_mutex_t*> t2(GetTicket, name2, &mutex);

    string name3 = GetThreadName();
    Thread<pthread_mutex_t*> t3(GetTicket, name3, &mutex);

    string name4 = GetThreadName();
    Thread<pthread_mutex_t*> t4(GetTicket, name4, &mutex);

    t1.Start();
    t2.Start();
    t3.Start();
    t4.Start();

    t1.Join();
    t2.Join();
    t3.Join();
    t4.Join();

    pthread_mutex_destroy(&mutex);
}
#pragma once

#include<pthread.h>

class Mutex
{
    pthread_mutex_t *_mutex;
public:
    Mutex(pthread_mutex_t *__mutex):_mutex(__mutex)
    {

    }
    ~Mutex()
    {

    }

    void Lock()
    {
        pthread_mutex_lock(_mutex);
    }

    void Unlock()
    {
        pthread_mutex_unlock(_mutex);
    }

};

class LockGuard
{
    Mutex _Mutex;
public:
    LockGuard(pthread_mutex_t* __mutex)
    :_Mutex(__mutex)
    {
        _Mutex.Lock();
    }

    ~LockGuard()
    {
        _Mutex.Unlock();
    }
};

标签:加锁,Linux,互斥,临界,mutex,pthread,线程
From: https://blog.csdn.net/qq_72982923/article/details/136974852

相关文章

  • linux下的进程(二)
    进程间的通信目录进程间的通信信号信号由谁产生?信号的处理信号的捕获信号的发送发送多个信号信号集阻塞式等待信号信号什么是信号?信号是给程序提供一种可以处理异步事件的方法,它利用软中断实现。不能定义信号,所有信号都是由系统预定义的信号由谁产生?1.由sh......
  • SH文件从Window拷贝到Linux运行失败
    1.问题现象bash:./startup.sh:/bin/bash^M:解释器错误:没有那个文件或目录这个错误通常发生在尝试在Unix-like系统中执行脚本时,脚本文件的行尾结束符是Windows风格的CRLF(回车+换行,即\r\n),而不是Unix风格的LF(换行,即\n)。/bin/bash^M说明了这个问题,^M是字符\r的控制台输出表......
  • ftp多线程下载工具
    //代码类似https多线程下载,整体实现逻辑类似,区别比较大的是curl_opt的相关参数设置不一样#include<iostream>#include<fstream>#include<curl/curl.h>#include<pthread.h>#include<sys/mman.h>#include<sys/stat.h>#include<fcntl.h>#include<sys......
  • java多线程(超详细讲解)下篇
    本章继续讲多线程目录一、线程同步1、为什么需要线程同步二、如何实现线程同步1、同步代码块2、同步方法3、线程同步特征三、线程安全的类型1、ArrayList是常用的集合类型,它是否线程安全的呢?2、对比Hashtable和HashMap1、是否线程安全2、效率比较3、对比StringBuffe......
  • https多线程下载代码
    这里使用了curl网络库和使用多线程来下载对应https链接的文件对应的.h头文件:#pragmaonce#include<iostream>#include<fstream>#include<curl/curl.h>#include<pthread.h>#include<sys/mman.h>#include<sys/stat.h>#include<fcntl.h>#......
  • linux下的进程
    创建进程众所周知linux由unix发展而来,所以本文先就unix进程先论为快,unix的进程创建很特别,许多其它的操作系统都提供了产生(spawn)进程的机制:首先在新的地址空间里创建进程,读入可执行文件,最后开始执行。unix采用与众不同的实现方式:它把上述步骤分解到两个单独的函数中去执行f......
  • Linux--Flappy_bird实现
    目录voidhandler(intsig): mian:voidinit_curses()intset_timer(intms_t); 小鸟的操作: voidshow_pipe(): voidcreate_list()voidclear_pipe()voidmove_pipe(); test_bird.c完整代码:代码实现:#include<stdio.h>#include<curses.h>#include<signal.......
  • linux分卷压缩解压
    1.压缩:.首先是考虑压缩,无论在windosw还是linux中tar这个压缩工具都有(windows可以使用7z这个工具进行分卷tar的压缩,自行网络下载)7z安装好windows下右键菜单就可以找到linux下压缩命令:格式tarcvzf-filedir|split-d-b50m-filename样例:tarcvzf-./dir|split-d-......
  • Linux 创建用户不创建家目录,配置ssh密钥的方式
    创建用户不创建家目录useradd-Mtest-user创建ssh密钥对文件#一路会车使用默认值ssh-keygen-trsa-mPEM配置test-user使用密钥vim编辑sshd_config配置文件。vim/etc/ssh/sshd_config添加以下内容,AuthorizedKeysFile配置密钥(公钥)文件所属的位置。MatchUsercodi......
  • 本地主机连接Linux虚拟机中的mongodb,并使用studio 3T连接,同时项目启动连接mongodb刷新
    本部分只做个人纪录**1.安装mongodb**本部分为尚硅谷的电影推荐系统的文档,具体以实际存放位置为准//通过WGET下载Linux版本的MongoDB[bigdata@linux~]$wgethttps://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel62-3.4.3.tgz//将压缩包解压到指定目录[......