c++多线程——互斥锁

简介: c++多线程——互斥锁

C++中多线程的实现方式有多种,其中可以使用操作系统相关的线程API,如在Linux上,可以使用pthread库;也可以使用boost::thread库或者自从C++ 11开始支持的std::thread1。

pthread库是POSIX线程库,是一套线程API,它定义了一套标准的线程操作函数,可以在多种平台上使用。boost::thread库是一个跨平台的C++多线程库,它提供了一些高级的线程操作接口。std::thread是C++11标准中提供的多线程库,它提供了一些高级的线程操作接口1。

互斥锁

在线程里也有这么一把锁——互斥锁(mutex),互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。

互斥锁的操作流程如下:

  1. 在访问共享资源后临界区域前,对互斥锁进行加锁;
  2. 在访问完成后释放互斥锁导上的锁。在访问完成后释放互斥锁导上的锁;
  3. 对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。 函数接口:

cpp

复制代码

# include <pthread.h>
# include <time.h>
// 初始化一个互斥锁。
int pthread_mutex_init(pthread_mutex_t *mutex,
const pthread_mutexattr_t*attr);
// 对互斥锁上锁,若互斥锁已经上锁,则调用者一直阻塞,
// 直到互斥锁解锁后再上锁。
int pthread_mutex_lock(pthread_mutex_t *mutex);
// 调用该函数时,若互斥锁未加锁,则上锁,返回 0;
// 若互斥锁已加锁,则函数直接返回失败,即 EBUSY。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
// 当线程试图获取一个已加锁的互斥量时,pthread_mutex_timedlock 互斥量
// 原语允许绑定线程阻塞时间。即非阻塞加锁互斥量。
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
const struct timespec*restrict abs_timeout);
// 对指定的互斥锁解锁。
int pthread_mutex_unlock(pthread_mutex_t *mutex);
// 销毁指定的一个互斥锁。互斥锁在使用完毕后,
// 必须要对互斥锁进行销毁,以释放资源。
int pthread_mutex_destroy(pthread_mutex_t *mutex);

样例使用

cpp

复制代码

/*
* mutexcnt.c - 加上互斥锁(mutex lock)的多线程同步计数器
* 两个线程并发的给共享变量自增,观察是否有BUG
 */
# include <stdio.h>
# include <stdlib.h>
# include <unistd.h>
# include <pthread.h>
void *thread(void*vargp);          /*Thread函数声明*/
/*共享的全局变量*/
volatile long long cnt = 0;               /*计数器*/
pthread_mutex_t count_mutex;        /*互斥锁声明*/
int main(int argc, char **argv)
{
    long long niters;                     /* 单线程的累加次数 */
    pthread_t tid1, tid2;
    /* 检查输入格式 */
    if (argc != 2) { 
 printf("输入格式: %s <次数>\n", argv[0]);
 exit(0);
    }
    niters = atol(argv[1]);
    
    /* 互斥锁初始化 */
    pthread_mutex_init(&count_mutex,NULL);    
    /* 创建两个线程去执行thread函数,参数为niters */
    pthread_create(&tid1, NULL, thread, &niters);
    pthread_create(&tid2, NULL, thread, &niters);
    /* 等待两个线程并发的执行结束 */
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    /* 检查计数器的值是否正确 */
    if (cnt != (2 * niters))
 printf("发现BUG! cnt=%lld\n", cnt);
    else
 printf("结果正确。 cnt=%lld\n", cnt);
    exit(0);
}
/*线程函数*/
void *thread(void*vargp)
{
    long long i, niters = *((long long*)vargp);
    for (i = 0; i < niters; i++){
        pthread_mutex_lock(&count_mutex);   //进入区
        cnt++;  //临界区
        pthread_mutex_unlock(&count_mutex); //退出区
    }
       
    return NULL;
}

自旋锁

参考www.cnblogs.com/cxuanBlog/p…

自旋锁的定义:当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会等待,间隔一段时间后会再次尝试获取。这种采用循环加锁 -> 等待的机制被称为自旋锁(spinlock)。

代码应用

cpp

复制代码

/* 
 * spincnt.c - 加上自旋锁(spin lock)的多线程同步计数器
 * 两个线程并发的给共享变量自增,观察是否有BUG
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void *thread(void *vargp);          /* Thread函数声明 */
/* 共享的全局变量 */
volatile long long cnt = 0;               /* 计数器 */
pthread_spinlock_t count_spinlock;  /* 自旋锁声明 */
int main(int argc, char **argv) 
{
    long long niters;                     /* 单线程的累加次数 */
    pthread_t tid1, tid2;
    
    /* 检查输入格式 */
    if (argc != 2) { 
 printf("输入格式: %s <次数>\n", argv[0]);
 exit(0);
    }
    niters = atol(argv[1]);
    
    /* 互斥锁初始化 */
    pthread_spin_init(&count_spinlock,0);    
    /* 创建两个线程去执行thread函数,参数为niters */
    pthread_create(&tid1, NULL, thread, &niters);
    pthread_create(&tid2, NULL, thread, &niters);
    /* 等待两个线程并发的执行结束 */
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    /* 检查计数器的值是否正确 */
    if (cnt != (2 * niters))
 printf("发现BUG! cnt=%lld\n", cnt);
    else
 printf("结果正确。 cnt=%lld\n", cnt);
    exit(0);
}
/* 线程函数 */
void *thread(void *vargp) 
{
    long long i, niters = *((long long *)vargp);
    for (i = 0; i < niters; i++){
        pthread_spin_lock(&count_spinlock);   //进入区
        cnt++;  //临界区
        pthread_spin_unlock(&count_spinlock); //退出区
    }
        
    return NULL;
}

读写锁

参考

zhuanlan.zhihu.com/p/62363777zhuanlan.zhihu.com/p/135983375

概念:允许多个读出,但只允许一个写入的需求。

读写锁与互斥量类似,不过读写锁允许更改的并行性,也叫共享互斥锁。

互斥量要么是锁住状态,要么就是不加锁状态,而且一次只有一个线程可以对其加锁。 读写锁可以有3种状态:读模式下加锁状态、写模式加锁状态、不加锁状态。

条件变量

与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直 到某特殊情况发生为止。通常条件变量和互斥锁同时使用。

条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步 的一种机制,主要包括两个动作:

一个线程等待"条件变量的条件成立"而挂起

另一个线程使 “条件成立”(给出条件成立信号)

  • 好像就是生产者和消费者

条件变量使用步骤:

  1. 初始化:init()或者pthread_cond_tcond=PTHREAD_COND_INITIALIER;属性置为NULL;
  2. 等待条件成立:pthread_wait,pthread_timewait.wait()释放锁,并阻塞等待条件变量为真 timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait);
  3. 激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
  4. 清除条件变量:destroy;无线程等待,否则返回EBUSY清除条件变量:destroy;无线程等待,否则返回EBUSY

cpp

复制代码

# include <pthread.h>
// 初始化条件变量
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t*cond_attr);
// 阻塞等待
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t*mutex);
// 超时等待
int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex*mutex,const timespec *abstime);
// 解除所有线程的阻塞
int pthread_cond_destroy(pthread_cond_t *cond);
// 至少唤醒一个等待该条件的线程
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒等待该条件的所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);

参考代码

打印10次ABC

cpp

复制代码

void *print(void *thread_id)
{
    char id = *(char *)thread_id;
    for (int i = 0; i < 10; ++i)
    {
        pthread_mutex_lock(&mutex); //互斥锁
        while (id != current_thread)
        {
            pthread_cond_wait(&cond, &mutex); // 使当前线程进入等待状态,等待条件变量满足特定条件。
        }
        printf("current_thread%c tid = %lld\n", id,pthread_self());
        current_thread = (current_thread - 'A' + 1) % 3 + 'A';
        pthread_cond_broadcast(&cond); // 唤醒所有等待该条件变量的线程。
        pthread_mutex_unlock(&mutex);
    }
    pthread_exit(NULL);
}

信号量

编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则可以访问,否则将阻塞。

PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。

PV成对出现


相关文章
|
22天前
|
分布式计算 并行计算 安全
在Python Web开发中,Python的全局解释器锁(Global Interpreter Lock,简称GIL)是一个核心概念,它直接影响了Python程序在多线程环境下的执行效率和性能表现
【6月更文挑战第30天】Python的GIL是CPython中的全局锁,限制了多线程并行执行,尤其是在多核CPU上。GIL确保同一时间仅有一个线程执行Python字节码,导致CPU密集型任务时多线程无法充分利用多核,反而可能因上下文切换降低性能。然而,I/O密集型任务仍能受益于线程交替执行。为利用多核,开发者常选择多进程、异步IO或使用不受GIL限制的Python实现。在Web开发中,理解GIL对于优化并发性能至关重要。
41 0
|
3天前
|
安全 算法 Java
Java 中的并发控制:锁与线程安全
在 Java 的并发编程领域,理解并正确使用锁机制是实现线程安全的关键。本文深入探讨了 Java 中各种锁的概念、用途以及它们如何帮助开发者管理并发状态。从内置的同步关键字到显式的 Lock 接口,再到原子变量和并发集合,本文旨在为读者提供一个全面的锁和线程安全的知识框架。通过具体示例和最佳实践,我们展示了如何在多线程环境中保持数据的一致性和完整性,同时避免常见的并发问题,如死锁和竞态条件。无论你是 Java 并发编程的新手还是有经验的开发者,这篇文章都将帮助你更好地理解和应用 Java 的并发控制机制。
|
19天前
|
调度 C语言
深入浅出:C语言线程以及线程锁
线程锁的基本思想是,只有一个线程能持有锁,其他试图获取锁的线程将被阻塞,直到锁被释放。这样,锁就确保了在任何时刻,只有一个线程能够访问临界区(即需要保护的代码段或数据),从而保证了数据的完整性和一致性。 线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一个进程可以包含一个或多个线程,而每个线程都有自己的指令指针和寄存器状态,它们共享进程的资源,如内存空间、文件句柄和网络连接等。 线程锁的概念
|
26天前
|
安全 C++
C++一分钟之-互斥锁与条件变量
【6月更文挑战第26天】在C++并发编程中,`std::mutex`提供互斥访问,防止数据竞争,而`std::condition_variable`用于线程间的同步协调。通过`lock_guard`和`unique_lock`防止忘记解锁,避免死锁。条件变量需配合锁使用,确保在正确条件下唤醒线程,注意虚假唤醒和无条件通知。生产者-消费者模型展示了它们的应用。正确使用这些工具能解决同步问题,提升并发性能和可靠性。
24 4
|
26天前
|
存储 设计模式 安全
C++一分钟之-并发编程基础:线程与std::thread
【6月更文挑战第26天】C++11的`std::thread`简化了多线程编程,允许并发执行任务以提升效率。文中介绍了创建线程的基本方法,包括使用函数和lambda表达式,并强调了数据竞争、线程生命周期管理及异常安全等关键问题。通过示例展示了如何用互斥锁避免数据竞争,还提及了线程属性定制、线程局部存储和同步工具。理解并发编程的挑战与解决方案是提升程序性能的关键。
38 3
|
25天前
|
安全 程序员 C++
C++一分钟之-原子操作与线程安全
【6月更文挑战第27天】**C++的`std::atomic`提供线程安全的原子操作,解决多线程数据竞争。涵盖原子操作概念、应用、问题与对策。例如,用于计数器、标志位,但选择数据类型、内存顺序及操作组合需谨慎。正确使用能避免锁,提升并发性能。代码示例展示自旋锁和线程安全计数。了解并恰当运用原子操作至关重要。**
28 1
|
10天前
|
安全 算法 Linux
【Linux】线程安全——补充|互斥、锁|同步、条件变量(下)
【Linux】线程安全——补充|互斥、锁|同步、条件变量(下)
19 0
|
10天前
|
存储 安全 Linux
【Linux】线程安全——补充|互斥、锁|同步、条件变量(上)
【Linux】线程安全——补充|互斥、锁|同步、条件变量(上)
16 0
|
12天前
|
存储 缓存 Java
Java面试题:解释Java中的内存屏障的作用,解释Java中的线程局部变量(ThreadLocal)的作用和使用场景,解释Java中的锁优化,并讨论乐观锁和悲观锁的区别
Java面试题:解释Java中的内存屏障的作用,解释Java中的线程局部变量(ThreadLocal)的作用和使用场景,解释Java中的锁优化,并讨论乐观锁和悲观锁的区别
13 0
|
12天前
|
安全 Java
Java多线程中的锁机制:深入解析synchronized与ReentrantLock
Java多线程中的锁机制:深入解析synchronized与ReentrantLock
12 0