现在的位置: 首页 > 综合 > 正文

Programming With POSIX Threads 读书笔记(二)

2019年08月22日 ⁄ 综合 ⁄ 共 8136字 ⁄ 字号 评论关闭

Pthread基础:

PThread类型和接口:

类型 描述
pthread_t 线程标识符
pthread_mutex_t 互斥量
pthread_cond_t 条件变量
pthread_key_t 线程私有权访问键
pthread_attr_t 线程属性对象
pthread_mutexattr_t 互斥量属性对象
pthread_condattr_t 条件变量属性对象
pthread_once_t 一次性控制变量

建立和使用线程:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start)(void*), void *arg);
int sched_yield(void);
int pthread_exit(void *value_ptr);
int pthread_detach(pthread_t thread);
int pthread_join(pthread_t thread, void **value_ptr);
int pthread_equal(pthread_t t1, pthread_t t2);
pthread_t pthread_self();

pthreat_create:创建线程

thread:第一个参数,指向线程标识符的指针。

attr:第二个参数,用来设置线程的属性。

start:第三个参数,线程运行函数的入口地址。

arg:第四个参数,运行函数的参数。

sched_yield:调用该函数,使进程主动让出执行权。

pthread_exit:结束线程

pthread_detach:分离某个线程,如果不想等待某个线程,而且知道不再需要控制它。

pthread_join:阻塞调用者,直到指定线程结束。

pthread_self:得到自身的线程id

pthread_equal:比较两线程是否相同





同步与互斥:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

pthread_mutex_t:互斥量类型

pthread_mutex_init:动态初始化一个互斥量。

pthread_mutex_destroy:销毁互斥量。

mutex:需要初始化的互斥量实例指针。

pthread_mutex_lock:给互斥量加锁,如果该互斥量已被上锁,那么他将一直阻塞,直到互斥锁变得可用为止。

pthread_mutex_trylock:给互斥量加锁,如果该互斥量已被上锁,那么他将返回上锁失败的状态。

EBUSY:返回状态——已被上锁

pthread_mutex_unlock:解锁互斥量。

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *condattr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, struct timespec *expiration);
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);

pthread_cond_t:条件变量类型

pthread_cond_init:动态初始化一个条件变量。

pthread_cond_destroy:销毁条件变量。

pthread_cond_wait:条件等待。

pthread_cond_timedwait:计时条件等待。

条件等待的使用必须和一个互斥锁相配合。

首先要由该线程对该互斥量上锁。

然后再调用pthread_cond_wait进入条件等待。

此时该互斥量也会解锁。

直到条件满足离开pthread_cond_wait之前,互斥量才会重新上锁。

pthread_cond_signal:激活一个等待该条件的线程

pthread_cond_boardcast:广播,激活所有等待该条件的线程





回到闹钟问题:

1.使用互斥锁

/*
 * 	主线程和副线程
 * 	主线程和副线程共享了
 * 		记录所有闹钟记录的链表 alarm_list
 * 		对闹钟记录链表进行操作的互斥量 alarm_mutex
 *
 * 	主线程负责将接收到的新纪录加入闹钟链表。
 * 		1.计算闹钟的启动时间。
 * 		2.根据到期时间排序将新纪录加进链表。
 * 	副线程负责处理闹钟的记录。
 * 		1.若链表不为空取出第一条记录(将链表头指针后移)
 * 		2.根据剩余时间sleep
 * 		3.sleep完了,输出闹钟信息
 *
 * 	其中主线程的第二步和副线程的第一步是有同步问题的。
 * 	必须使用互斥量保证链表操作的原子性。
 *
 * 	但是这个程序会有一个BUG,当副线程得到一个闹钟记录的时候
 * 	会调用sleep,然后在这期间,就算是主线程添加了更早结束的闹钟信息
 * 	副线程也只有在当前闹钟信息处理完成之后,才可能对其他的记录进行处理
 */

#include <pthread.h>
#include <time.h>
#include <sched.h>
#include "errors.h"

typedef struct alarm_tag {
	struct alarm_tag *link;
	int seconds;
	time_t time;
	char message[64];
}alarm_t;

pthread_mutex_t alarm_mutex = PTHREAD_MUTEX_INITIALIZER;
alarm_t *alarm_list = NULL;

void *alarm_thread(void *arg) {
	alarm_t *alarm;
	int sleep_time;
	time_t now;
	int status;

	while (1) {
		status = pthread_mutex_lock(&alarm_mutex);
		if (status != 0)
			err_abort(status, "Lock mutex");
		alarm = alarm_list;

		if (alarm == NULL)
			sleep_time = 1;
		else {
			alarm_list = alarm->link;
			now = time(NULL);
			if (alarm->time <= now)
				sleep_time = 0;
			else
				sleep_time = alarm->time - now;
#ifdef DEBUG
			printf("waiting: %d(%d)\"%s\"", alarm->time, sleep_time, alarm->message);
#endif
		}

		status = pthread_mutex_unlock(&alarm_mutex);
		if (status != 0)
			err_abort(status, "Unlock mutex");
		if (sleep_time > 0)
			sleep(sleep_time);
		else
			sched_yield();

		if (alarm != NULL) {
			printf("(%d) %s\n", alarm->seconds, alarm->message);
			free(alarm);
		}
	}
	return NULL;
}

int main(int argc, char **argv) {
	int status;
	char line[128];
	alarm_t *alarm, **last, *next;
	pthread_t thread;

	status = pthread_create(&thread, NULL, alarm_thread, NULL);
	if (status != 0)
		err_abort(status, "Create alarm thread");
	while (1) {
		printf("alarm> ");
		if (fgets(line, sizeof(line), stdin) == NULL) exit(0);
		if (strlen(line) <= 1) continue;
		alarm = (alarm_t*)malloc(sizeof(alarm_t));
		if (alarm == NULL)
			errno_abort("Allocate alarm");

		if (sscanf(line, "%d %64[^\n]", &alarm->seconds, alarm->message) < 2) {
			fprintf(stderr, "Bad Command\n");
			free(alarm);
		} else {
			status = pthread_mutex_lock(&alarm_mutex);
			if (status != 0)
				err_abort(status, "Lock mutex");
			alarm->time = time(NULL) + alarm->seconds;

			last = &alarm_list;
			next = *last;
			while (next != NULL) {
				if (next->time >= alarm->time) {
					alarm->link = next;
					*last = alarm;
					break;
				}
				last = &next->link;
				next = next->link;
			}
			if (next == NULL) {
				*last = alarm;
				alarm->link = NULL;
			}
#ifdef DEBUG
			printf("[list: ");
			for (next = alarm_list; next!=NULL; next=next->link)
				printf("%d(%d)[\"%s\"]", next->time, next->time-time(NULL), next->message);
			printf("]\n");
#endif
			status = pthread_mutex_unlock(&alarm_mutex);
			if (status != 0)
				err_abort(status, "Unlock mutex");
		}
	}
}

2.使用条件变量

分析一下第一份代码中的问题:

子线程的策略是从闹钟的有序链表中取出第一条记录,也就是最早触发的记录。然后就sleep相应的时间,然后输出闹钟信息。再去取出之后的记录。这里会出现一种有问题的情况,当子线程取出一条记录之后,主线程可能会添加一个触发时间比当前子线程处理的触发时间更早的记录。而此时,父线程却没有办法通知子线程去处理这个变化。只能默默等待子线程处理完之前的记录之后,才发现原来这里有一条已经过期许久的记录。

这个时候可以通过使用条件等待解决这个问题,当子线程得到一条记录之后,并不是去sleep,而是进入计时条件等待的状态。如果此时父线程发现新的闹钟记录的触发时间要早于当前子线程正在处理的记录的话。父线程可以在插入记录之后主动激发条件等待。然后子线程将当前处理的闹钟重新添加回闹钟的有序链表中,然后再重新取出最早触发的记录,然后再次进入条件等待的状态。

/*
 * alarm_mutex.c程序的增强版
 * 程序中一共有三个函数
 * 1.主函数:主函数还是和alarm_mutex.c中的主函数一样
 * 2.alarm_insert函数:有序链表的插入函数,惟一一点特别的是在这个函数的最后。
 * 3.alarm_thread函数:处理闹钟的线程函数。
 * 整个程序的逻辑是:
 * 主函数启动之后,创建alarm_thread线程。
 * 主函数只负责接受用户的输入,然后调用alarm_insert函数将闹钟的信息插入链表。
 * alarm_thread检查链表是否为空,如果为空进入条件等待状态。
 * 当链表不为空时,取出链表的第一节,然后有限条件等待的状态,等待的就是第一个闹钟的到期时间。
 * 这个时候alarm_thread的唤醒有两种可能
 * 		一种是超时,这时候第一个闹钟的时间就到了,打印闹钟的信息,然后free。
 * 		另一种是在alarm_insert的时候,新插入的闹钟到期时间早于当前等待的闹钟
 * 		alarm_insert发出了唤醒信号。这是alarm_thread就不再等待这个闹钟了
 * 		alarm_thread会把这个闹钟重新放回链表里面,然后重新取出最早的一个记录。
 * alarm_insert函数在插入之后,会对当前插入进行判断
 * 如果链表之前为空,或则新记录的请求时间早于当前记录的请求时间,那么该函数会唤醒alarm_thread线程
 */

#include <pthread.h>
#include <time.h>
#include "errors.h"

//#define DEBUG 1 //取消这一行注释可以看到更多信息

typedef struct alarm_tag {
	struct alarm_tag *link;
	int seconds;
	time_t time;
	char message[64];
} alarm_t;

pthread_mutex_t alarm_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t alarm_cond = PTHREAD_COND_INITIALIZER;
alarm_t *alarm_list = NULL;
time_t current_alarm = 0;

void alarm_insert(alarm_t *alarm) {
	int status;
	alarm_t **last, *next;

	last = &alarm_list;
	next = *last;
	while (next != NULL) {
		if (next->time >= alarm->time) {
			alarm->link = next;
			*last = alarm;
			break;
		}
		last = &next->link;
		next = next->link;
	}

	if (next == NULL) {
		*last = alarm;
		alarm->link = NULL;
	}

#ifdef DEBUG
	printf("[list: ");
	for (next = alarm_list; next!=NULL; next=next->link)
		printf("%d(%d)[\"%s\"]", next->time, next->time-time(NULL), next->message);
	printf("]\n");
#endif

	if (current_alarm == 0 || alarm->time < current_alarm) {
		current_alarm = alarm->time;
		status = pthread_cond_signal(&alarm_cond);
		if (status != 0)
			err_abort(status, "Signal cond");
	}
}

void *alarm_thread(void *arg) {
	alarm_t *alarm;
	struct timespec cond_time;
	time_t now;
	int status, expired;

	status = pthread_mutex_lock(&alarm_mutex);
	if (status != 0)
		err_abort(status, "Lock mutex");

	while (1) {
		current_alarm = 0;
		while (alarm_list == NULL) {
			status = pthread_cond_wait(&alarm_cond, &alarm_mutex);
			if (status != 0)
				err_abort(status, "Wait on cond");
		}

		alarm = alarm_list;
		alarm_list = alarm->link;
		now = time(NULL);
		expired = 0;
		if (alarm->time > now) {
#ifdef DEBUG
			printf("waiting: %d(%d)\"%s\"", alarm->time, alarm->time - time(NULL), alarm->message);
#endif
			cond_time.tv_sec = alarm->time;
			cond_time.tv_nsec = 0;
			current_alarm = alarm->time;
			while (current_alarm == alarm->time) {
				status = pthread_cond_timedwait(&alarm_cond, &alarm_mutex,
						&cond_time);
				if (status == ETIMEDOUT) {
					expired = 1;
					break;
				}
				if (status != 0)
					err_abort(status, "Cond timedwait");
			}
			if (!expired)
				alarm_insert(alarm);
		} else {
			expired = 1;
		}
		if (expired) {
			printf("(%d) %s\n", alarm->seconds, alarm->message);
			free(alarm);
		}
	}
	return NULL;
}

int main(int argc, char **argv) {
	int status;
	char line[128];
	alarm_t *alarm;
	pthread_t thread;

	status = pthread_create(&thread, NULL, alarm_thread, NULL);
	if (status != 0)
		err_abort(status, "Create alarm thread");
	while (1) {
		printf("alarm> ");
		if (fgets(line, sizeof(line), stdin) == NULL)
			exit(0);
		if (strlen(line) <= 1)
			continue;
		alarm = (alarm_t*) malloc(sizeof(alarm_t));
		if (alarm == NULL)
			errno_abort("Allocate alarm");

		if (sscanf(line, "%d %64[^\n]", &alarm->seconds, alarm->message) < 2) {
			fprintf(stderr, "Bad Command\n");
			free(alarm);
		} else {
			status = pthread_mutex_lock(&alarm_mutex);
			if (status != 0)
				err_abort(status, "Lock mutex");
			alarm->time = time(NULL) + alarm->seconds;

			alarm_insert(alarm);

			status = pthread_mutex_unlock(&alarm_mutex);
			if (status != 0)
				err_abort(status, "Unlock mutex");
		}
	}
}

这个版本的闹钟程序算是基本圆满了。

Programming With POSIX Threads 读书笔记(一)http://blog.csdn.net/hyzhou33550336/article/details/16890691

Programming With POSIX Threads 读书笔记(二)http://blog.csdn.net/hyzhou33550336/article/details/16890959

Programming With POSIX Threads 读书笔记(三)http://blog.csdn.net/hyzhou33550336/article/details/16899433

抱歉!评论已关闭.