C/C++多线程编程:互斥量

一、互斥量的创建销毁

1.介绍

        在多线程编程中,互斥量(Mutex,也称为互斥锁)是一种用于保护共享资源不被多个线程同时访问的工具。互斥量在使用前必须被初始化,使用完毕后必须被销毁。初始化和销毁互斥量的函数分别是pthread_mutex_initpthread_mutex_destroy

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

        这个函数将初始化一个互斥量,参数mutex是指向要初始化的互斥量的指针,attr是指向互斥量属性对象的指针,如果传递NULL,则使用默认的互斥量属性(通常是非递归、非错误检查的)。

int pthread_mutex_destroy(pthread_mutex_t *mutex);

        这个函数将销毁一个互斥量,参数mutex是指向要销毁的互斥量的指针。成功时,这个函数返回0,失败时返回一个错误码。

        然而,对于静态分配的互斥量,你也可以使用常量PTHREAD_MUTEX_INITIALIZER进行初始化,这种情况下不需要显式调用pthread_mutex_destroy函数。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

2.例子

#include <pthread.h>
#include <stdio.h>
​
pthread_mutex_t mutex;
​
void* thread_func(void* arg) {
    pthread_mutex_lock(&mutex);
    // 访问共享资源
    pthread_mutex_unlock(&mutex);
    return NULL;
}
​
int main() {
    pthread_t thread1, thread2;
​
    // 初始化互斥量
    pthread_mutex_init(&mutex, NULL);
​
    // 创建两个线程
    pthread_create(&thread1, NULL, thread_func, NULL);
    pthread_create(&thread2, NULL, thread_func, NULL);
​
    // 等待两个线程结束
    pthread_join(thread1, NULL);
    pthread_join(thread2, NULL);
​
    // 销毁互斥量
    pthread_mutex_destroy(&mutex);
​
    return 0;
}

        在这个例子中,我们在主函数中初始化互斥量,然后创建两个线程。每个线程在访问共享资源前都会尝试获得互斥量的锁,如果互斥量已经被锁定,那么线程会被阻塞,直到互斥量解锁。在访问完共享资源后,线程会解锁互斥量,这样其他正在等待的线程就可以获得互斥量的锁并访问共享资源了。

        当所有线程都结束后,我们在主函数中销毁互斥量。销毁一个互斥量会释放它占用的资源。需要注意的是,只能销毁一个已经解锁的互斥量。

        需要注意对于线程相关代码的编译需要链接上posix线程库:gcc xxx.c -lpthread         -l是链接选项,pthread是库名称,默认不添加空格也可以添加空格gcc xxx.c -l pthread

二、互斥量操作函数

1.介绍

  pthread_mutex_lock: 这个函数用于获取一个互斥锁。如果互斥锁已经被其他线程锁定,那么调用此函数的线程将会阻塞,直到该锁可用。

int pthread_mutex_lock(pthread_mutex_t *mutex);

     其中,mutex 是要锁定的互斥锁。如果成功,此函数将返回 0;否则,将返回错误代码。

  pthread_mutex_trylock: 这个函数尝试获取一个互斥锁。如果互斥锁已经被其他线程锁定,那么它将不会阻塞,而是立即返回。这是 pthread_mutex_lock 的非阻塞版本。函数原型如下:

int pthread_mutex_trylock(pthread_mutex_t *mutex);

      其中,mutex 是要尝试锁定的互斥锁。如果成功,此函数将返回 0;如果锁已经被其他线程锁定,它将返回 EBUSY;其他错误情况下,将返回相应的错误代码。

  pthread_mutex_unlock: 这个函数用于释放一个互斥锁。只有成功获取到锁的线程才能释放该锁。

int pthread_mutex_unlock(pthread_mutex_t *mutex);

      其中,mutex 是要释放的互斥锁。如果成功,此函数将返回 0;否则,将返回错误代码。

2.例子

#include <stdio.h>
#include <pthread.h>
​
pthread_mutex_t lock;
int counter = 0;
​
void* do_work(void* arg) {
    pthread_mutex_lock(&lock);
​
    // 保护共享资源
    counter += 1;
    printf("Counter value: %d\n", counter);
​
    pthread_mutex_unlock(&lock);
​
    return NULL;
}
​
int main() {
    pthread_t threads[5];
    
    // 初始化互斥锁
    pthread_mutex_init(&lock, NULL);
​
    // 创建多个线程,让它们对共享资源进行操作
    for(int i = 0; i < 5; i++) {
        pthread_create(&threads[i], NULL, do_work, NULL);
    }
​
    // 等待所有线程完成
    for(int i = 0; i < 5; i++) {
        pthread_join(threads[i], NULL);
    }
​
    // 销毁互斥锁
    pthread_mutex_destroy(&lock);
​
    return 0;
}

3.锁粒度问题

        锁粒度是描述锁所保护资源大小的术语。粗粒度锁表示锁保护的资源较大,涵盖的范围较广,例如一个锁保护整个列表或数据库;细粒度锁则表示锁保护的资源较小,例如一个锁只保护列表或数据库中的一项。

  • 粗粒度锁:优点是易于实现,开销较小,因为只需管理少数几个锁。缺点是并发度较低,因为即使两个线程需要访问的资源不冲突,只要它们尝试获取的是同一个锁,就会导致不必要的等待。例如:

pthread_mutex_t mutex;
std::vector<int> vec;
​
void add_item(int item) {
    pthread_mutex_lock(&mutex);
    vec.push_back(item);
    pthread_mutex_unlock(&mutex);
}

        在这个例子中,任何添加项目到vec的操作都需要获取同一个互斥锁,即使这些操作是在添加不同的项目。

  • 细粒度锁:优点是并发度高,因为锁的粒度较小,只要线程访问的资源不冲突,它们就可以并行执行。缺点是实现起来较复杂,需要管理更多的锁,可能导致更大的开销,同时也增加了死锁的风险。例如:

std::vector<pthread_mutex_t> mutexes;
std::vector<int> vec;
​
void add_item(int index, int item) {
    pthread_mutex_lock(&mutexes[index]);
    vec[index] = item;
    pthread_mutex_unlock(&mutexes[index]);
}

        在这个例子中,每一个vec的元素都有一个相关的互斥锁。这样,只有当两个线程尝试访问同一个元素时,它们才会互相阻塞。如果它们访问的是不同的元素,那么就可以并行执行。

        通常,选择锁的粒度需要在并发度和开销之间进行权衡。如果程序的瓶颈在于并发性能,那么使用细粒度锁可能会有帮助。如果程序的瓶颈在于锁的开销,或者如果并发竞争实际上不太激烈,那么使用粗粒度锁可能会更好。

九、带超时操作的互斥锁

1.介绍

pthread_mutex_timedlock 是 POSIX 线程库中的一个函数,它允许线程试图在指定的时间范围内对互斥锁进行加锁。如果互斥锁在此期间无法被锁定(因为其他线程已经持有锁并且没有释放),那么 pthread_mutex_timedlock 函数会返回一个超时错误。

int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
                            const struct timespec *restrict abs_timeout);

函数的形参如下:

  • pthread_mutex_t *restrict mutex:指向需要加锁的互斥锁对象的指针。

  • const struct timespec *restrict abs_timeout:一个包含超时时间的timespec结构体的指针。这个时间是绝对的,也就是说,它是从特定的时间点(例如 Unix 纪元,1970-01-01T00:00:00 UTC)开始计算的时间,而不是从现在开始的相对时间。timespec结构体包含两个字段:tv_sec代表秒数,tv_nsec代表纳秒数。

函数的返回值:

  • 如果成功获取了锁,函数返回0。

  • 如果在指定的时间内没有获取到锁,函数返回ETIMEDOUT

  • 如果函数调用失败,函数返回一个错误代码。

2.例子

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
​
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 5;  //等待5秒超时
​
int ret = pthread_mutex_timedlock(&mutex, &ts);
if (ret == ETIMEDOUT) {
    printf("Timed out while waiting for the mutex\n");
} else if (ret != 0) {
    printf("Error occurred while locking the mutex\n");
} else {
    // 你的代码:操作保护的资源
    pthread_mutex_unlock(&mutex);
}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值