Linux多线程编程线程基本编程这里要讲的线程相关操作都是用户空间中的线程操作。在Linux中pthread是一个遵循POSlX标准的通用的线程库具有良好的可移植性。创建线程时要指定线程的执行函数通常使用函数pthread_create来创建线程。线程创建之后就开始执行相应的线程函数。在该函数运行完之后线程结束。退出线程的方法是使用函数pthread_exit这是线程的主动行为。需要注意的是在使用线程函数时不能使用exit函数退出。exit函数的作用是使当前进程终止。通常一个进程包含多诚个线程如果调用了exit函数该进程中的所有线程都会尚8结束。因此在线程中要使用pthread_exit函数代替exit线程的使用意义函数结束当前线程。进程之间可以用wait()函数来等待回收子进程线程之间也有类似机制那就是pthread_join()函数。这个函数是一个线程阻塞的函数调用它的函数将一直等待到指定的线程结束为止。当函数返回时表明可以释放已结束线程的相关资源。前面提到线程可以调用pthread_exit()函数主动结束。在某些应用中经常需要在一个线程中去终止另一个线程的可以通过pthread_cancel()函数实现这种功能。当然在被取消的线程的内部需要先调用pthread_setcancel()函数和pthread_setcanceltype()函数设置相应的取消状态。创建了 3个线程为了更好地理解线程之间的并行执行 让 3个线程执行同一个执行函数。 每个线程执行 5次循环可以看成 5个小任务每次循环之间会随机等待 1 6s 意义在于模拟每个任务的完成时间是随机的。*thread.c*/#includestdio.h#includestdlib.h#includepthread.h#includeunistd.h#defineTHREAD_NUM3/*线程数*/#defineREPEAT_NUM6/*每个线程中的循环次数*/#defineDELAY_TIME_LEVELS6.0/*循环之间的是大时间间隔*/void*thrd_func(void*arg){intthrd_num(int)arg;intdelay_time0;intcount0;printf(Thread %d is starting\n,thrd_num);for(count0;countREPEAT_NUM;count){delay_time(int)(rand()*DELAY_TIME_LEVELS/(RAND_MAX))1;sleep(delay_time);printf(\tThread %d :job %d delay %d\n,thrd_num,count,delay_time);}printf(Thread %d finished\n,thrd_num);pthread_exit(NULL);}intmain(void){pthread_tthread[THREAD_NUM];intno0,res;void*thrd_ret;srand(time(NULL));for(no0;noTHREAD_NUM;no){/*创建多线程*/respthread_create(thread[no],NULL,thrd_func,(void*)no);if(res!0){printf(Create thread %d faild\n,no);exit(res);}}printf(Create threads success\n Waiting for threads to finist...\n);for(no0;noTHREAD_NUM;no){/*等线程结束*/respthread_join(thread[no],thrd_ret);if(!res){printf(thread %d joined \n,no);}else{printf(Thread %d join failed\n,no);}}return0;}线程之间的同步与互斥由于线程共享进程的资源和地址空间因此在对这些资源进行操作时必须考虑到线程间资源访问的同步与互厅问题。下面介绍 POSIX 中线程的同步和互斤机制互斥锁和信号量。互斤锁线程控制互斥锁通过简单的加锁方法来保证对共享资源的原子操作。互斥锁只有两种状态上锁和解锁可以把互斥锁看成某种意义上的全局变量。 在同一时刻只能有一个线程持有某个互斥锁 拥有互斥锁的线程能够对共享资源进行操作。 若线程对一个已经被上锁的互斥锁加锁该线程就会睡眠直到其他线程释放掉互斥锁为止。可以说这把互斥锁保证让每个线程对共享资源按顺序进行原子操作。互斥锁机制的基本函数如下。 1 互斥锁初始化 pthread_mutex _init()。 2 互斥锁上锁 pthread_mutex_lock()。 3 互斥锁判断上锁 pthread_mutex_trylock()。 4 互斥 锁 解 锁 pthread_mutex_unloc k()。 5 消除互斥 锁 pt hread_mutex_destroy() 。信号量线程控制在前面已经讲到信号量也就是操作系统中所用到的 PV 原子操作它广泛用于进程或线程间的同步与互斥。信号量本质上是一个非负的整数计数器它被用来控制对公共资源的访问。PV 原子操作主要用于进程或线程间的同步和互斥这两种典型情况。若用于互斥几个进程或线程往往只设置一个信号量 sem当信号量用于同步操作时往往会设置多个信号量并安排不同的初始值来实现它们之间的顺序执行它们的操作流程如图Linux实现了POSIX的无名信号量主要用于线程间的互斥与同步。这里主要介绍几个常见函数。1sem_init()用于初始化信号量。2sem_wait()和sem_trywait()都相当于P操作在信号量大于零时它们都能将信号量的值减1。两者的区别在于若信号量的值为零时sem_wait()将会阻塞线程而sem_trywait()则会立即返回。3sem_post()相当于V操作它将信号量的值加1同时唤醒等待的线程。4sem_getvalue()用于获取信号量的值。5semdestroy()用于删除信号量/*thread_mutex.c*/#includestdio.h#includestdlib.h#includepthread.h#includeunistd.h#defineTHREAD_NUM3/*线程数*/#defineREPEAT_NUM3/*每个线程循环次数*/#defineDELAY_TIME_LEVELS6.0/*循环之间的最大时间间隔*/pthread_mutex_tmutex;void*thrd_func(void*arg){intthrd_num(int)arg;intdelay_time0,count0;intres;/*互斥锁上锁*/respthread_mutex_lock(mutex);if(res){printf(Thread %d lock faild\n,thrd_num);pthread_exit(NULL);}printf(Thread %d is starting\n,thrd_num);for(count0;countREPEAT_NUM;count){delay_time(int)(rand()*DELAY_TIME_LEVELS/(RAND_MAX))1;sleep(delay_time);printf(\tThread %d:job %d delay %d\n,thrd_num,count,delay_time);}printf(Thread %d finished\n,thrd_num);/*互斥锁解锁*/pthread_mutex_unlock(mutex);pthread_exit(NULL);}intmain(void){pthread_tthread[THREAD_NUM];intno0,res;void*thrd_ret;srand(time(NULL));/*互斥锁初始化*/pthread_mutex_init(mutex,NULL);for(no0;noTHREAD_NUM;no){respthread_create(thread[no],NULL,thrd_func,(void*)no);if(res!0){printf(Create thread %d failed\n,no);exit(res);}}printf(Create treads success\n Waiting for threads to finish...\n);for(no0;noTHREAD_NUM;no){respthread_join(thread[no],thrd_ret);if(!res){printf(Thread %d joined\n,no);}else{printf(Thread %d join faild\n,no);}}pthread_mutex_destroy(mutex);return0;}线程属性读者是否还记得pthread_create()函数的第二个参数(pthread_attr_t *attr的含义该参数表示线程的属性。在示例中将该值设为NULL也就是采用默认属性线程的多项属性都是可以更改的。线程的常用属性主要包括分离属性、堆栈大小以及调度策略和优先级。其中系统默认的属性为非分离、默认1MB的堆栈以及与主线程同样的调度策略和相同的优先级。下面介绍分离属性。分离属性是用来决定一个线程何时释放自已的资源。在非分离情况下当一个线程结束时它所占用的系统资源并没有被释放也就是没有真正的终止。只有当pthread_join()函数返回时线程才能释放自己占用的系统资源。而在分离情况下一个线程结束时将立即释放它所占有的系统资源。线程属性的设置可通过相关的函数来完成。通常先调用pthread_attr_init()函数进行初始化之后再设置相应的属性最后调用pthread_attr_destroy()函数进行清理和回收。分离属性线程/*thread_attr.c*/#includestdio.h#includestdlib.h#includepthread.h#defineREPEAT_NUM3/*线程中的小任务数*/#defineDELAY_TIME_LEVELS6.0/*小任务之间最大时间间隔*/intfinish_flag0;void*thrd_func(void*arg){intdelay_time0;intcount0;printf(Thread is starting\n);for(count0;countREPEAT_NUM;count){delay_time(int)(rand()*DELAY_TIME_LEVELS/(RAND_MAX))1;sleep(delay_time);printf(\tThread:job %d delay %d\n,count,delay_time);}printf(Thread finished\n);finish_flag1;pthread_exit(NULL);}intmain(void){pthread_tthread;pthread_attr attr;intno0;res;void*thrd_ret;srand(time(NULL));/*初始化线程属性对象*/respthread_attr_setdetachstate(attr,PTHREAD_CREATE_DETACHEDA);if(res!0){printf(Setting attribute failed\n);exit(res);}respthread_create(thread,attr,thrd_func,NULL);if(res!0){printf(Create thread failed\n);exit(res);}/*释放线程属性对象*/pthread_attr_destroy(attr);printf(Create thread success\n);while(!finish_flag){printf(Waiting for thread to finisth...\n);sleep(2);}return0;}实践内容 多线程编程实践目的通过编写经典的 “生产者一消费者” 问题的实验可以进一步熟悉 Linu x 中的多线程编程并且掌握用信号量处理线程间的同步和互斥问题。实践内容“生产者一消费者” 问题描述如下。有一个有限缓冲区这里用有名管道实现 FIFO 式缓冲区和两个线程生产者和消费者它们分别不停地把产品放入缓冲区和从缓冲区中拿走产品。一个生产者在缓冲区满的时候必须等待一个消费者在缓冲区空的时候也必须等待。另外因为缓冲区是临界资源所以生产者和消费者之间必须互斥执行。这里要求使用有名管道来模拟有限缓冲区 并且使用信号量来解决 “生产者一消费者” 问题中的同步和互斥问题。3. 实践步骤 1 信号量分析。这里使用 3 个信号量 其中两 个信号量 avail 和 full 分别用于解决生产者和消费者线程之间的同步问题mutex 是用于这两个线程之间的互斥问题。其中 avail 表示冲 区中的空单元数 初始值为 N; full 表示缓冲区中非空单元数,初始值为 0; mute x 是互斥信号量,初始值为 1 读者可以用互斥锁来实现互斥操作 。画出流程图3 编写代码。本实验的代码中缓冲区拥有 3个单元 每个单元为 5字节。为了尽量体现每个信号量的意义在程序中生产过程和消费过程是随机 采取 0 5s 的随机时间间隔 进行的而且生产者的速度比消费者的速度平均快两倍左右 这种关系可以相反 。 生产者一次生产 一个单元的产品 放入 hello 字符串 消费者一次消费一个单元的产品 。/*producer-customer.c*/#includestdio.h#includestdlib.h#includestring.h#includeunistd.h#includefcntl.h#includepthread.h#includeerrno.h#includesemaphore.h#includesys/ipc.h#includesys/types.h#includesys/stat.h#defineMYFIFOmyfifo//缓冲区有名管道的名字#defineBUFFER_SIZE3//缓冲区的单元数#defineUNIT_SIZE5//每个单元的大小#defineRUN_TIME30//运行时间#defineDELAY_TIME_LEVELS5.0//周期的最大值intfd;time_tend_time;sem_tmutex,full,avail;//三个信号量/*生产者线程*/void*producer(void*arg){intreal_write;intdelay_time0;while(time(NULL)end_time){delay_time(int)(rand()*DELAY_TIME_LEVELS/(RAND_MAX)/2.0)1;sleep(delay_time);/*P操作信号量avail和mutex*/sem_wait(avail);sem_wait(mutex);printf(\nProducer:delay%d\n,delay_time);/*生产者写入数据*/if((real_writewrite(fd,hello,UNIT_SIZE))-1){if(errnoEAGAIN){printf(The FIFO has not been read yet.Please try later\n);}}else{printf(Write %d to the FIFO\n,real_write);}/*V操作信号量full和mutex*/sem_post(full);sem_post(mutex);}pthread_exit(NULL);}/*消费者线程*/void*customer(void*arg){unsignedcharread_buf[UNIT_SIZE];intreal_read;intdelay_time;while(time(NULL)end_time){delay_time(int)(rand()*DELAY_TIME_LEVELS/(RAND_MAX))1;sleep(delay_time);/*P操作信号量full和mutex*/sem_wait(full);sem_wait(mutex);memset(read_buf,0,UNIT_SIZE);printf(\nCustomer:delay %d\n,delay_time);if((real_readread(fd,read_buf,UNIT_SIZE))-1){if(errnoEAGAIN){printf(No data yer \n);}}printf(Read %s from FIFO\n,read_buf);/*V操作信号量avail和mutex*/sem_post(avail);sem_post(mutex);}pthread_exit(NULL);}intmain(){pthread_tthrd_prd_id,thrd_cst_id;pthread_tmon_th_id;intret;srand(time(NULL));end_timetime(NULL)RUN_TIME;/*创建有名管道*/intmkfiforetmkfifo(MYFIFO,0777);printf(mkfiforet:%d,O_CREAT:%d,O_EXCL:%d,EEXIST:%d,errno:%d\n,mkfiforet,O_CREAT,O_EXCL,EEXIST,errno);if(mkfiforet0(errno!EEXIST)){printf(Cannot create fifo\n);returnerrno;}/*打开管道*/fdopen(MYFIFO,O_RDWR);if(fd-1){printf(Open fifo error\n);returnfd;}/*初始化互斥信号量为1*/retsem_init(mutex,0,1);/*初始化avail信号量为N*/retsem_init(avail,0,BUFFER_SIZE);/*初始化full信号量为0*/retsem_init(full,0,0);if(ret!0){printf(Any semaphore initialization failed\n);returnret;}/*创建两个线程*/retpthread_create(thrd_prd_id,NULL,producer,NULL);if(ret!0){printf(Create producer thread error\n);returnret;}retpthread_create(thrd_cst_id,NULL,customer,NULL);if(ret!0){printf(Create customer thread error\n);returnret;}pthread_join(thrd_prd_id,NULL);pthread_join(thrd_cst_id,NULL);close(fd);unlink(MYFIFO);return0;}