刚接触linux互斥锁的时候可能会比较抽象,所以本文想要用PV原语来更加具体的理解linux互斥锁。如若有误,烦请指出,不甚感激!
由于线程共享了进程的资源和地址空间,因此,任何线程对系统资源的操作都会给其他线程带来影响,如果多个线程同时对进程的共享资源read 或者write,将会发生无法预知的错误。所以,多线程中的同步就是非常重要的事情了。
同步的机制有哪些呢?
POSIX 中线程同步的方法,主要有互斥锁和信号量的方式。
这一节主要讲互斥锁。在理解互斥锁前,我们了解一下什么是PV原语。
PV原语
PV原语通过操作信号量来处理进程间的同步与互斥的问题。
P原语:P是荷兰语Proberen(测试)的首字母。为阻塞原语,负责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。操作为:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞。
V原语:V是荷兰语Verhogen(增加)的首字母。为唤醒原语,负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。
pv原语三种情况
具体PV原语对信号量的操作可以分为三种情况:
- 把信号量视为一个加锁标志位,实现对一个共享变量的互斥访问。
实现过程:
- P(mutex); // mutex的初始值为1 访问该共享数据;
- V(mutex);
- 非临界区
- 把信号量视为是某种类型的共享资源的剩余个数,实现对一类共享资源的访问。
实现过程:
- P(resource); // resource的初始值为该资源的个数N 使用该资源;
- V(resource); 非临界区
- 把信号量作为进程间的同步工具
我们先来看一个简单线程的例子,不使用任何同步互斥机制。然后后面再通过修改这个例子来实现互斥锁。这样对比,对互斥锁理解更加直观。
/* no_pv.c*/
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//定义一个线程共享资源
//static int shareNum = 1 ;
static int count = 0 ;
#define FILENAME "no_order"
/*线程一*/
void thread1(void * arg)
{
char write_buf[] = "1";
int fd = *(int *)arg;
printf("this is pthread1,fd = %d\n",fd);
int i = 0;
while(1)
{
//if (shareNum > 0)
{
//shareNum--;
for(i = 0;i<5;i++)
{
if (write(fd,write_buf,sizeof(write_buf)))
{
perror("write");
}
count++;
}
printf("This is a pthread1....and count = %d\n",count);
//shareNum++;
sleep(2);
}
}
}
/*线程二*/
void thread2(void * arg)
{
char write_buf[] = "2";
int fd = *(int *)arg;
printf("this is pthread2 and fd = %d\n",fd);
int i = 0;
while(1)
{
//if (shareNum > 0)
{
// shareNum--;
for(i = 0;i<5;i++)
{
if (write(fd,write_buf,sizeof(write_buf)))
{
perror("write");
}
count++;
}
printf("This is a pthread2.... count = %d\n",count);
// shareNum++;
sleep(2);
}
}
}
int main(void)
{
int fd = open(FILENAME,O_RDWR | O_CREAT,0777);
if(fd < 0)
{
perror("open");
}
printf("open success!\n");
pthread_t id1,id2;
int i,ret;
/*创建线程一*/
ret=pthread_create(&id1,NULL,(void *) thread1,&fd);
if(ret!=0){
perror("Create pthread error!\n");
}
/*创建线程二*/
ret=pthread_create(&id2,NULL,(void *) thread2,&fd);
if(ret!=0){
perror ("Create pthread error!\n");
}
/*等待线程结束*/
pthread_join(id1,NULL);
pthread_join(id2,NULL);
return 0;
}
实验结果:
ubuntu:~/test/pthread_test$ gcc no_pv.c -o no_pv -lpthread
ubuntu:~/test/pthread_test$ ./no_pv
open success!
this is pthread1,fd = 3
this is pthread2 and fd = 3
write: Success
write: Success
write: Success
write: Success
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread2.... count = 9
write: Success
This is a pthread1....and count = 10
write: Success
write: Success
write: Success
write: Success
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread1....and count = 19
write: Success
This is a pthread2.... count = 20
^C
ubuntu:~/test/pthread_test$ cat no_order
2121212111221112
从上面的例子程序中,可以看到,我们先创建线程一和线程二,然后在线程一向文件no_pv每隔2秒写入5个字符“1”,在线程二向文件no_pv每隔2秒写入5个字符“2”。终端打印的结果说明了两个线程具有竞争关系,线程执行具有不确定性,这样的结果是对文件no_pv没有做好相关的保护,不能保证在某一个线程向文件no_pv写数据的时候暂停其他线程向文件no_pv写数据。我们现在想要达到的目的是,当某一个线程向文件no_pv写数据的时候,控制另一个线程不能向no_pv写数据。
通过修改上面的例子,我们可以实现这一简单的功能。请看下面的例子:
/* PV.c*/
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//定义一个线程共享资源
static int shareNum = 1 ;
static int count = 0 ;
#define FILENAME "order"
/*线程一*/
void thread1(void * arg)
{
char write_buf[] = "1";
int fd = *(int *)arg;
printf("this is pthread1,fd = %d\n",fd);
int i = 0;
while(1)
{
if (shareNum > 0)
{
shareNum--;
for(i = 0;i<5;i++)
{
if (write(fd,write_buf,sizeof(write_buf)))
{
perror("write");
}
count++;
}
printf("This is a pthread1....and count = %d\n",count);
shareNum++;
sleep(2);
}
}
}
/*线程二*/
void thread2(void * arg)
{
char write_buf[] = "2";
int fd = *(int *)arg;
printf("this is pthread2 and fd = %d\n",fd);
int i = 0;
while(1)
{
if (shareNum > 0)
{
shareNum--;
for(i = 0;i<5;i++)
{
if (write(fd,write_buf,sizeof(write_buf)))
{
perror("write");
}
count++;
}
printf("This is a pthread2.... count = %d\n",count);
shareNum++;
sleep(2);
}
}
}
int main(void)
{
int fd = open(FILENAME,O_RDWR | O_CREAT,0777);
if(fd < 0)
{
perror("open");
}
printf("open success!\n");
pthread_t id1,id2;
int i,ret;
/*创建线程一*/
ret=pthread_create(&id1,NULL,(void *) thread1,&fd);
if(ret!=0){
perror("Create pthread error!\n");
}
/*创建线程二*/
ret=pthread_create(&id2,NULL,(void *) thread2,&fd);
if(ret!=0){
perror ("Create pthread error!\n");
}
/*等待线程结束*/
pthread_join(id1,NULL);
pthread_join(id2,NULL);
return 0;
}
实验结果:
ubuntu:~/test/pthread_test$ gcc PV.c -o pv -lpthread
ubuntu:~/test/pthread_test$ ./pv
open success!
this is pthread1,fd = 3
this is pthread2 and fd = 3
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread1....and count = 5
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread2.... count = 10
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread1....and count = 15
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread2.... count = 20
^C
ubuntu:~/test/pthread_test$ cat order
11111222221111122222
上面的例子中,我们增加了全局变量shareNum,并且初始化为1,然后在线程中,每次想要向文件写入数据时,都要判断shareNum是否大于0,如果大于0,那么shareNum减1,然后向文件写入数据。另外一个线程也是如此。这样一来,我们就保证了当一个线程向文件写入数据的时候,shareNum的值为0,另外一个线程因为访问文件前要先判断shareNum是否大于0,条件不满足就访问不了文件了。所以可以达到同一个进程中的共享资源互斥。
通过上面两个例子的对比分析,我们基本能实现互斥锁的功能,但是想要在访问不到资源的时候阻塞程序或者不阻塞程序等更加强大的功能,我们需要用到linux系统自带的互斥锁的功能。通常操作系统的函数库会考虑的事情更多,更加安全可靠。但是对于linux mutex 互斥锁线程控制的理解是完全可以用上面两个例子来帮助消化的。
linux mutex互斥锁
mutex 是一种简单的加锁的方法来控制对共享资源的存取。这个互斥锁只有两种状态,也就是上锁和解锁,可以把互斥锁看作某种意义上的全局变量,像上面例子那样理解。在同一时刻只能有一个线程掌握某个互斥上的锁,拥有上锁状态的线程能够对共享资源进行操作。若其他线程希望上锁一个已经上锁了的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。可以说,
这把互斥锁使得共享资源按序在各个线程中操作。
来看看互斥锁常用的函数:
pthread_mutex_init 函数
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr)
返回值:若成功,返回0;若出错,返回-1
参数:
- Mutex:互斥锁
- Mutexattr:(通常默认 null)
- PTHREAD_MUTEX_INITIALIZER:创建快速互斥锁
- Mutexattr PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:创建递归互斥锁
- PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP: 创建检错互斥锁
其他相关函数:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex,) //互斥锁上锁
int pthread_mutex_trylock(pthread_mutex_t *mutex,) //非阻塞上锁
int pthread_mutex_unlock(pthread_mutex_t *mutex,) //互斥锁解锁
int pthread_mutex_destroy(pthread_mutex_t *mutex,) //销毁互斥锁
通过修改上面的例子来看看互斥锁是如何使用的:
/* mutex.c*/
#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//定义一个线程共享资源
//static int shareNum = 1 ;
pthread_mutex_t mutex;
static int count = 0 ;
#define FILENAME "mutex_order"
/*线程一*/
void thread1(void * arg)
{
char write_buf[] = "1";
int fd = *(int *)arg;
printf("this is pthread1,fd = %d\n",fd);
int i = 0;
while(1)
{
//if (shareNum > 0)
/*互斥锁上锁*/
if(pthread_mutex_lock(&mutex)!=0)
{
perror("pthread_mutex_lock");
}
else
{
//shareNum--;
for(i = 0;i<5;i++)
{
if (write(fd,write_buf,sizeof(write_buf)))
{
perror("write");
}
count++;
}
printf("This is a pthread1....and count = %d\n",count);
if(pthread_mutex_unlock(&mutex)!=0)
{
perror("pthread_mutex_unlock");
}
else
{
sleep(2);
}
//shareNum++;
}
}
}
/*线程二*/
void thread2(void * arg)
{
char write_buf[] = "2";
int fd = *(int *)arg;
printf("this is pthread2 and fd = %d\n",fd);
int i = 0;
while(1)
{
//if (shareNum > 0)
/*互斥锁上锁*/
if(pthread_mutex_lock(&mutex)!=0)
{
perror("pthread_mutex_lock");
}
else
{
// shareNum--;
for(i = 0;i<5;i++)
{
if (write(fd,write_buf,sizeof(write_buf)))
{
perror("write");
}
count++;
}
printf("This is a pthread2.... count = %d\n",count);
// shareNum++;
if(pthread_mutex_unlock(&mutex)!=0)
{
perror("pthread_mutex_unlock");
}
else
{
sleep(2);
}
}
}
}
int main(void)
{
int fd = open(FILENAME,O_RDWR | O_CREAT,0777);
if(fd < 0)
{
perror("open");
}
printf("open success!\n");
/*互斥锁初始化*/
pthread_mutex_init(&mutex,NULL);
pthread_t id1,id2;
int i,ret;
/*创建线程一*/
ret=pthread_create(&id1,NULL,(void *) thread1,&fd);
if(ret!=0){
perror("Create pthread error!\n");
}
/*创建线程二*/
ret=pthread_create(&id2,NULL,(void *) thread2,&fd);
if(ret!=0){
perror ("Create pthread error!\n");
}
/*等待线程结束*/
pthread_join(id1,NULL);
pthread_join(id2,NULL);
return 0;
}
实验结果:
ubuntu:~/test/pthread_test$ gcc mutex.c -o mutex -lpthread
ubuntu:~/test/pthread_test$ ./mutex
open success!
this is pthread1,fd = 3
this is pthread2 and fd = 3
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread1....and count = 5
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread2.... count = 10
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread1....and count = 15
write: Success
write: Success
write: Success
write: Success
write: Success
This is a pthread2.... count = 20
^C
ubuntu:~/test/pthread_test$ cat mutex_order
11111222221111122222
看到上面的例子是不是有种似曾相识的感觉。其实只是把PV.c 中关于shareNum全局变量替换成了mutex相关的函数,其余部分没变。
所以,想要理解程序mutex.c,先理解PV.c吧,整个流程一模一样。
这节讲互斥锁,下次再讲线程同步的一些机制。