使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立 的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相 同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要 的时间。
使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不 仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数 据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是 编写多线程程序时最需要注意的地方。
1、函数语法简述。
线程的创建和终止
pthread_create
头文件: pthread.h
函数原型: int pthread_create (pthread_t* thread, pthread_attr_t* attr,
void* (start_routine)(void*), void* arg);
函数传入值: thread: 线程标识符
attr: 线程属性设置
start_routine:线程函数入口
arg:传递给线程函数的参数
返回值: 0: 成功
-1: 失败
调用此函数可以创建一个新的线程,新线程创建后执行start_routine 指定的程序。其中参数attr是用户希望创建线程的属性,当为NULL时表示以默认的属性创建线程。arg是向start_routine 传递的参数。当成功创建一个新的线程时,系统会自动为新线程分配一个线程ID号,并通过pthread 返回给调用者。
pthread_exit
头文件: pthread.h
函数原型: void pthread_exit(void *value_ptr);
函数传入值:retval:pthread_exit()调用者线程的返回值,可又其他函数如pthread_join来检索获取。
调用该函数可以退出线程,参数value_ptr是一个指向返回状态值的指针。
phread_join
头文件: pthread.h
函数原型: int pthread_join (pthread_t* thread, void** thread_return);
函数传入值:thread:等待线程的标识符。
thread_return:用户定义的指针,用来存储被等待线程的返回值(不为NULL值);
函数返回值:成功: 0
失败:-1
这个函数的作用是等待一个线程的结束。调用pthread_join()的线程将被挂起直到线程ID为参数thread 指定的线程终止。
pthread_self(void);
为了区分线程,在线程创建时系统为其分配一个唯一的ID号,由pthread_create()返回给调用者,也可以通过pthread_self()获取自己的线程ID。
pthread_detach
创建一个线程默认的状态是joinable, 如果一个线程结束运行但没有被join,则它的状态类似于进程中的Zombie Process,即还有一部分资源没有被回收(退出状态码),所以创建线程者应该调用pthread_join来等待线程运行结束,并可得到线程的退出代码,回收其资源(类似于wait,waitpid)
但是调用pthread_join(pthread_id)后,如果该线程没有运行结束,调用者会被阻塞,在有些情况下我们并不希望如此,比如在Web服务器中当主线程为每个新来的链接创建一个子线程进行处理的时候,主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的链接),这时可以在子线程中加入代码
pthread_detach(pthread_self())
或者父线程调用
pthread_detach(thread_id)(非阻塞,可立即返回)
这将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。
linux线程执行和windows不同,pthread有两种状态joinable状态和unjoinable状态,
如果线程是joinable状态,当线程函数自己返回退出时或pthread_exit时都不会释放线程所占用堆栈和线程描述符(总计8K多)。只有当你调用了pthread_join之后这些资源才会被释放。
若是unjoinable状态的线程,这些资源在线程函数退出时或pthread_exit时自动会被释放。
unjoinable属性可以在pthread_create时指定,或在线程创建后在线程中pthread_detach自己, 如:pthread_detach(pthread_self()),将状态改为unjoinable状态,确保资源的释放。或者将线程置为 joinable,然后适时调用pthread_join.
pthread_mutex_init
头文件: <pthread.h>
函数原型: int pthread_mutex_init (pthread_mutex_t* mutex,
const pthread_mutexattr_t* mutexattr);
函数传入值: mutex:互斥锁。
mutexattr:PTHREAD_MUTEX_INITIALIZER:创建快速互斥锁。
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:创建递归互斥锁。
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP:创建检错互斥锁。
函数返回值: 成功:0
出错:-1
上锁函数:
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_trylock (pthread_mutex_t* mutex);
int pthread_mutex_unlock (pthread_mutex_t* mutex);
int pthread_mutex_destroy (pthread_mutex_t* mutex);
函数传入值: mutex:互斥锁。
函数返回值: 同上。
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指定的互斥体变量解锁。
线程间的同步
同步就是线程等待某一个事件的发生,当等待的事件发生时,被等待的线程和事件一起继续执行。如果等待的事件未到达则挂起。在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指定的条件变量的线程的阻塞状态。
生产者和消费者问题,生产者线程向一缓冲区中写数据,消费者线程从缓冲区中读取数据,由于生产者线程和消费者线程共享同一缓冲区,为了正确读写数据,在使用缓冲队列时必须保持互斥。生产者线程和消费者线程必须满足:生产者写入缓冲区的数目不能超过缓冲区容量,消费者读取的数目不能超过生产者写入的数目。在程序中使用了一个小技巧来判断缓冲区是空还是满。在初始化时读指针和写指针为0;如果读指针等于写指针,则缓冲区是空的;如果(写指针+ 1) % N 等于读指针,则缓冲区是满的,%表示取余数,这时实际上有一个单元空出未用。
#include<stdio.h> #include<stdlib.h> #include<time.h> #include<pthread.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的整数写入缓冲区,而消费者负责从同一个缓冲区中读取写入的整数并打印出来。因为生产者和消费者是两个同时运行的线程,并且要使用同一个缓冲区进行数据交换,因此必须利用一种机制进行同步。
多线程的最大好处是,除堆栈之外,几乎所有的数据均是共享的,因此线程间的通讯效率很高;
缺点:因为共享所有数据,从而非常容易导致线程之间互相破坏数据。
版权声明:本文为博主原创文章,未经博主允许不得转载。