apue 学习笔记——线程

前序:

本人菜鸟学生一枚,从5月9号开始学习Linux,到现在整整一个月了,这一个月真的收获很大,特别充实。

由于毕业课题项目提前做完了,论文也写完了,坐等毕业的节奏,但是距毕业还有差不多一年的时间,感觉这段时间不应该就这么荒废掉,应该做点有意义的事情。

之前都是搞裸机项目,从来没有接触操作系统,一直对它挺好奇的,现在终于有了充裕的时间可以干自己想干的事情了,所以决定学习一下Linux。自从入手,真是欲罢不能,这一个月每天的时间基本上都在看书和敲代码。

最开始是从鸟哥的Linux私房菜开始入手的,5月9号买下,5月25号基本看完,后面有些章节没看。5月25号买下apue,开始深入研究这本圣经,在看的时候真是给人一种意犹未尽和欲罢不能的感觉,不愧为一本经典之作。介绍的确实很深入,但大部分还是可以看懂的,很多地方真是值得细细品味。边看书、边敲代码、边时不时的搜一下相关内容的博客补充理解,不知不觉,全书已经看完了一半,现在刚刚看完第12章——线程控制。

自己的能量超乎了我的想象,入手时都说这本书很难,真是有些担心自己看不进去,没想到自己的进度会这么快,更没想到自己居然能激发出这么大兴趣,兴趣真的很重要。

半个月,看完了一半,匆匆走过,是时候回头做做总结了,是时候做点笔记了,就先从线程这一章开始吧。

自己的字太丑了,就用博客记录读书笔记吧!由于是菜鸟,自己的博客仅作自己的学习笔记之用。



线程标识

每个线程都有一个线程标识:线程ID,只在它所属的进程环境中有效。数据类型为:pthread_t
通过调用pthread_self()函数可以获取自己的ID。
pthread_t  pthread_self(void);

线程创建

int pthread_create( pthread_t *thread,   const pthread_attr_t *attr,  void *(*start_rtn)(void *),   void*arg );
若线程创建成功,返回0。若线程创建失败,则返回出错编号。
4个参数:
1,thread:线程ID指针
2,attr:设置线程属性
3,start_rtn:线程运行函数起始地址,函数指针
4,arg:线程函数的唯一入口参数,如果是多个参数,可以将把参数存放在一个结构中,然后将结构地址传递给arg。


线程终止

线程终止方式:
(1)线程从启动例程中简单的返回,返回值是线程的退出码;
(2)线程可以被同一进程的其他线程取消;pthread_cancel(pthread_t tid)
(3)线程调用pthread_exit函数。终止该线程,并返回一个指向某一对象的指针,可由其他函数如pthread_join来检索获取。

void  pthread_exit(void * retval)  //终止状态存于retval指向的单元


等待线程终止并获取终止状态

对于以可结合状态启动的线程(线程属性为PTHREAD_CREATE_JOINABLE)
int pthread_join(pthread_t tid, void ** retval) //以阻塞方式等待指定线程tid结束,retval用于获取线程的终止状态。
例:
pthread_t  tid;
void * tret;
void* tid_func(void * arg);

pthread_create(&tid,NULL,tid_func,NULL);//创建线程tid
pthread_join(tid,&tret);

注意:要确保调用完成之后,线程返回值指向的内存仍然是有效的。若线程返回值是线程内部定义的局部变量的指针,线程结束后,变量即被释放,此时返回值将变成野指针。

#include "apue.h"
#include <pthread.h>
void * thread_func(void * arg);

char s[]="Hello word!";

int main(void)
{
    int res;
    pthread_t td;
    void * retval;

    printf("main starting\n");
    
    res=pthread_create(&td,NULL,thread_func,s);
    if(res!=0)
    {
        printf("thread create error");
        return(1);
    }
    
    if(pthread_join(td,&retval)!=0)
    {
        printf("pthread_join error\n");
        return(1);
    }

    printf("thread1 return: %s\n",(char *)retval);
    printf("Now,the s becomming: %s\n",s);
    exit(0);
    
}

void * thread_func(void * arg)
{
    printf("thread1 starting,Argument is %s\n",(char*)arg);
    sleep(5);
    strcpy(arg,"HaHa");
    return ("thread1 exited");
}



线程取消

线程可以调用pthread_cancel函数来请求取消同一进程中的其他线程,并不等待线程终止,只是提出请求而已。
函数原型为 int pthread_cancel(pthread_t tid)。
函数功能等价于使得tid标识的线程调用pthread_exit(PTHREAD_CANCELED)。

线程清理函数

线程清理程序,类似进程的atexit函数。
线程可以建立多个清理处理程序,处理程序记录在栈中,执行顺序与注册顺序相反。
void pthread_cleanup_push(void (*routine)(void *),void *arg);   //入栈,注册清理函数
void pthread_cleanup_pop(int execute);  //出栈,弹出清理程序,若execute=0,清理函数将只被弹出,不被调用

注意:若线程仅仅是简单的返回而终止的,清理函数不会被调用。


线程同步

线程同步的方法:线程锁(互斥量)、读写锁、条件变量。

1、互斥量

数据类型: pthread_mutex_t
使用前必须初始化:1,可设置为常量PTHREAD_MUTEX_INITIALIZER
2,可调用pthread_mutex_init函数

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutex_attr_t *mutexattr);//初始化互斥量
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);  //对互斥量进行解锁


以上函数成功返回0,失败返回错误编号。

若同时使用多个互斥量时,应总是使用相同的顺序加锁,避免死锁。

2、读写锁

互斥锁只有两种状态,而读写锁可以有三种状态:读模式下加锁状态、写模式下加锁状态、不加锁状态。
一次只有一个线程可以占有写模式的读写锁,而多个线程可以同时占有读模式的读写锁。
当读写锁处于读加锁状态时,所有以读模式试图对其加锁的线程都可以成功,但若以写模式试图对其加锁,它将被阻塞并阻塞随后的读模式锁请求。

读写锁数据类型为pthread_rwlock_t,操作函数如下:

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_mutex_t *mutex); 
int pthread_rwlock_trywrlock(pthread_mutex_t *mutex); 

3、条件变量

条件变量总是和一个互斥锁结合在一起使用,受互斥锁保护,线程在改变条件状态前必须首先锁住互斥量。

条件变量类型为pthread_cond_t,使用前必须进行初始化:

1.静态方式初始化:赋常量PTHREAD_COND_INITIALIZER

2.int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

int pthread_cond_destroy(pthread_cond_t *cond);//释放条件变量内存

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); //等待条件为真
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const structtimespec *restrict abstime);//指定等待时间方式


int pthread_cond_broadcast(pthread_cond_t *cond);  //唤醒等待该条件的所有线程
int pthread_cond_signal(pthread_cond_t *cond); //唤醒等待该条件的某个线程

注意:

pthread_cond_wait函数内部包含的操作:

1.对互斥量解锁

2.阻塞,使线程睡眠

3.苏醒后立即重新上锁互斥量,然后函数返回。

上述1、2两步为原子操作。


调用pthread_cond_wait前要一定要先锁住互斥量,因为在判断完条件不成立和进入阻塞之间,有一个时间窗口,在这个时间窗口内,若其他线程改变了条件变量的话,那我们就漏掉了这个条件pthread_cond_wait会自动对其解锁,并在一个原子操作内进入阻塞等待唤醒。

pthread_cond_wait必须放在pthread_mutex_lock和pthread_mutex_unlock之间。

pthread_cond_signal最好放在pthread_mutex_lock和pthread_mutex_unlock之间。放在之外和放在之内各有优缺点(具体参考apue2 p312 习题11.4,或http://baike.baidu.com/view/7844657.htm?fr=aladdin)。

一般用一个while循环判断条件,结构框架为:

pthread_mutex_lock(&mutex);//先上锁

while(某个条件)

pthread_cond_wait(&cond,&mutex);//等待条件为真

执行具体操作

pthread_mutex_unlock(&mutex);//解锁


关于互斥锁和条件变量,这里写了个小程序:

1. 买书卖书问题

书店里销售一种图书,由于书比较好,很抢手,甲乙两个线程都想买这本书,但是店里经常断货,无货的时候店家让他们登记一下,什么时候有货了就通知他们。一旦到货,立即通知所有登记的顾客,这时候谁能抢着就是谁的。

#include "apue.h"
#include <pthread.h>

pthread_mutex_t sail_lock=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t sail=PTHREAD_COND_INITIALIZER;
int count;//货物数量

void * tid1_func(void * arg)
{
    printf("tid1 want to buy the book\n");

    pthread_mutex_lock(&sail_lock);//先上锁
    while(count==0)
    {
        printf("tid1:The book is not available\n");
    
        pthread_cond_wait(&sail,&sail_lock);
    }
    printf("tid1:Now,the book is available,you can buy it!\n");
    count=count-1;
    pthread_mutex_unlock(&sail_lock);//解锁
}

void * tid2_func(void * arg)
{
        printf("tid2 want to buy the book\n");

        pthread_mutex_lock(&sail_lock);//先上锁
        while(count==0)
        {
                printf("tid2:The book is not available\n");
                pthread_cond_wait(&sail,&sail_lock);
        }
        printf("tid2:Now,the book is available,you can buy it!\n");
        count=count-1;
        pthread_mutex_unlock(&sail_lock);//解锁

}

int main(void)
{
    pthread_t tid;
    pthread_create(&tid,NULL,tid1_func,NULL);
        pthread_create(&tid,NULL,tid2_func,NULL);

    sleep(10);

    pthread_mutex_lock(&sail_lock);//上锁
    count=count+1;//进货
    pthread_mutex_unlock(&sail_lock);//解锁
    
//    pthread_cond_signal(&sail);//通知某个顾客到货了
    pthread_cond_broadcast(&sail);//通知所有顾客到货了
    sleep(5);
    exit(0);   
}


2. 生产者——消费者问题

2.1 先入后出的栈结构

/*该生产者——消费者程序实现的是一个类似于先入后出的栈*/
#include "apue.h"
#include <pthread.h>
#define BUF_SIZE 16//定义缓冲区长度
int count=0;//缓冲区内数据个数
int buf[BUF_SIZE];//定义缓冲区
pthread_cond_t count_cond;
pthread_mutex_t count_lock;

/*生产者线程函数*/
void * producer(void * arg)
{
    int i;
    for(i=1;i<=1000;i++)
    {
        pthread_mutex_lock(&count_lock);//上锁
        while(count >= BUF_SIZE)//若缓冲区已满
        {
            pthread_cond_wait(&count_cond,&count_lock);//等待
             //包含动作:解锁,阻塞,等待唤醒,唤醒后立即重新上锁
        }    
        buf[count++]=i;//生产者把数据放入缓冲区,数据个数加一。
        printf("producer: %d  ->  buf\n",i);
        pthread_cond_signal(&count_cond);//唤醒消费者
        pthread_mutex_unlock(&count_lock);//解锁
    }
}

/*消费者线程函数*/
void * consumer(void * arg)
{
    int out;
    int i=0;//计数
    while(1)
    {
        pthread_mutex_lock(&count_lock);
        while(count == 0)//若缓冲区已空
        {
            pthread_cond_wait(&count_cond,&count_lock);//等待
        }
        out=buf[count-1];
        count--;
        printf("consumer: buf -> %d\n",out);
        i++;
        pthread_cond_signal(&count_cond);//唤醒生产者
        pthread_mutex_unlock(&count_lock);
        if(i==1000)
            break;
    }
}


int main(void)
{
    pthread_t tid1,tid2;

    /*初始化互斥量和条件变量*/
    pthread_mutex_init(&count_lock,NULL);
    pthread_cond_init(&count_cond,NULL);
    
    /*创建生产者和消费者两个子线程*/
    pthread_create(&tid1,NULL,producer,NULL);
    pthread_create(&tid2,NULL,consumer,NULL);

    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);

    printf("the main is end.\n");
    exit(0);
}


2.2 先入先出的FIFO结构

/*该生产者——消费者程序实现的是一个类似于先入先出的FIFO*/
#include "apue.h"
#include <pthread.h>
#define BUF_SIZE 16//定义缓冲区长度
int count=0;//缓冲区内数据个数
int buf[BUF_SIZE];//定义缓冲区
pthread_cond_t count_cond;
pthread_mutex_t count_lock;

/*生产者线程函数*/
void * producer(void * arg)
{
    int i;
    for(i=1;i<=1000;i++)
    {
        pthread_mutex_lock(&count_lock);//上锁
        while(count >= BUF_SIZE)//若缓冲区已满
        {
            pthread_cond_wait(&count_cond,&count_lock);//等待
             //包含动作:解锁,阻塞,等待唤醒,唤醒后立即重新上锁
        }    
        buf[count++]=i;//生产者把数据放入缓冲区,数据个数加一。
        printf("producer: %d  ->  buf\n",i);
        pthread_cond_signal(&count_cond);//唤醒消费者
        pthread_mutex_unlock(&count_lock);//解锁
    }
}

/*消费者线程函数*/
void * consumer(void * arg)
{
    int out;
    int i=0;//计数
    int buf_p=0;
    while(1)
    {
        pthread_mutex_lock(&count_lock);
        while(count == 0)//若缓冲区已空
        {
                    buf_p=0;
             pthread_cond_wait(&count_cond,&count_lock);//等待
        }
        out=buf[buf_p++];
        count--;
        printf("consumer: buf -> %d\n",out);
        i++;
        pthread_cond_signal(&count_cond);//唤醒生产者
        pthread_mutex_unlock(&count_lock);
        if(i==1000)
            break;
    }
}


int main(void)
{
    pthread_t tid1,tid2;

    /*初始化互斥量和条件变量*/
    pthread_mutex_init(&count_lock,NULL);
    pthread_cond_init(&count_cond,NULL);
    
    /*创建生产者和消费者两个子线程*/
    pthread_create(&tid1,NULL,producer,NULL);
    pthread_create(&tid2,NULL,consumer,NULL);

    pthread_join(tid1,NULL);
    pthread_join(tid2,NULL);

    printf("the main is end.\n");
    exit(0);
}


本章中,pthread_cond_wait() 非常重要 ,它是 POSIX 线程信号发送系统的核心,也是最难以理解的部分。在看这一部分时刚开始很难理解,期间看了很多博文。



发布了3 篇原创文章 · 获赞 5 · 访问量 2万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览