用于线程互斥的互斥量也有相应的属性 pthread_mutexattr_t,这里只讨论三个方面:
- 共享属性
- 鲁棒属性
- 互斥量的递归类型
本文先介绍共享属性。
1. 属性的初始化与回收
互斥量属性的数据类型是 pthread_mutexattr_t. 下面两个函数分别用于互斥量属性的初始化与回收。
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
2. 共享属性
除了互斥量有共享属性外,其它的线程互斥同步对象如读写锁、自旋锁、条件变量、屏障都有共享属性。
该属性有两种情况:
PTHREAD_PROCESS_PRIVATE
: 这种是默认的情况,表示互斥量只能在本进程内部使用。PTHREAD_PROCESS_SHARED
:表示互斥量可以在不同进程间使用。
对于第一种情况,我们早已经学会。第二种情况,需要结合前面的进程间通信技术才有用。一般需要在共享内存中分配互斥量,然后再为互斥量指定PTHREAD_PROCESS_SHARED
属性就可以了。
3. 实验
本实验中的程序分为了 4 个部分,分别是 init、destroy、buyticket 和 rbstbuyticket.
init 程序的作用是申请共享内存,并在共享内存中分配互斥量等。
destroy 用来回收共享内存中的互斥量,并销毁共享内存。
buyticket 和 rbstbuyticket 是从共享内存的数据中抢票的。
.
|- init.c
|- destroy.c
|- buyticket.c
|- rbstbuyticket.c
|- Makefile // Makefile 文件主要用来一次性编译完所有文件。
本部分实验,只用的到前三个程序,最后一个 rbstbuyticket.c 在讲鲁棒属性的时候用的到。
3.1 init.c 程序
程序 init 用来创建一块共享内存,可以给 init 程序传递一个命令行参数,也可以不传。如果传递参数,表示为互斥量指定鲁棒性属性,本文我们先不传递参数。
// init.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pthread.h>
#define PERR(msg) do { perror(msg); exit(-1); } while(0)
#define PPERR(err, msg) do { err = errno; perror(msg); exit(-1); } while(0)
struct ticket {
int remain;
pthread_mutex_t lock;
};
// 打印共享属性
void printshared(pthread_mutexattr_t *attr) {
int err, shared;
err = pthread_mutexattr_getpshared(attr, &shared);
if (err != 0) PPERR(err, "pthread_mutexattr_getshared");
if (shared == PTHREAD_PROCESS_PRIVATE)
puts("shared = PTHREAD_PROCESS_PRIVATE");
else if (shared == PTHREAD_PROCESS_SHARED)
puts("shared = PTHREAD_PROCESS_SHARED");
else
puts("shared = ???");
}
// 打印鲁棒属性
void printrobust(pthread_mutexattr_t *attr) {
int err, robust;
err = pthread_mutexattr_getrobust(attr, &robust);
if (err != 0) PPERR(err, "pthread_mutexattr_getrobust");
if (robust == PTHREAD_MUTEX_STALLED)
puts("robust = PTHREAD_MUTEX_STALLED");
else if (robust == PTHREAD_MUTEX_ROBUST)
puts("robust = PTHREAD_MUTEX_ROBUST");
else
puts("robust = ???");
}
int main(int argc, char* argv[]) {
int err, shared, robust = 0, flag = 1;
if (argc >= 2) robust = 1;
key_t key = 0x8888;
// 创建共享内存
int id = shmget(key, getpagesize(), IPC_CREAT | IPC_EXCL | 0666);
if (id < 0) PERR("shmget");
// 挂接共享内存
struct ticket *t = (struct ticket*)shmat(id, NULL, 0);
if ((int)t == -1) PERR("shmat");
// 设置余票数量为 5
t->remain = 5;
pthread_mutexattr_t mutexattr;
err = pthread_mutexattr_init(&mutexattr);
if (err != 0) PPERR(err, "pthread_mutexattr_init");
printshared(&mutexattr);
printrobust(&mutexattr);
// 将互斥量的共享属性设置为可以进程间共享使用。
shared = PTHREAD_PROCESS_SHARED;
err = pthread_mutexattr_setpshared(&mutexattr, shared);
if (err != 0) PPERR(err, "pthread_mutexattr_setshared");
// 如果有命令行参数,就将鲁棒性设置为 PTHREAD_MUTEX_ROBUST
// 本文暂时不设置此值
if (robust) {
err = pthread_mutexattr_setrobust(&mutexattr, PTHREAD_MUTEX_ROBUST);
if (err != 0) PPERR(err, "pthread_mutexattr_setshared");
}
puts("modify attribute ------------------>");
printshared(&mutexattr);
printrobust(&mutexattr);
pthread_mutex_init(&t->lock, &mutexattr);
err = pthread_mutexattr_destroy(&mutexattr);
if (err != 0) PPERR(err, "pthread_mutexattr_destroy");
err = shmdt((void*)t);
if (err != 0) PERR("shmdt");
return 0;
}
3.2 destroy.c
destroy 程序主要用于回收互斥量和共享内存。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pthread.h>
#define PERR(msg) do { perror(msg); exit(-1); } while(0)
#define PPERR(err, msg) do { err = errno; perror(msg); exit(-1); } while(0)
struct ticket {
int remain;
pthread_mutex_t lock;
};
int main() {
int err;
key_t key = 0x8888;
int id = shmget(key, 0, 0);
if (id < 0) PERR("shmget");
struct ticket *t = (struct ticket*)shmat(id, NULL, 0);
if ((int)t == -1) PERR("shmat");
err = pthread_mutex_destroy(&t->lock);
if (err != 0) PPERR(err, "pthread_mutex_destroy");
err = shmdt((void*)t);
if (err != 0) PERR("shmdt");
err = shmctl(id, IPC_RMID, NULL);
if (err != 0) PERR("shmctl");
return 0;
}
3.3 buyticket.c
buyticket 程序主要用于抢票,该程序需要从命令行传递参数,表示抢票人的名字。比如./buyticket allen
表示抢票人是 allen.
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <pthread.h>
#define PERR(msg) do { perror(msg); exit(-1); } while(0)
#define PPERR(err, msg) do { err = errno; perror(msg); exit(-1); } while(0)
struct ticket {
int remain;
pthread_mutex_t lock;
};
int main(int argc, char* argv[]) {
if (argc < 2) {
printf("Usage: %s <name>\n", argv[0]);
exit(-1);
}
char *name = argv[1];
int err, shared, flag = 1;
key_t key = 0x8888;
int id = shmget(key, 0, 0);
if (id < 0) PERR("shmget");
struct ticket *t = (struct ticket*)shmat(id, NULL, 0);
if ((int)t == -1) PERR("shmat");
// 只要票数大于 0 就不断抢票。
while(flag) {
pthread_mutex_lock(&t->lock);
int remain = t->remain;
if (remain > 0) {
sleep(1);
printf("%s buy a ticket\n", name);
--remain;
sleep(3);
t->remain = remain;
}
else flag = 0;
pthread_mutex_unlock(&t->lock);
sleep(2);
}
err = shmdt((void*)t);
if (err != 0) PERR("shmdt");
return 0;
}
3.4 编译——Makefile 文件
此文件可以方便程序编译。
main:init destroy buyticket
init:init.c
gcc init.c -o init -lpthread
destroy:destroy.c
gcc destroy.c -o destroy -lpthread
buyticket:buyticket.c
gcc buyticket.c -o buyticket -lpthread
注意,上面所有以 gcc 开头的行前面是个 tab 符号,即按下键盘上的 Tab 键所产生的,千万不能是空格。
完成些文件后,保存退出,然后在命令行键入
$ make
此时就会产生三个程序:init、destroy 和 buyticket
3.5 运行
- 初始化
./init
- 开启两个进程进行抢票
开启两个终端假设为终端 A 和终端 B. 在终端 A 中键入
./buyticket allen
终端 B 中键入
./buyticket luffy
记住这两个进程在执行时间上不能差的太远,别等到其中一个运行结束才开始另一个。
- 运行结果
图1 运行结果
可以从图 1 中看到,一共有 5 张票,分别被两个不同进程中的线程所抢到,其中 allen 抢了 3 张票,而 luffy 抢了 2 张票。
完成了上面的程序的执行后,别忘记使用 ./destroy
对共享内存进行回收。
4. 总结
- 掌握互斥量的共享属性
- 掌握共享内存的使用
练习:上面的程序中,当 allen 抢到第 2 张票后,立即按下 CTRL + C 中断进程,会有什么后果?