线程同步与互斥

这篇具有很好参考价值的文章主要介绍了线程同步与互斥。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言:基于多线程不安全并行抢票

一、线程互斥锁 mutex

1.1 加锁解锁处理多线程并发

 1.2 如何看待锁

1.3 如何理解加锁解锁的本质

1.4 C++RAII方格设计封装锁

前言:基于线程安全的不合理竞争资源

二、线程同步

1.1 线程同步处理抢票

1.2 如何理解"条件变量"

1.3 如何理解条件变量函数需要传锁参数


前言:基于多线程不安全并行抢票

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#define NUM 10
using namespace std;
int global_ticket = 10000;

void* GetTicket(void* args)
{
    char* pc =(char*)args;
    while(global_ticket > 0)
    {
        usleep(123);
        cout << pc <<" ticket= " << global_ticket--<<endl;
    }
    delete pc;
}

int main()
{
    for(int i=0;i<NUM;i++)
    {
        char* pc = new char[64];
        pthread_t tid;
        snprintf(pc,128,"thread %d get a ticket",i+1);
        pthread_create(&tid,nullptr,GetTicket,pc);
    }
    while(true);
    return 0;
}

线程同步与互斥,Linux,linux

发现结果和我们代码的预想不一样,出现了票已经售完却仍抢票的现象!

为什么会出现这种现象? 原来Linux线程是轻量级进程,是CPU调度的基本单位,所以每个线程在CPU中都有自己的时间片,所以线程会在CPU上不断的切换,这样有可能某个线程在运行函数过程中突然被截胡了!然后再轮到它的时候继续运行!那结合本次抢票逻辑,可能某个线程首先while条件满足了拥有了抢票资格,但是没有开始抢票就被截胡了,下次回来后它仍拥有资格拿到一张票,可是之前票已经被抢完了!所以他其实没有资格了,但是因为不安全访问全局变量,使得它可以继续拿到票!

这样因为线程的特性原因使得我们无法安全的访问一个全局共享变量!所以我们要使线程互斥访问该变量,当某一个线程访问的时候,其他线程无法干预截胡,该线程必须将自己的代码逻辑执行完或者不执行(要么不做,做就必须完成!) 这就叫线程的互斥!那些代码区域称作为临界区!


一、线程互斥锁 mutex

1.1 加锁解锁处理多线程并发

线程同步与互斥,Linux,linux

线程同步与互斥,Linux,linux


#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<vector>
using namespace std;

int tickets = 10000;

class Thread_data
{
public:
    string name_;
    pthread_mutex_t* mutex_;
};

void* pthread_route(void* args)
{
    Thread_data* pt = static_cast<Thread_data*>(args);
    while(true)
    {
        //加锁
        pthread_mutex_lock(pt->mutex_);
        if(tickets > 0)
        {
            cout << pt->name_<<"get a ticket, tickets = "<<--tickets <<endl;
            //解锁
            pthread_mutex_unlock(pt->mutex_);

            //!!!抢完票后的操作 如果没有这个步骤,该线程会一直占用CPU执行抢票逻辑!
            usleep(1234);
        }
        else
        {
            //注意这个逻辑里也要解锁!
            pthread_mutex_unlock(pt->mutex_);
            break;
        }
    }
    delete pt;
}

#define NUM 5
int main()
{
    vector<pthread_t> vp(NUM); 
    char buffer[64];
    //锁初始化
    //1.锁类型 + 初始化函数
    //2.全局变量 pthread_mutex_t mutex =宏(PTHREAD_MUTEX_INITIALIZER)
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex,nullptr);


    for(int i=0;i<NUM;i++)
    {
        pthread_t tid;
        snprintf(buffer,sizeof(buffer),"thread %d ",i+1);
        Thread_data* pt = new Thread_data();
        pt->mutex_ = &mutex;
        pt->name_ = buffer;
        pthread_create(&(vp[i]),nullptr,pthread_route,pt);
    }
    for(const auto& tid : vp)
    {
        pthread_join(tid,nullptr);
    }
    return 0;
}

线程同步与互斥,Linux,linux

现在结果看到不会出现票数为负数的情况,锁保护了我们的共享资源tickets! 


 1.2 如何看待锁

a.锁本身被线程共享,是一个共享资源,锁保护全局变量,锁本身也需要被保护

b.加锁和解锁的过程必须是安全的,也就说加锁的过程必须是原子性的!

c.如果锁申请成功,就继续向后执行,如果不成功则执行流阻塞!

d.谁持有锁,谁进入临界区!

1.3 如何理解加锁解锁的本质

首先理解这个过程我们首先要明确关于CPU的一个知识就是寄存器:

寄存器只有一套,但寄存器被线程所共享,寄存器存储的内容只被当前线程所拥有!

线程同步与互斥,Linux,linux

加锁的过程:

① 将内存中的线程变量al的值0放入CPU的寄存器内

② 然后将寄存器内的0与内存变量mutex值交换,其中mutex的为1,这样mutex = 0

③ 最后通过判断al在寄存器的值来判断是否能申请锁成功!>0 成功 else 不成功

此时当某个线程完成了前面两个步骤,其他线程申请锁时将等于0的mutex与al交换,al依旧是0所以他无法申请锁!这样就可以保证只有一个线程申请到锁!

解锁的过程:就是把寄存器al的1存入mutex中即可!

这里swap过程很重要,它是一条汇编完成!其本质是将共享变量放入到我们的上下文中!


1.4 C++RAII方格设计封装锁

//mutex.hpp
#include<iostream>
#include<pthread.h>


class Mutex
{
public:
    Mutex(pthread_mutex_t* lock_p = nullptr):lock_p_(lock_p)
    {}
    void lock()
    {
        if(lock_p_)
        pthread_mutex_lock(lock_p_);
    }
    void unlock()
    {
        if(lock_p_)
        pthread_mutex_unlock(lock_p_);
    }
private:
    pthread_mutex_t* lock_p_;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t* mutex):mutex_(mutex)
    {
        mutex_.lock();
    }
    ~LockGuard()
    {
        mutex_.unlock();
    }
private:
    Mutex mutex_;
};

💡该封装利用局部类变量的构造析构随着作用域自动调用使得加锁解锁自动调用! 


前言:基于线程安全的不合理竞争资源

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>
#include<vector>
#include"mutex.hpp"
using namespace std;

int tickets = 10000;

class Thread_data
{
public:
    string name_;
    pthread_mutex_t* mutex_;
};

void* pthread_route(void* args)
{
    Thread_data* pt = static_cast<Thread_data*>(args);
    while(true)
    {
        //加锁
        //pthread_mutex_lock(pt->mutex_);
        LockGuard lock(pt->mutex_);
        if(tickets > 0)
        {
            cout << pt->name_<<"get a ticket, tickets = "<<--tickets <<endl;
            //解锁
            //pthread_mutex_unlock(pt->mutex_);

            //!!!抢完票后的操作 如果没有这个步骤,该线程会一直占用CPU执行抢票逻辑!
        }
        else
        {
            //注意这个逻辑里也要解锁!
            //pthread_mutex_unlock(pt->mutex_);
            break;
        }
        usleep(1000);
    }
    delete pt;
}

#define NUM 5
int main()
{
    vector<pthread_t> vp(NUM); 
    char buffer[64];
    //锁初始化
    //1.锁类型 + 初始化函数
    //2.全局变量 pthread_mutex_t mutex =宏(PTHREAD_MUTEX_INITIALIZER)
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex,nullptr);


    for(int i=0;i<NUM;i++)
    {
        pthread_t tid;
        snprintf(buffer,sizeof(buffer),"thread %d ",i+1);
        Thread_data* pt = new Thread_data();
        pt->mutex_ = &mutex;
        pt->name_ = buffer;
        pthread_create(&(vp[i]),nullptr,pthread_route,pt);
    }
    for(const auto& tid : vp)
    {
        pthread_join(tid,nullptr);
    }
    return 0;
}

线程同步与互斥,Linux,linux

 可以肯定的是线程资源是安全的了,但是却是一个线程在疯狂抢票,这样的行为不是错误的,但不合理!我们需要每个线程都有机会,所以引入线程同步,利用同步"信号"与等待队列来实现线程公平的竞争!


二、线程同步

1.1 线程同步处理抢票

线程同步与互斥,Linux,linux

线程同步与互斥,Linux,linux

线程同步与互斥,Linux,linux


#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
using namespace std;

int tickets = 100;

//初始化锁和条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void *pthread_route(void *args)
{
    string name = (char *)args;
    while (true)
    {
        //加锁
        pthread_mutex_lock(&mutex);

        //条件等待信号唤醒
        pthread_cond_wait(&cond, &mutex);
        
        if (tickets > 0)
            cout << name << "get a ticket, tickets = " << --tickets << endl;
        pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    pthread_t tid1, tid2, tid3;

    pthread_create(&tid1, nullptr, pthread_route, (void *)"thread 1 ");
    pthread_create(&tid2, nullptr, pthread_route, (void *)"thread 2 ");
    pthread_create(&tid3, nullptr, pthread_route, (void *)"thread 3 ");

    // 主线程负责每隔一秒信号唤醒条件变量
    while (true)
    {
        sleep(1);
        pthread_cond_signal(&cond);
        cout << "main wake up a thread ..." << endl;
    }

    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);
    pthread_join(tid3, nullptr);
    return 0;
}

线程同步与互斥,Linux,linux


 1.2 如何理解"条件变量"

首先明确一下整个代码逻辑:
a.先加锁,保护临界资源

b.条件等待,等待条件满足信号唤醒,否则阻塞!

c.解锁

当线程条件阻塞,会被放入条件队列中等待!当有信号唤醒的时候,从队列中一个一个得出!

1.3 如何理解条件变量函数需要传锁参数

线程同步与互斥,Linux,linux

因为等待队列被线程共享,为了保证线程入队列安全,所以需要加锁保护线程安全! 文章来源地址https://www.toymoban.com/news/detail-688946.html

到了这里,关于线程同步与互斥的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux】多线程互斥与同步

    互斥 指的是一种机制,用于确保在同一时刻只有一个进程或线程能够访问共享资源或执行临界区代码。 互斥的目的是 防止多个并发执行的进程或线程访问共享资源时产生竞争条件,从而保证数据的一致性和正确性 ,下面我们来使用多线程来模拟实现一个抢票的场景,看看所

    2024年02月09日
    浏览(40)
  • Linux-线程的同步与互斥

    🚀 临界资源:多线程指行流共享的资源叫做临界资源。 🚀 临界区:每个线程内部访问临界资源的代码片段叫做临界区。 🚀 互斥:任何时刻,互斥保证只有一个指行流进入临界区,访问临界资源,通常是对临界区起保护作用。 🚀 原子性:不被任何调度所打断的操作,该

    2024年02月09日
    浏览(48)
  • 『Linux』第九讲:Linux多线程详解(三)_ 线程互斥 | 线程同步

    「前言」文章是关于Linux多线程方面的知识,上一篇是 Linux多线程详解(二),今天这篇是 Linux多线程详解(三),内容大致是线程互斥与线程同步,讲解下面开始! 「归属专栏」Linux系统编程 「主页链接」个人主页 「笔者」枫叶先生(fy) 「枫叶先生有点文青病」「每篇一句

    2024年02月02日
    浏览(74)
  • 【关于Linux中----线程互斥与同步】

    先来用代码模拟一个抢票的场景,四个线程不停地抢票,一共有1000张票,抢完为止,代码如下: 执行结果如下: 可以看到,最后出现了票数为负数的情况,很显然这是错误的,是不应该出现的。 为什么会出现这种情况? 首先要明确,上述的几个线程是不能同时执行抢票的

    2023年04月08日
    浏览(43)
  • 【Linux】多线程2——线程互斥与同步/多线程应用

    💭上文主要介绍了多线程之间的独立资源,本文将详细介绍多线程之间的 共享资源 存在的问题和解决方法。 intro 多线程共享进程地址空间,包括创建的全局变量、堆、动态库等。下面是基于全局变量实现的一个多线程抢票的demo。 发现错误:线程抢到负数编号的票,为什么

    2024年02月10日
    浏览(43)
  • 【Linux】多线程 --- 线程同步与互斥+生产消费模型

    人生总是那么痛苦吗?还是只有小时候是这样? —总是如此 1. 假设现在有一份共享资源tickets,如果我们想让多个线程都对这个资源进行操作,也就是tickets- -的操作,但下面两份代码分别出现了不同的结果,上面代码并没有出现问题,而下面代码却出现了票为负数的情况,这

    2024年02月06日
    浏览(45)
  • Linux pthread线程操作 和 线程同步与互斥操作

    在Linux系统中玩线程,使用pthread,这篇博客记录如何 创建线程 和 使用线程 和线程的 同步 与 互斥 。 还有一份nginx线程池的代码供大家阅读学习! 目录 一、简介 什么是线程 线程的优点、缺点 线程的应用场合 二、线程的使用 1.  创建线程 - pthread_create 2.  线程的终止 - pt

    2024年02月02日
    浏览(31)
  • 【Linux】多线程02 --- 线程的同步互斥问题及生产消费模型

    🍎 作者: 阿润菜菜 📖 专栏: Linux系统编程 线程同步互斥问题是指多线程程序中,如何保证共享资源的正确访问和线程间的协作。 因为线程互斥是实现线程同步的基础和前提,我们先讲解线程互斥问题。 在多线程中,假设我们有一个黄牛抢票的代码,其中有一份共享资源

    2024年02月08日
    浏览(44)
  • 一文搞定Linux线程间通讯 / 线程同步方式-互斥锁、读写锁、自旋锁、信号量、条件变量、信号等等

    目录 线程间通讯 / 线程同步方式 锁机制 互斥锁(Mutex) 读写锁(rwlock) 自旋锁(spin) 信号量机制(Semaphore) 条件变量机制 信号(Signal) 线程间通讯 / 线程同步方式 p.s 以下有很多段落是直接引用,没有使用 markdown 的 “引用” 格式,出处均已放出。 参考 / 引用: 100as

    2024年02月10日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包