linux 多线程的线程控制和线程通信

来源:互联网 发布:网络系统集成中的软件 编辑:程序博客网 时间:2024/06/05 07:02

1、Linux 线程概念

     进程与线程之间是有区别的,不过Linux内核只提供了轻量进程的支持,而其所谓的线程本质上在内核里仍然是进程。

     进程是资源分配的单位,同一进程中的多个线程共享该进程的资源。Linux中的线程只是在被创建时clone了父进程的资源,因此clone出来的进程表现为线程,只是它有共享父进程资源的特性。

   程序与线程库相链接即可支持Linux平台上的多线程,在程序中需包含头文件pthread. h,在编译链接时使用命令: 
gcc -D -REENTRANT -lpthread xxx. c

其中-REENTRANT宏使得相关库函数(如stdio.h、errno.h中函数) 是可重入的、线程安全的(thread-safe),-lpthread则意味着链接库目录下的libpthread.a或libpthread.so文件。

流行的线程模型有LinuxThreads 和 NPTL。使用线程库需要2.0以上版本的Linux内核,及相应版本的C库(libc 或glibc )。

参考:http://www.ibm.com/developerworks/cn/linux/l-threading.html

2、线程控制 

(1)线程创建 


进程被创建时,系统会为其创建一个主线程,而要在进程中创建新的线程,则可以调用pthread_create: 

pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (start_routine)(void*), void *arg);

start_routine为新线程的入口函数,arg为传递给start_routine的参数。 

每个线程都有自己的线程ID,以便在进程内区分。线程ID在pthread_create调用时回返给创建线程的调用者;一个线程也可以在创建后使用pthread_self()调用获取自己的线程ID: 

pthread_self (void) ;

(2)线程退出 


线程的退出方式: 

1)执行完成后隐式退出
2)由线程本身显示调用pthread_exit 函数退出

pthread_exit (void * retval) ;

3)被其他线程用pthread_cance函数终止

pthread_cance (pthread_t thread) ;

在某线程中调用此函数,可以终止由参数thread 指定的线程。 

如果一个线程要等待另一个线程的终止,可以使用pthread_join函数,该函数的作用是调用pthread_join的线程将被挂起直到线程ID为参数thread的线程终止: 

pthread_join (pthread_t thread, void** threadreturn);



3、线程通信 


(1)线程互斥 


互斥意味着“排它”,即两个线程不能同时进入被互斥保护的代码。Linux下可以通过pthread_mutex_t 定义互斥体机制完成多线程的互斥操作,该机制的作用是对某个需要互斥的部分,在进入时先得到互斥体,如果没有得到互斥体,表明互斥部分被其它线程拥有,此时欲获取互斥体的线程阻塞,直到拥有该互斥体的线程完成互斥部分的操作为止。 

下面的代码实现了对共享全局变量x1 用互斥体mutex 进行保护的目的: 
int x1; // 进程中的全局变量 pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL); //按缺省的属性初始化互斥体变量mutex pthread_mutex_lock(&mutex); // 给互斥体变量加锁 … //对变量x1 的操作 phtread_mutex_unlock(&mutex); // 给互斥体变量解除锁


(2)线程同步 


同步就是线程等待某个事件的发生。只有当等待的事件发生线程才继续执行,否则线程挂起并放弃处理器。当多个线程协作时,相互作用的任务必须在一定的条件下同步。 

1)条件变量

Linux下的C语言编程有多种线程同步机制,最典型的是条件变量(condition variable)。

pthread_cond_init用来创建一个条件变量,其函数原型为: 

pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr);

pthread_cond_wait和pthread_cond_timedwait用来等待条件变量被设置,值得注意的是这两个等待调用需要一个已经上锁的互斥体mutex,这是为了防止在真正进入等待状态之前别的线程有可能设置该条件变量而产生竞争。

pthread_cond_wait的函数原型为: 

pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);

pthread_cond_broadcast用于设置条件变量,即使得事件发生,这样等待该事件的线程将不再阻塞: 

pthread_cond_broadcast (pthread_cond_t *cond) ;

pthread_cond_signal则用于解除某一个等待线程的阻塞状态: 

pthread_cond_signal (pthread_cond_t *cond) ;


pthread_cond_destroy(pthread_cond_t *cond)  则用于释放一个条件变量的资源。 

pthread_cond_destroy(pthread_cond_t *cond) ;


pthread_cond_timedwait 计时等待方式

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);

如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。


pthread_cond_timedwait 和pthread_cond_wait,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()(或pthread_cond_timedwait(),下同)的竞争条件(Race Condition)。mutex互斥锁必须是普通锁(PTHREAD_MUTEX_TIMED_NP)或者适应锁(PTHREAD_MUTEX_ADAPTIVE_NP),且在调用pthread_cond_wait()前必须由本线程加锁(pthread_mutex_lock()),而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()之前,mutex将被重新加锁,以与进入pthread_cond_wait()前的加锁动作对应。
激发条件有两种形式,pthread_cond_signal()激活一个等待该条件的线程,存在多个等待线程时按入队顺序激活其中一个;而pthread_cond_broadcast()则激活所有等待线程。


条件变量实例1:

以名的生产者/消费者问题为例来阐述Linux线程的控制和通信。一组生产者线程与一组消费者线程通过缓冲区发生联系。生产者线程将生产的产品送入缓冲区,消费者线程则从中取出产品。缓冲区有N 个,是一个环形的缓冲池。 

#include <stdio.h>#include <pthread.h>#define BUFFER_SIZE 16 // 缓冲区数量// 缓冲区相关数据结构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;    /* 设置缓冲区非空的条件变量*/    if (b->writepos == (b->readpos + 1) % BUFFER_SIZE)//判断通知条件    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;    /* 设置缓冲区未满的条件变量*/    if ((b->writepos + 2) % BUFFER_SIZE == b->readpos)//判断通知条件    pthread_cond_signal(&b->notfull);//读者发送信号给写者    pthread_mutex_unlock(&b->lock);    return data;}/* 测试:生产者线程将1 到10000 的整数送入缓冲区,消费者线   程从缓冲区中获取整数,两者都打印信息*/#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;}

条件变量实例2:

#include <pthread.h>#include <unistd.h>#include <stdio.h>#include <string.h>#include <stdlib.h>static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;struct node{    intn_number;    structnode *n_next;} *head = NULL; /*[thread_func]*//*释放节点内存 */static void cleanup_handler(void*arg){    printf("Cleanup handler of second thread.\n");    free(arg);    pthread_mutex_unlock(&mtx);}static void* thread_func(void*arg){    structnode *p = NULL;    pthread_cleanup_push(cleanup_handler, p);//pthread_cond_wait是取消点,所以需要在被取消时解锁    while(1)    {        pthread_mutex_lock(&mtx);        //这个mutex_lock主要是用来保护wait等待临界时期的情况,        //当在wait为放入队列时,这时,已经存在Head条件等待激活        //的条件,此时可能会漏掉这种处理        //这个while要特别说明一下,单个pthread_cond_wait功能很完善,        //为何这里要有一个while (head == NULL)呢?因为pthread_cond_wait        //里的线程可能会被意外唤醒,如果这个时候head == NULL,则        //则不是我们想要的情况。这个时候,        //应该让线程继续进入pthread_cond_wait        //(本例逻辑上使用if while(head == NULL)也可以达到目的,使用while(head == NULL)是为了编码安全的习惯)               while(head == NULL)        {            pthread_cond_wait(&cond, &mtx);            // pthread_cond_wait会先解除之前的pthread_mutex_lock锁定的mtx,            //然后阻塞在等待队列里休眠,直到再次被唤醒            //(大多数情况下是等待的条件成立而被唤醒,唤醒后,            //该进程会先锁定先pthread_mutex_lock(&mtx);,            // 再读取资源 用这个流程是比较清楚的            /*block-->unlock-->wait() return-->lock*/        }      p = head;        head = head->n_next;        printf("Got %d from front of queue\n", p->n_number);        free(p);        pthread_mutex_unlock(&mtx);//临界区数据操作完毕,释放互斥锁    }    pthread_cleanup_pop(0);    return0;}int main(void){    pthread_t tid;    inti;    structnode *p;    pthread_create(&tid, NULL, thread_func, NULL);    //子线程会一直等待资源,类似生产者和消费者,    //但是这里的消费者可以是多个消费者,    //而不仅仅支持普通的单个消费者,这个模型虽然简单,但是很强大    for(i = 0; i < 10; i++)    {        p = (struct node *)malloc(sizeof(struct node));//分配新节点        p->n_number = i;        pthread_mutex_lock(&mtx);//需要操作head这个临界资源,先加锁(需要修改共享资源的才在pthread_cond_signal前后加锁,并把资源操作放置在临界区内)        p->n_next = head;//放到链表头部        head = p;        pthread_cond_signal(&cond);//有数据就通知读者        pthread_mutex_unlock(&mtx);//解锁        sleep(1);    }    printf("thread 1 wanna end the cancel thread 2.\n");    pthread_cancel(tid);    //关于pthread_cancel,有一点额外的说明,它是从外部终止子线程,    //子线程会在最近的取消点,退出线程,而在我们的代码里,最近的    //取消点肯定就是pthread_cond_wait()了。    pthread_join(tid, NULL);    printf("All done -- exiting\n");    return0;}


2)信号量

在头文件semaphore.h 中定义的信号量则完成了互斥体和条件变量的封装,按照多线程程序设计中访问控制机制,控制对资源的同步访问,提供程序设计人员更方便的调用接口。 

sem_init(sem_t *sem, int pshared, unsigned int val);

这个函数初始化一个信号量sem 的值为val,参数pshared 是共享属性控制,表明是否在进程间共享。 

sem_wait(sem_t *sem);

调用该函数时,若sem为无状态,调用线程阻塞,等待信号量sem值增加(post )成为有信号状态;若sem为有状态,调用线程顺序执行,但信号量的值减一。

sem_post(sem_t *sem); 

调用该函数,信号量sem的值增加,可以从无信号状态变为有信号状态。

    

 

4、WIN32、Linux线程函数比较


对于win32和linux的线程控制和线程通信函数,本质内容一致。接口如下:

事项WIN32Linux 线程创建CreateThreadpthread_create 线程终止执行完成后退出;线程自身调用ExitThread函数即终止自己;被其他线程调用函数TerminateThread函数执行完成后退出;由线程本身调用pthread_exit 退出;被其他线程调用函数pthread_cance终止 获取线程IDGetCurrentThreadIdpthread_self 创建互斥CreateMutexpthread_mutex_init 获取互斥WaitForSingleObject、WaitForMultipleObjectspthread_mutex_lock 释放互斥ReleaseMutexphtread_mutex_unlock 创建信号量CreateSemaphoresem_init 等待信号量WaitForSingleObjectsem_wait 释放信号量ReleaseSemaphoresem_post 

0 0
原创粉丝点击