读书笔记:第9章 记录上锁 (4)

《UNIX网络编程:卷2》P167:图9-8

它获取某个完整文件的一个读出锁,然后fork两个子进程。第一个子进程首先尝试获取一个写入锁(它将阻塞,因为父进程已持有整个文件的一个读出锁),然后由第二个进程尝试获取一个读出锁。

----------------------------------------

/*
 * test2.c
 * P167 图9-8 确定在有一个写入锁待处理期间是否允许有另一个读出锁
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <sys/time.h>

#define FILE_MODE	(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)

#define read_lock(fd, offset, whence, len) \
	lock_reg(fd, F_SETLK, F_RDLCK, offset, whence, len)
#define readw_lock(fd, offset, whence, len) \
	lock_reg(fd, F_SETLKW, F_RDLCK, offset, whence, len)
#define write_lock(fd, offset, whence, len) \
	lock_reg(fd, F_SETLK, F_WRLCK, offset, whence, len)
#define writew_lock(fd, offset, whence, len) \
	lock_reg(fd, F_SETLKW, F_WRLCK, offset, whence, len)

#define un_lock(fd, offset, whence, len) \
	lock_reg(fd, F_SETLK, F_UNLCK, offset, whence, len)

#define is_read_lockable(fd, offset, whence, len) \
	!lock_test(fd, F_RDLCK, offset, whence, len)
#define is_write_lockable(fd, offset, whence, len) \
	!lock_test(fd, F_WRLCK, offset, whence, len)

int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len);
int lock_test(int fd, int type, off_t offset, int whence, off_t len);
char *gf_time(void);

int main(int argc, char *argv[])
{
	int		fd;
	pid_t	pid;

	/*
	 * 父进程打开文件并获取读出锁
	 */
	if ((fd = open("test1.data", O_RDWR | O_CREAT, FILE_MODE)) < 0) {
		fprintf(stderr, "open error: %s\n", strerror(errno));
		exit(1);
	}
	if (read_lock(fd, 0, SEEK_SET, 0) < 0) {
		fprintf(stderr, "read_lock error: %s\n", strerror(errno));
		exit(1);
	}
	printf("%s: parent has read lock\n", gf_time());

	/*
	 * 创建第一个子进程,睡眠1秒,然后阻塞,等待整个文件的一个写入锁
	 * 取得写入锁后,持有2秒,释放它
	 */ 
	if ((pid = fork()) < 0) {
		fprintf(stderr, "fork error: %s\n", strerror(errno));
		exit(1);
	} else if (pid == 0) {
		/* 第一个子进程 */
		sleep(1);		

		printf("%s: first child tries to obtain write lock\n", gf_time());
		
		if (writew_lock(fd, 0, SEEK_SET, 0) < 0) {
			fprintf(stderr, "write_lockw error: %s\n", strerror(errno));
		}
		printf("%s: first child obtains write lock\n", gf_time());

		sleep(2);

		if (un_lock(fd, 0, SEEK_SET, 0) < 0) {
			fprintf(stderr, "unlock error: %s\n", strerror(errno));
		}
		printf("%s: first child releases write lock\n", gf_time());

		exit(0);
	}

	/*
	 * 创建第二个子进程,睡眠3秒以允许第一个子进程的写入锁处于待处理状态
	 * 然后尝试获取文件的一个读出锁,该锁持有4秒时间
	 */
	if ((pid = fork()) < 0) {
		fprintf(stderr, "fork error: %s\n", strerror(errno));
		exit(1);
	} else if (pid == 0) {
		/* 第二个子进程 */
		sleep(3);

		printf("%s: second child tries to obtain read lock\n", gf_time());

		if (readw_lock(fd, 0, SEEK_SET, 0) < 0) {
			fprintf(stderr, "readw_lock error: %s: ", strerror(errno));
		}
		printf("%s: second child obtains read lock\n", gf_time());

		sleep(4);

		if (un_lock(fd, 0, SEEK_SET, 0) < 0) {
			fprintf(stderr, "un_lock error: %s\n", strerror(errno));
		}
		printf("%s: second child releases read lock\n", gf_time());
		exit(0);
	}

	/* 父进程持有读锁5秒后释放该锁 */
	sleep(5);

	if (un_lock(fd, 0, SEEK_SET, 0) < 0)
		fprintf(stderr, "un_lock error: %s\n", strerror(errno));

	printf("%s: parent release read lock\n", gf_time());

	exit(0);
}

char *gf_time(void)
{
	struct timeval	tv;
	static char		str[30];
	char			*ptr;

	if (gettimeofday(&tv, NULL) < 0)
		fprintf(stderr, "gettimeofday error: %s\n", strerror(errno));

	ptr = ctime(&tv.tv_sec);
	strcpy(str, &ptr[11]);

	snprintf(str+8, sizeof(str) - 8, ".%06ld", tv.tv_usec);

	return(str);
}

int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
	struct flock	lock;
	lock.l_type = type;
	lock.l_start = offset;
	lock.l_whence = whence;
	lock.l_len = len;

	return(fcntl(fd, cmd, &lock));
}

int lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
	struct flock	lock;

	lock.l_type = type;
	lock.l_start = offset;
	lock.l_whence = whence;
	lock.l_len = len;

	if (fcntl(fd, F_GETLK, &lock) == -1)
		return(-1);

	if (lock.l_type == F_UNLCK)
		return(0);

	return(lock.l_pid);
}

运行程序:

$ ./test2 
19:32:34.733342: parent has read lock
19:32:35.734277: first child tries to obtain write lock
19:32:37.734414: second child tries to obtain read lock
19:32:37.734784: second child obtains read lock
19:32:39.734360: parent release read lock
$ 19:32:41.734903: second child releases read lock
19:32:41.734920: first child obtains write lock
19:32:43.735088: first child releases write lock

即使已有来自第一个子进程的一个待处理写入锁请求,第二个子进程请求的读出锁也是立即给予的。这样,只要连续不断的发出读出锁请求,写入者就可能因获取不了写入锁而“挨饿”。

你可能感兴趣的:(读书笔记,《UNIX网络编程》)