Linux多线程程序设计

线程:线程可以理解为“轻量级”的进程,它与创建它的进程共享代码段和数据段,同时拥有自己独立的栈。

pthread_create

原型:int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg)
头文件:<pthread.h>,编译时需要链接pthread库
功能:创建新的线程,成功返回0,失败返回错误编号
参数:thread:用来存储新创建的线程id
     attr:一个指向pthread_attr_t结构的指针,指向的结构决定了新创建的线程的属性,如优先级等。可为NULL,表示采用默认属性。
     start_routine:函数指针。创建线程与创建进程后的执行情况不同。创建进程后,子进程顺着代码执行下去,而创建线程,线程是从一个指定的函数入口开始执行。start_routine指向了线程的函数入口
     arg:线程函数入口需要一个参数void *,arg就是该参数,可为NULL

多进程程序中,父进程的退出不会导致子进程的退出;而在多线程程序中,进程结束时,该进程所创建的线程也会结束运行。所以多线程程序中进程需要等待其线程结束才能结束。

pthread_join

原型:int pthread_join(pthread_t thread,void *retval)
头文件:<pthread.h>编译时需要链接pthread库
功能:等待线程结束,成功返回0,失败返回错误编号
参数:thread:要等待结束的线程的id
     retval:保存线程结束时的状态,一般为NULL

pthread_exit

原型:void pthread_exit(void *retval)
头文件:<pthread.h>编译时需要链接pthread库
功能:结束线程
参数:retval:线程返回的值,可与pthread_join中的参数retval配合使用,但一般设为NULL

在实际应用中,多个线程往往会访问同一数据或资源,为避免线程之间相互影响,需要引入线程互斥机制,而互斥锁是互斥机制中的一种。

pthread_mutex_init

原型:int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr)
头文件:<pthread.h>
功能:初始化互斥锁,成功返回0,失败返回错误编号
参数:mutex:要初始化的互斥锁的指针
     attr:用于设置互斥锁的属性,一般设为NULL,表示默认属性

pthread_mutex_lock

原型:int pthread_mutex_lock(pthread_mutex_t *mutex)
头文件:<pthread.h>
功能:获取互斥锁,成功返回0,失败返回错误编号
参数:mutex:要获取的互斥锁的指针

pthread_mutex_unlock

原型:int pthread_mutex_unlock(pthread_mutex_t *mutex)
头文件:<pthread.h>
功能:释放互斥锁,成功返回0,失败返回错误编号
参数:mutex:要获取的互斥锁的指针

线程互斥

线程互斥:进程创建出两个线程,两个线程互斥

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
pthread_mutex_t mutex;
pthread_t thread[2];
int number=0;
void* work1(void *)
{
    int i;
    for(i=0;i<10;i++)
    {
        pthread_mutex_lock(&mutex);
        number++;
        printf("work1:%d\n",number);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    pthread_exit(NULL);
}
void* work2(void *)
{
    int i;
    for(i=0;i<10;i++)
    {
        pthread_mutex_lock(&mutex);
        number++;
        printf("work2:%d\n",number);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }
    pthread_exit(NULL);
}
int main()
{
    /*初始化互斥锁*/
    pthread_mutex_init(&mutex,NULL);
    /*创建线程1*/
    pthread_create(&thread[0],NULL,work1,NULL);
    /*创建线程2*/
    pthread_create(&thread[1],NULL,work2,NULL);
    /*等待线程1结束*/
    pthread_join(thread[0],NULL);
    /*等待线程2结束*/
    pthread_join(thread[1],NULL);
    return 0;
}

线程同步

多个线程按照规定的顺序执行,即为线程同步。线程同步可利用全局变量来实现,但这会导致执行效率低下,应采用专用的函数来实现同步。

初始化:pthread_cond_t cond_ready=PTHREAD_COND_INITIALIZER;
等待条件成熟:pthread_cond_wait(&cond_ready, &mut);
设置条件成熟:pthread_cond_signal(&cond_ready);

线程同步:进程创建了两个线程,其中线程work2必须在线程work1执行完后才执行

#include<stdio.h>
#include<pthread.h>
#include<unistd.h>
pthread_mutex_t mutex;
pthread_t thread[2];
int number=0;
pthread_cond_t cond_ready=PTHREAD_COND_INITIALIZER;
void* work1(void *)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);
        number++;
        printf("work1:%d\n",number);
        pthread_mutex_unlock(&mutex);
        if(number==5)
        {
            pthread_cond_signal(&cond_ready);/*发送信号,说明条件成熟*/
            break;
        }
        sleep(1);
    }
    pthread_exit(NULL);
}
void* work2(void *)
{
    pthread_mutex_lock(&mutex);
    if(number<5)
    {
        pthread_cond_wait(&cond_ready, &mutex);/*需要配合着互斥锁使用,当条件不成熟时会自动释放互斥锁,从而使其它进程能够获取互斥锁*/
    }
    number=0;
    printf("work2:%d\n",number);
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}
int main()
{
    /*初始化互斥锁*/
    pthread_mutex_init(&mutex,NULL);
    /*创建线程1*/
    pthread_create(&thread[0],NULL,work1,NULL);
    /*创建线程2*/
    pthread_create(&thread[1],NULL,work2,NULL);
    /*等待线程1结束*/
    pthread_join(thread[0],NULL);
    /*等待线程2结束*/
    pthread_join(thread[1],NULL);
    return 0;
}





生产者消费者

摘要 介绍linux线程的基本概念,线程间的互斥和同步机制,分析了linuxpthread库的API函数,并结合一个例子阐述多线程编程的核心技术,最后总结出多线程编程应注意的事项。
关键词 线程进程 同步 互斥
中图分类号:TP316 文献标识码:A
1.引言
目前,许多流行的多任务操作系统都提供线程机制,线程就是程序中的单个顺序控制流。利用多线程进行程序设计,就是将一个程序(进程)的任务划分为执行的多个部分(线程) ,每一个线程为一个顺序的单控制流,而所有线程都是并发执行的,这样,多线程程序就可以实现并行计算,高效利用多处理器。线程可分为用户级线程和内核级线程两种基本类型。用户级线程不需要内核支持,可以在用户程序中实现,线程调度、同步与互斥都需要用户程序自己完成。内核级线程需要内核参与,由内核完成线程调度并提供相应的系统调用,用户程序可以通过这些接口函数对线程进行一定的控制和管理。Linux操作系统提供了LinuxThreads库,它是符合POSIX1003.1c标准的内核级多线程函数库。在linuxthreads库中提供了一些多线程编程的关键函数,在多线程编程时应包括pthread.h文件。
2.LinuxThread中的关键库函数
2.1线程的创建和终止
int pthread_create(pthread_t * pthread,const pthread_attr_t *attr,void *(*start_routine(*void)),void *arg);调用此函数可以创建一个新的线程,新线程创建后执行start_routine 指定的程序。其中参数attr是用户希望创建线程的属性,当为NULL时表示以默认的属性创建线程。arg是向start_routine 传递的参数。当成功创建一个新的线程时,系统会自动为新线程分配一个线程ID号,并通过pthread 返回给调用者。
void pthread_exit(void *value_ptr);调用该函数可以退出线程,参数value_ptr是一个指向返回状态值的指针。
2.2线程控制函数
pthread_self(void);为了区分线程,在线程创建时系统为其分配一个唯一的ID号,由pthread_create()返回给调用者,也可以通过pthread_self()获取自己的线程ID。
Int pthread_join (pthread- t thread , void * *status);这个函数的作用是等待一个线程的结束。调用pthread_join()的线程将被挂起直到线程ID为参数thread 指定的线程终止。
int pthread_detach(pthread_t pthread);参数pthread代表的线程一旦终止,立即释放调该线程占有的所有资源。
2.3线程间的互斥
互斥量和临界区类似,只有拥有互斥量的线程才具有访问资源的权限,由于互斥对象只有一个,这就决定了任何情况下共享资源(代码或变量)都不会被多个线程同时访问。使用互斥不仅能够在同一应用程序的不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。Linux中通过pthread_mutex_t来定义互斥体机制完成互斥操作。具体的操作函数如下
pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);初使化一个互斥体变量mutex,参数attr表示按照attr属性创建互斥体变量mutex,如果参数attr为NULL,则以默认的方式创建。
pthread_mutex_lock(pthread_mutex_t *mutex);给一个互斥体变量上锁,如果mutex指定的互斥体已经被锁住,则调用线程将被阻塞直到拥有mutex的线程对mutex解锁为止。
Pthread_mutex_unlock(pthread_mutex_t *mutex);对参数mutex指定的互斥体变量解锁。
2.4线程间的同步
同步就是线程等待某一个事件的发生,当等待的事件发生时,被等待的线程和事件一起继续执行。如果等待的事件未到达则挂起。在linux操作系统中是通过条件变量来实现同步的。
Pthread_cond_init(pthread_cond_t *cond,const pthread_cond_t *attr);这个函数按参数attr指定的属性初使化一个条件变量cond。
Pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);等待一个事件(条件变量)的发生,发出调用的线程自动阻塞,直到相应的条件变量被置1。等待状态的线程不占用CPU时间。
pthread_cond_signal(pthread_cond_t *cond);解除一个等待参数cond指定的条件变量的线程的阻塞状态。
3.多线程编程的应用实例。
在这里利用多线程技术实现生产者和消费者问题,生产者线程向一缓冲区中写数据,消费者线程从缓冲区中读取数据,由于生产者线程和消费者线程共享同一缓冲区,为了正确读写数据,在使用缓冲队列时必须保持互斥。生产者线程和消费者线程必须满足:生产者写入缓冲区的数目不能超过缓冲区容量,消费者读取的数目不能超过生产者写入的数目。在程序中使用了一个小技巧来判断缓冲区是空还是满。在初始化时读指针和写指针为0;如果读指针等于写指针,则缓冲区是空的;如果(写指针+ 1) % N 等于读指针,则缓冲区是满的,%表示取余数,这时实际上有一个单元空出未用。下面是完整的程序段和注释。

#include<stdio.h>

#include<pthread.h>
#include<unistd.h>

#define BUFFER_SIZE 8 

struct prodcons {
    int buffer[BUFFER_SIZE]; 
    pthread_mutex_t lock;      //互斥LOCK
    int readpos , writepos; 
    pthread_cond_t notempty;   //缓冲区非空条件判断
    pthread_cond_t notfull;    //缓冲区未满条件判断
};

void init(struct prodcons * b){
    pthread_mutex_init(&b->lock,NULL);
    pthread_cond_init(&b->notempty,NULL);
    pthread_cond_init(&b->notfull,NULL);

    b->readpos=0;
    b->writepos=0;
}

void put(struct prodcons* b,int data){
    pthread_mutex_lock(&b->lock);

    if((b->writepos + 1) % BUFFER_SIZE == b->readpos)
    {
        pthread_cond_wait(&b->notfull, &b->lock) ;
    }
    
    b->buffer[b->writepos]=data;
    b->writepos++;
    
    if(b->writepos >= BUFFER_SIZE)
        b->writepos=0;
    
    pthread_cond_signal(&b->notempty);
    pthread_mutex_unlock(&b->lock);
}
int get(struct prodcons *b){
    int data;

    pthread_mutex_lock(&b->lock);
    if(b->writepos == b->readpos)
    {
        pthread_cond_wait(&b->notempty, &b->lock);
    }
    
    data = b->buffer[b->readpos];
    b->readpos++;

    if(b->readpos >= BUFFER_SIZE)
        b->readpos=0;
    
    pthread_cond_signal(&b->notfull);
    pthread_mutex_unlock(&b->lock);

    return data;
}

#define OVER (-1)
struct prodcons buffer;

void *producer(void *data)
{
    int n;
    
    for(n = 0; n < 10000; n++)
    {
        printf("%d \n", n) ;
        put(&buffer, n);
    }
    
    put(&buffer, OVER);
    
    return NULL;
}
void *consumer(void * data)
{
    int d;
    
    while(1)
    {
        d = get(&buffer);

        if(d == OVER)
            break;
        
        printf("%d\n", d);
    }

    return NULL;
}

int main(void)
{
    pthread_t th_a, th_b;
    
    void *retval;
    
    init(&buffer);
    
    pthread_create(&th_a, NULL, producer, 0);
    pthread_create(&th_b, NULL, consumer, 0);
    
    pthread_join(th_a, &retval);
    pthread_join(th_b, &retval);
    
    return 0;
}



上面的例子中,生产者负责将1到1000的整数写入缓冲区,而消费者负责从同一个缓冲区中读取写入的整数并打印出来。因为生产者和消费者是两个同时运行的线程,并且要使用同一个缓冲区进行数据交换,因此必须利用一种机制进行同步。通过上面的例子我们可以看到,多线程的最大好处是,除堆栈之外,几乎所有的数据均是共享的,因此线程间的通讯效率很高;缺点:因为共享所有数据,从而非常容易导致线程之间互相破坏数据,这一点在编程时必须注意。
4.结束语
Linux中基于POSIX标准的很好的支持了多线程技术,它减少了程序并发执行时的系统开销,提高了计算机的工作效率。在具体编程过程中要了解线程的间的关系,还要考虑共享数据的保护,在互斥和同步机制下保证代码的高效运行,程序编译时用gcc -D –REENTRANT -libpthread.xx.so filename.c编译。
5.参考文献
[1]郑燕飞,余海燕.Linux的多线程机制探讨与实践[J].计算机应用,2001.
[2]刘俞.Linux 多线程的互斥与同步控制及实践[J].安徽冶金科技职业学院学报,2005.
[3]姚继锋.Linux 应用实例与技巧[M] . 北京:机械工业出版社. 2001
[4]魏永明.Linux 实用教程[M]. 北京:电子工业出版社,2000.
[5]唐靖飚.UNIX平台下C 语言高级编程指南[M].北京:希望电子出版社,2000. 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值