线程实现睡觉的理发师问题
一、题目
理发师问题的描述:一个理发店接待室有n张椅子,工作室有1张椅子;没有顾客时,理发师睡觉;第一个顾客来到时,必须将理发师唤醒;顾客来时如果还有空座的话,他就坐在一个座位上等待;如果顾客来时没有空座位了,他就离开,不理发了;当理发师处理完所有顾客,而又没有新顾客来时,他又开始睡觉。
二、基础知识
(1)互斥
互斥是指散布在不同任务之间的若干程序片断,当某个任务运行其中一个程序片段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完这个程序片段后才可以运行。
最基本的场景就是:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。
(2)互斥锁
互斥锁(
m
u
t
e
x
mutex
mutex)是一种简单的加锁的方法来控制对共享资源的访问,每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。互斥锁只有两种状态,即上锁和解锁。
多个线程只有一把锁一个钥匙,谁上的锁就只有谁能开锁。当一个线程要访问一个共享变量时,先用锁把变量锁住,然后再操作,操作完了之后再释放掉锁。
当另一个线程也要访问这个变量时,发现这个变量被锁住了,无法访问,它就会一直等待,直到锁被解了,它再给这个变量上个锁,然后使用,使用完了再释放锁,以此进行。
(3)POSIX线程
POSIX线程 (英语:POSIX Threads,常被缩写为
P
t
h
r
e
a
d
s
Pthreads
Pthreads) 是POSIX的线程标准,定义了创建和操纵线程的一套API。
实现POSIX 线程标准的库常被称作
P
t
h
r
e
a
d
s
Pthreads
Pthreads。
三、函数说明
接下来的函数都是 P t h r e a d s Pthreads Pthreads定义的函数,其头文件都为pthread.h。
(1)pthread_t
typedef unsigned long int pthread_t
sizeof(pthread_t) = 8
pthread_t
用于声明线程ID。
(2) pthread_create
int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,void *(*start_rtn)(void*),void *arg)
//函数原型
pthread_create
是的创建线程的函数。
第一个参数为指向线程标识符的指针。第二个参数用来设置线程属性。第三个参数是线程运行函数的起始地址。最后一个参数是运行函数的参数。本次内容运行函数不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。
若线程创建成功,则返回0。若线程创建失败,则返回出错编号。
(3) pthread_join
int pthread_join(pthread_t thread, void **retval)
//函数原型
pthread_join()
函数,以阻塞的方式等待
t
h
r
e
a
d
thread
thread指定的线程结束。pthread_join
使一个线程等待另一个线程结束
第一个参数
t
h
r
e
a
d
thread
thread: 线程标识符,即线程
I
D
ID
ID,标识唯一线程。第二个参数
r
e
t
v
a
l
retval
retval: 用户定义的指针,用来存储被等待线程的返回值。
运行成功返回0;失败,返回的则是错误号。
(4) pthread_mutex_t
pthread_mutex_t
为
m
u
t
e
x
mutex
mutex 数据类型
(5) pthread_mutex_init
int pthread_mutex_init(pthread_mutex_t *restrict_mutex,const pthread_mutexattr_t *restrict_attr)
//函数原型
pthread_mutex_init()
函数是以动态方式创建互斥锁的。函数成功执行后,互斥锁被初始化为未锁住态。
参数restrict_attr指定了新建互斥锁的属性。如果参数restrict_attr 为
N
U
L
L
NULL
NULL,则使用默认的互斥锁属性,默认属性为快速互斥锁 。
函数成功完成之后会返回零,其他任何返回值都表示出现了错误。
(6) pthread_mutex_lock
int pthread_mutex_lock(pthread_mutex_t *mutex)
//函数原型
pthread_mutx_lock
用于
m
u
t
e
x
mutex
mutex 所指向的互斥锁上锁。
在成功完成之后会返回零。其他任何返回值都表示出现了错误。
(7) pthread_mutex_unlock
int pthread_mutex_unlock(pthread_mutex_t *mutex)
//函数原型
pthread_mutex_unlock
可以解除锁定
m
u
t
e
x
mutex
mutex 所指向的互斥锁。
在成功完成之后会返回零。其他任何返回值都表示出现了错误。
(8) pthread_mutex_destroy
int pthread_mutex_destroy(pthread_mutex_t *mutex)
//函数原型
pthread_mutex_destroy
用于互斥锁销毁。
在执行成功后返回 0,否则返回错误码。
四、代码
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<unistd.h>
pthread_mutex_t mutex; //创建mutex变量
int n; //有n个椅子,即有n个空位
int num; //有num个顾客
int flag =1; //函数循环判断
void *customer(void *arg)
{
while(flag)
{
pthread_mutex_lock(&mutex); //互斥锁上锁
if(num < n)
{
num++; //顾客数num加一
printf("有顾客进来,还剩下%d个椅子\n",n-num);
pthread_mutex_unlock(&mutex); //互斥锁解锁
}
else
{
pthread_mutex_unlock(&mutex); //互斥锁解锁
printf("没有椅子了,顾客离开\n");
}
}
}
void *barber(void *arg)
{
while(flag)
{
if(num > 0)
{
pthread_mutex_lock(&mutex); //互斥锁上锁
num--; //顾客数num减一
printf("顾客离开,还剩下%d个椅子\n",n-num);
pthread_mutex_unlock(&mutex); //互斥锁解锁
}
}
}
int main(void)
{
printf("请输入椅子总数n:");
scanf("%d",&n);
num = 0; //初始化顾客数num为0
pthread_t Barber,Customer;
pthread_mutex_init(&mutex,NULL); //创建互斥锁
pthread_create(&Barber,NULL,barber,NULL); //创建线程
pthread_create(&Customer,NULL,customer,NULL);
sleep(1); //主线程休息1秒,执行子线程.
flag = 0; //1秒后,子线程停止执行.
pthread_join(Barber,NULL); //等待子线程
pthread_join(Customer,NULL);
pthread_mutex_destroy(&mutex); //摧毁互斥锁
}
运行结果:
✔ 由于
p
t
h
r
e
a
d
pthread
pthread库不是标准
L
i
n
u
x
Linux
Linux库, 所以出错。 在编译命令后面添加
−
l
p
t
h
r
e
a
d
-lpthread
−lpthread。
分析:
首先是输入椅子总数,然后第
46
46
46行和第
47
47
47行创建了两个线程
B
o
s
s
Boss
Boss和
C
u
s
t
m
o
e
r
Custmoer
Custmoer;首先进入
b
a
r
b
e
r
barber
barber函数,此时
f
l
a
g
=
1
flag=1
flag=1,所以
w
h
i
l
e
while
while会在这时一直执行,而
n
u
m
=
0
num=0
num=0所以单单执行
w
h
i
l
e
while
while循环什么也没做。然后进入
c
u
s
t
o
m
e
r
customer
customer函数 (此时函数
b
a
r
b
e
r
barber
barber也在执行) ,只要顾客数
n
u
m
num
num比椅子数
n
n
n小,就会进入
i
f
if
if语句,第
16
16
16行互斥锁上锁 (资源占用),有顾客进来,顾客数
n
u
m
num
num加一,执行完后再对互斥锁解锁。其中只要
n
u
m
>
0
num>0
num>0也会对互斥锁进行解锁从而对资源的占用。所以,该程序总共有三个线程在执行,一个主线程和两个子线程,它们同时执行着,但是只要一个子进程对互斥锁上锁了,相当于另一个子进程只能等互斥锁解锁了才能执行 (资源释放),从而达到互斥的效果,而主线程执行了第
49
49
49行的代码主线程休眠了
1
1
1秒,
1
1
1秒后执行第
50
50
50行把
f
l
a
g
flag
flag改为
0
0
0,相当于两个子线程都执行了空语句,第
51
51
51行和第
52
52
52行分别等待子线程结束,最后所有线程都结束,也就是程序运行结束。
需要转载请标明出处