一些基础概念
临界资源: 任一时刻只允许一个线程访问的共享资源
临界区: 访问临界资源的代码
原子性: 不会被任何调度机制打断的操作,该操作只有两态:要么完成,要么未完成。
互斥: 任何时刻,互斥可以保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用,而锁就是实现互斥的。
同步: 同步是一种机制,用于协调不同进程、线程或设备之间的操作,确保它们按照预期的顺序和方式进行。同步的目的是保持数据的一致性和系统的稳定性。
互斥锁
线程之间共享同一地址空间和资源,而线程的并发执行也可能导致资源竞争的问题。为了解决线程之间资源竞争的问题,linux系统提供了互斥锁(Mutex)这一机制。
在使用互斥锁的过程中,当一个线程要访问共享资源时,首先会尝试获取互斥锁。如果锁已被其他线程占用的,则该线程会被阻塞,直到其他线程释放锁为止;而当线程访问完共享资源后,需要显式地释放互斥锁,以便其他线程可以继续访问共享资源。
使用互斥锁的一些API函数
#include <pthread.h>
#include <time.h>
pthread_mutex_t local_mutex;//定义锁
// 初始化一个互斥锁。后面那个参数是他的属性。
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);
互斥锁的阻塞模式
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<time.h>
char *buf[5];
int pos;
pthread_mutex_t mutex;
void* task(void *p)
{
pthread_mutex_lock(&mutex);
buf[pos]=(char*)p;
pos++;
pthread_mutex_unlock(&mutex);
}
int main()
{
pthread_t id1,id2;
pthread_mutex_init(&mutex,NULL);
pthread_create(&id1,NULL,task,(void *)"zhangsan" "yaowenwen");
pthread_create(&id2,NULL,task,(void *)"lisi");
pthread_join(id1,NULL);
pthread_join(id2,NULL);
pthread_mutex_destroy(&mutex);
for(int i=0;i<pos;i++)
{
printf("%s",buf[i]);
}
printf("\n");
return 0;
}
可以看到主函数创建了两个线程,两个线程调用的是同一个函数,作用均为向一个全局数组内写入字符。
在那个函数内调用了加锁函数和解锁函数。即当第一个线程执行时,第二个线程必须等待第一个线程执行完毕。最后结果如下:
条件变量
互斥量可以防止多个线程同时访问临界资源,而条件变量允许一个线程将某个临界资源的状态变化通知其他线程,在共享资源设定一个条件变量,如果共享资源条件不满足,则让线程到该条件变量下阻塞等待,当条件满足时,其他线程可以唤醒条件变量阻塞等待的线程。(也就是说可以防止一个线程一只占着某一个资源,其他线程共享不了资源)
在线程之间有一种情况:线程A需要某个条件才能继续往下执行,如果该条件不成立,此时线程A进行阻塞等待,当线程B运行后使该条件成立后,则唤醒该线程A继续往下执行。
在pthread库中,可以通过条件变量中,可以设定一个阻塞等待的条件,或者唤醒等待条件的线程。
怎么使用条件变量
条件变量的接口
pthread_cond_t 是 POSIX 线程库(Pthreads)中用于表示条件变量的数据类型。
条件变量的初始化
#include <pthread.h>//头文件
方式一(pthread_cond_t是局部全局都可以用):
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
方式二(pthread_cond_t是全局变量时):
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
注意:restrict 是一个类型限定符,它用于告知编译器两个指针不会指向同一个内存位置,这样编译器可以生成更高效的代码
参数
cond:一个指向 pthread_cond_t 类型的指针,用于存储初始化后的条件变量。cond只有两种取值:0、1
attr:一个指向 pthread_condattr_t 类型的指针,用于指定条件变量的属性。通常可以传递 NULL(nullptr),以使用默认属性。
返回值:
如果成功,返回 0。
如果失败,返回错误码。
/*pthread_cond_t cond; 变量cond只有两种取值1、0。*/
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
//功能:初始化一个条件变量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
/*
功能:阻塞等待一个条件变量,也就是等待信号的发起
函数作用:
阻塞等待条件变量cond(参1)满足
释放已掌握的互斥锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
*/
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime); //功能:限时等待一个条件变量
int pthread_cond_signal(pthread_cond_t *cond); //功能:唤醒至少一个阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond); //功能:唤醒全部阻塞在条件变量上的线程
int pthread_cond_destroy(pthread_cond_t *cond); // 功能:销毁一个条件变量
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
pthread_mutex_t lock;
pthread_cond_t cond;
void*Run(void*arg)
{
while(1){
char*Str=(char*)arg;
pthread_mutex_lock( &lock ) ;
pthread_cond_wait(&cond,&lock);//默认不满足条件,线程会阻塞等待条件变量就绪
printf("%s is open\n" , Str) ;
pthread_mutex_unlock( &lock ) ;
sleep(4) ;
}
}
int main()
{
pthread_mutex_init(&lock,NULL);//初始化锁
pthread_cond_init(&cond,NULL);//初始化条件变量
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,Run,(void*)"tid1");
pthread_create(&tid2,NULL,Run,(void*)"tid2");
while(1)
{
pthread_cond_broadcast(&cond);//唤醒线程
sleep(2) ;
}
pthread_mutex_destroy(&lock);
pthread_cond_destroy(&cond);
return 0;
}
在给信号那行代码处,如果给的是一个线程,那么"tid1 is open",出现之后,再经过两秒“tid2 is open”才会出现。
如果给信号处,是给所有线程信号,那么"tid1 is open"和“tid2 is open”同时出现,再经过两秒,又再次同时出现。
具体执行过程:运行主函数,初始化锁,初始化条件变量。然后创建两个线程tid1,tid2。这两个线程均调用run函数,run函数内部先上锁,然后就是等待阻塞,等到主函数内给出来了信号再继续执行。