practice: [multiMutex-backoff]
[书中位置]:代码来自英文版p65页的 backoff.c, 基本没有改动。 errors.h代码不在这里。
[主要代码]:
#include <pthread.h>
#include "errors.h"
#include <stdio.h>
#include <stdlib.h>
#define ITERATIONS 10
pthread_mutex_t mutex[3] = {
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_MUTEX_INITIALIZER,
PTHREAD_MUTEX_INITIALIZER
};
int backoff = 1;
int yield_flag = 0;
void *lock_forward (void *arg)
{
int i,iterate,backoffs;
int status;
for (iterate = 0; iterate < ITERATIONS; iterate++)
{
backoffs = 0;
for (i = 0; i < 3; i++)
{
if (i == 0) {
status = pthread_mutex_lock (&mutex[i]);
if (status != 0)
err_abort (status, "create mutex ");
}else {
if (backoff)
status = pthread_mutex_trylock (&mutex[i]);
else
status = pthread_mutex_lock (&mutex[i]);
if (status == EBUSY) {
backoffs++;
printf ("[ forward locker backing off at %d/n",i);
for (; i >= 0; i--)
pthread_mutex_unlock(&mutex[i]);
}
else if(status != 0) {
err_abort (status, "lock mutex ");
printf ("( forward locker get %d/n", i);
}
}
if (yield_flag)
{
if (yield_flag > 0)
sched_yield ();
else
sleep (1);
}
}
printf ("look forward get all locks, %d backoffs/n", backoffs);
pthread_mutex_unlock (&mutex[2]);
pthread_mutex_unlock (&mutex[1]);
pthread_mutex_unlock (&mutex[0]);
sched_yield ();
}
return NULL;
}
void *lock_backward (void *arg)
{
int i,iterate,backoffs;
int status;
for (iterate = 0; iterate < ITERATIONS; iterate++)
{
backoffs = 0;
for (i = 2; i >= 0; i--)
{
if (i == 2) {
status = pthread_mutex_lock (&mutex[i]);
if (status != 0)
err_abort (status, "create mutex ");
}else {
if (backoff)
status = pthread_mutex_trylock (&mutex[i]);
else
status = pthread_mutex_lock (&mutex[i]);
if (status == EBUSY) {
backoffs++;
printf ("[ backward locker backing off at %d/n",i);
for (; i < 2; i++)
pthread_mutex_unlock(&mutex[i]);
}
else if(status != 0) {
err_abort (status, "lock mutex ");
printf ("( backward locker get %d/n", i);
}
}
if (yield_flag)
{
if (yield_flag > 0)
sched_yield ();
else
sleep (1);
}
}
printf ("look backward get all locks, %d backoffs/n", backoffs);
pthread_mutex_unlock (&mutex[0]);
pthread_mutex_unlock (&mutex[1]);
pthread_mutex_unlock (&mutex[2]);
sched_yield ();
}
return NULL;
}
int main (int argc, char *argv[])
{
pthread_t forward,backward;
int status;
if (argc > 1)
backoff = atoi(argv[1]);
if (argc > 2)
yield_flag = atoi(argv[2]);
pthread_create (&forward, NULL, lock_forward, NULL);
pthread_create (&forward, NULL, lock_backward, NULL);
pthread_exit (NULL);
return 0;
}
[说明]:
运行程序带1或者两个参数,第一个参数用来赋值给backoff,第二个参数用来赋值给yield_flag,能够产生不同的输出。当 backoff = 1,使用backoff机制;backoff = 0,不使用backoff机制。 yield_flag = 0 表示不yield, yield_flag < 0,表示sleep, yield_flag > 0, 表示yield.
1 backoff = 0, yield_flag != 0 发生deadlock.
2 backoff = 0, yield_flag == 0 同3。
3 backoff = 1. yield_flag = 0; 0 backoffs. no deadlocks, 效率高。
4 backoff = 1. yield_flag > 0; 存在 backoffs. no deadlocks, 效率高。
5 backoff = 1. yield_flag < 0; 存在 backoffs. no deadlocks, 效率低。
sched_yield的作用是提示系统进行线程调度,即当系统存在其他线程比当前线程优先级高或存在同级别的优先级
时候,当前线程挂起,运行新线程。如果没有满足运行条件的线程,那么函数返回,依然是当前线程运行。
sleep函数也起到了提示系统线程调度的作用,但是它阻塞了当前的线程,知道sleep的参数值代表的时间过后,当
前线程才能成为就绪态的。
所以,在本例子中,用sleep函数效率很低。如果不出现线程交替运行,backoff的机会会很小。如果存在线程交替
运行,而且没有线程同向获取锁的机制,经典的deadlock便发生了!
practice : [alarm-project]
[书中位置]:代码来自英文版p83页的 alarm_cond.c, 基本没有改动。 errors.h代码不在这里。
[背景知识]: 在这个例子当前线程进入wait状态是不能调用sleep函数的,一旦调用sleep函数,这个线程在这段时间内将不会被启用,对于我们的alarm的工程来说是一个缺陷。pthread_cond_timewait在某方面能够实现sleep的功能,而且可以通过pthrea_cond_signal函数来wake线程。
[基本动作]:
1)使用mutex来保护predicate变量和shared data ; pthread_mutex_lock (&mutex);
2)编码作条件判断,如果predicate为真,调用pthread_cond_wait, pthread系统将会先unlock线程mutex,而后使
线程进入wait状态(为了能够让其他线程修改predicate)。直到predicate为真,pthread系统会将线程wake,并且重
新获取线程mutex。 pthread_cond_wait (&cond, &mutex) pthread_cond_signal (&cond)
3)unlock mutex 并且释放所有资源。
[存在的问题]: mutex 与predicate必须存在联系,最简单的是mutex包含一个predicate。这样将没有race
condition 或者deadlock出现。但是可以出现mutex和多个predicate联系的情况。
比如说,pthread系统不允许线程1等待条件变量A使用mutex A,而线程2等待条件变量A使用mutex B.但是可以线程
A等待条件变量A使用mutex A,线程2等待条件变量B使用mutex A。
上面的描述可以总结为等待必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或
pthread_cond_timedwait())的竞争条件(Race Condition)。
[主要代码]:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#include <errno.h>
#include <pthread.h>
#include <time.h>
#include <assert.h>
#include "errors.h"
/*multi thread with mutex*/
typedef struct alarm_tag {
struct alarm_tag *link;
int seconds;
time_t time;
char message[64];
} alarm_t_mutex;
pthread_mutex_t alarm_mutex = PTHREAD_MUTEX_INITIALIZER;
/*cond for alarmlist != NULL*/
pthread_cond_t alarm_cond = PTHREAD_COND_INITIALIZER;
alarm_t_mutex *alarm_list = NULL;
void *alarm_thread_mutex (void *arg)
{
alarm_t_mutex *alarm;
time_t now;
int status;
time_t cond_time;
int expired = 0;
while (1)
{
status = pthread_mutex_lock (&alarm_mutex);
if (status != 0)
err_abort (status, "lock mutex");
alarm = alarm_list;
while (alarm == NULL)
{
pthread_cond_wait (&alarm_cond, &alarm_mutex);
alarm = alarm_list;
}
/*最近的时间没有到期*/
while (alarm->time > time(NULL))
{
alarm = alarm_list;
cond_time = alarm->time - now;
printf ("[waiting: %d(%d) /"%s/"]/n", alarm->time, cond_time, alarm->message);
//中途有注册时钟,需要修改注册时间
pthread_cond_timedwait (&alarm_cond, &alarm_mutex, &cond_time);
}
if (alarm->time <= time(NULL))
expired = 1;
if (expired == 1)
{
alarm_list = alarm_list->link;
}
status = pthread_mutex_unlock (&alarm_mutex);
if (status != 0)
err_abort (status, "unlock mutex");
if (alarm != NULL)
{
printf ("%d(%s)/n", alarm->time, alarm->message);
free (alarm);
alarm = NULL;
}
}
}
void multi_thread_mutex_loop (void)
{
int status;
char line [128];
alarm_t_mutex *alarm, **last, *next;
pthread_t thread;
status = pthread_create (&thread, NULL, alarm_thread_mutex, NULL);
if (status != 0)
err_abort (status, "create thread");
while (1)
{
printf ("alarm> ");
if (fgets (line, sizeof(line), stdin) == NULL) exit (0);
if (strlen (line) == 0)
continue;
alarm = (alarm_t_mutex*)malloc (sizeof (alarm_t_mutex));
assert (alarm != NULL);
if (sscanf (line ,"%d %64[^/n]", &alarm->seconds, &alarm->message) < 2) {
fprintf (stderr, "bad comand");
free (alarm);
alarm = NULL;
continue;
}
else {
status = pthread_mutex_lock (&alarm_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 为空*/
if (next == NULL)
{
*last = alarm;
alarm->link = NULL;
}
printf ("[list: ");
for (next = alarm_list; next != NULL; next = next->link)
{
printf ("%d(%d)[/"%s/"] ",next->time, next->time-time(NULL), next->message);
pthread_cond_signal (&alarm_cond);
}
printf ("]/n");
status = pthread_mutex_unlock (&alarm_mutex);
}
}
int main (int argc, char *argv[])
{
multi_thread_mutex_loop();
return 0;
}
practice : memory barrier
背景知识 : memory barrier似乎与C标准的序列点的概念类似。C语言的变量与变量实际地址的值之间,其实还相
隔了寄存器、CPU cache几层。变量首先从内存--> cache --> register(作运算) --> cache --> 内存,经历了这
么多过程,有读有写,如果只是当一的理想顺序的读写是不会出现问题的。编译器生成汇编语言以及系统内存控制
器使得这种理想顺序有可能不能实现(具体例子见书中P113 figure3.10)。C标准提出序列点的概念,pthread提出
memory barrier的概念来保证内存读写的唯一性和正确性。正如编写C代码要考虑序列点之间不要两次修改同一内
存一样,在编写多线程程序的时候要注意到memory barrier的存在。具体为严格遵守posix的memory visible规则。
规则有四条,在p107页。
这样就解除了我的一个疑惑,为什么在多线程程序中使用所有在当前scope内可见的变量。这个对于编译来说是完
全没有问题的,为了保证程序正确性,必须遵守memory visible规则,因为规则中定义了pthread库访问内存的
memory barrier点,遵守规则才能确保完全正确!