一、主要函数介绍
(1)定义锁 :
pthread_mutex_t mutex; //互斥锁 数据类pthread_mutex_t
(2)初始化锁:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr // 参数 1:定义的锁;参数2: 互斥锁属性,通常传 NULL);
注:restrict 是一个修饰指针的关键字,对该函数的使用没有影响,只是为了保证锁的唯一性。
作用:指针 p 指向一块内存,s = p ,因此通过 s 也可以访问该内存,但是有了 restrict 修饰 p,s 就不能访问该内存了。
(3)加锁:
int pthread_mutex_lock(pthread_mutex_t *mutex); //加锁
mutex 没有被上锁,当前线程会将锁锁上,如果 mutex 被上锁,当前线程会阻塞,直至被解锁后,解除阻塞。
int pthread_mutex_trylock(pthread_mutex_t *mutex); //尝试加锁
mutex 没有被上锁,当前线程会将锁锁上,如果 mutex 被上锁,直接返回,不阻塞。
用法:
if ( pthread_mutex_trylock(&mutex) == 0 )
{ // 尝试加锁成功,访问共享资源 }
else { // 错误处理 }
(4)解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
(5)销毁锁:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
以上函数的返回值都是成功返回 0, 失败返回错误号。
二、上述函数引用实例的加锁修改
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#define MAX 10000
// 定义互斥锁
pthread_mutex_t mutex;
//定义全局变量
int num = 0;
void *func_a(void *arg) {
for(int i = 0; i < MAX; i++)
{
// 访问全局变量前加锁
pthread_mutex_lock(&mutex);
// 阻塞,直至锁处于解锁状态,该子线程访问全局变量
int cur = num;
cur++;
num = cur;
printf("Thread A ,id = %lu, num = %d\n",pthread_self(), num);
// 解锁
pthread_mutex_unlock(&mutex);
usleep(10);
}
return NULL; }
void *func_b(void *arg) {
for(int i = 0; i < MAX; i++)
{
pthread_mutex_lock(&mutex);
int cur = num;
cur++;
num = cur;
printf("Thread B ,id = %lu, num = %d\n",pthread_self(), num);
pthread_mutex_unlock(&mutex);
usleep(10);
}
return NULL; }
int main()
{
pthread_t p1, p2;
// 初始化互斥锁
pthread_mutex_init(&mutex, NULL);
// 创建两个子线程
pthread_create(&p1, NULL, func_a, NULL);
pthread_create(&p2, NULL, func_b, NULL);
// 阻塞回收资源
pthread_join(p1, NULL);
pthread_join(p2, NULL);
// 销毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
}
最后 num = 20000
三、使用互斥锁的注意问题
(1)pthread_mutex_ lock 和 pthread_mutex_ unlock 之间称为临界区,临界区越小越好。
(2)使用 pthread_mutex_trylock 要作返回值判断。
(3)作线程同步,操作共享数据的所有线程都要上锁,而且上同一把锁。
说明:lock 和 unlock 之间其实模拟了一个“原子操作”,这也是不会产生数据混乱的根本原因。原子操作就是该操作不可分隔,或者说,该操作执行过程中,不会丢失 CPU。
注意:lock 和 unlock 会丢失 CUP ,但是由于上锁,即使其他线程抢到 CPU,也会因为阻塞而丢弃 CPU,也就是说,只有原线程能再次抢到 CPU,因此该执行过程效果跟原子操作相同。