《program with posix thread》第三章实例

本文介绍了两种多线程同步机制的实现案例:一种利用互斥锁和回退机制避免死锁;另一种通过条件变量实现定时报警功能。文章详细解析了代码逻辑及关键概念,并强调了memory barrier的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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点,遵守规则才能确保完全正确!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值