首页 > 代码库 > Linux 多线程编程
Linux 多线程编程
前言
Linux系统下的多线程遵循POSIX线程接口,称为pthread。编写Linux下的多线程程序,需要使用头文件pthread.h,连接时需要使用libpthread.a。Linux下pthread的实现是通过系统调用clone()来实现的。clone()是Linux所特有的系统调用,它的使用方式类似fork。
基础知识点和代码实现
1.运行一个进程中的多个线程,彼此之间使用相同的地址空间,共享大部分数据。
2.启动一个线程所花费的空间远远小于启动一个进程所话费的空间。
3.线程间切换所需要的时间远远小于进程间切换所需要的时间。
4.不同进程具有独立的数据空间,数据的传递只能通过通信的方式。--费时、不方便
统一进程下的线程之间共享数据空间,一个线程数据可以直接为其他线程所用。--快捷、方便5.编写多线程需要注意的地方:
有的变量不能同时被两个线程所修改子程序中声明为static的数据可能为多线程带来灾难性打击
6.多线程优点:
1)提高应用程序相响应,将耗时长的操作置于一个新的线程,避免等待。2)使CPU多核系统更加高效。
3)改善程序结构。将长而复杂的进程可以分为多个线程。
7. 进程是资源分配的基本单位,线程没什么资源。共享进程资源
8. volatile的作用是: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值.
简单地说就是防止编译器对代码进行优化.比如如下程序:XBYTE[2]=0x55; XBYTE[2]=0x56; XBYTE[2]=0x57; XBYTE[2]=0x58;对外部硬件而言,上述四条语句分别表示不同的操作,会产生四种不同的动作,但是编译器却会对上述四条语句进行优化,认为只有XBYTE[2]=0x58(即忽略前三条语句,只产生一条机器代码)。如果键入volatile,则编译器会逐一的进行编译并产生相应的机器代码(产生四条代码).
9.线程相关操作
9.1线程的标识符 :pthread_tpthread_t 在头文件/usr/include/bits/pthreadtypes.h定义如下:
typedef unsigned long int pthread_t;
9.2创建线程:pthread_creat
原型为:
extern int pthread_create _P((pthread_t *_thread,_const pthread_attr_t *_attr,void *(*_start_routine) (void *),void *_arg));第一个参数:指向线程标识符的指针 第二个参数:设置线程属性 第三个参数:线程运行函数的起始地址 最后一个参数:运行函数的参数
线程创建成功,返回0,不为0,创建失败。
常见错误返回代码:EAGAIN:系统限制创建新的线程 EINVAL:线程的属性值非法
线程创建成功,新创建线程运行参数三和参数四确定的函数。
9.3等待线程结束pthread_join和线程结束pthread_exit
pythread_join函数原型:
extern int pthread_join_P((pthread_t _th,void**_thread_return));第一个参数:被等待的线程标识符 第二个参数:一个用户定义的指针,可以用来存储被等待线程的返回值
这个函数是线程阻塞函数,调用它的函数一直等待到被等待线程结束为止,函数返回时,被等待线程的资源被收回。
线程结束有两种途径:函数结束,调用它的线程结束;另一种方式通过函数pthread_exit来实现。
pthread_exit函数原型:exter void pthread_exit_P((void*_retval)_attribute_((_noreturn_));
唯一的参数是函数的返回代码:只要pthread_join中的第二个参数thread_return不是NULL,该值传递给thread_return。
一个线程不能被多个线程等待,否则第一个接受到的信号的线程成功返回,其余调用pthread_join的线程则返回错误代码ESRCH.10.线程的同步
虽然线程本地存储可以避免线程访问共享数据,但是线程之间的大部分数据始终还是共享的。在涉及到对共享数据进行读写操作时,就必须使用同步机制,否则就会造成线程们哄抢共享数据的结果,这会把你的数据弄的七零八落理不清头绪。Linux提供的线程同步机制主要有互斥锁和条件变量。
10.1互斥锁
所谓的互斥就是线程之间互相排斥,获得资源的线程排斥其它没有获得资源的线程。Linux使用互斥锁来实现这种机制。
既然叫锁,就有加锁和解锁的概念。当线程获得了加锁的资格,那么它将独享这个锁,其它线程一旦试图去碰触这个锁就立即被系统“拍晕”。当加锁的线程解开并放弃了这个锁之后,那些被“拍晕”的线程会被系统唤醒,然后继续去争抢这个锁。至于谁能抢到,只有天知道。但是总有一个能抢到。于是其它来凑热闹的线程又被系统给“拍晕”了……如此反复。
从互斥锁的这种行为看,线程加锁和解锁之间的代码相当于一个独木桥,同意时刻只有一个线程能执行。从全局上看,在这个地方,所有并行运行的线程都变成了排队运行了。比较专业的叫法是同步执行,这段代码区域叫临界区。同步执行就破坏了线程并行性的初衷了,临界区越大破坏得越厉害。所以在实际应用中,应该尽量避免有临界区出现。实在不行,临界区也要尽量的小
Linux初始化和销毁互斥锁的接口是pthread_mutex_init()和pthead_mutex_destroy(),对于加锁和解锁则有pthread_mutex_lock()、pthread_mutex_trylock()和pthread_mutex_unlock()。这些接口的完整定义如下:
从这些定义中可以看到,互斥锁也是有属性的。只不过这个属性在绝大多数情况下都不需要改动,所以使用默认的属性就行。方法就是给它传递NULL。int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); int pthread_mutex_destory(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);phtread_mutex_trylock()比较特别,用它试图加锁的线程永远都不会被系统“拍晕”,只是通过返回EBUSY来告诉程序员这个锁已经有人用了。至于是否继续“强闯”临界区,则由程序员决定。系统提供这个接口的目的可不是让线程“强闯”临界区的。它的根本目的还是为了提高并行性,留着这个线程去干点其它有意义的事情。当然,如果很幸运恰巧这个时候还没有人拥有这把锁,那么自然也会取得临界区的使用权。
互斥锁在同一个线程内,没有互斥的特性。也就是说,线程不能利用互斥锁让系统将自己“拍晕”。解释这个现象的一个很好的理由就是,拥有锁的线程把自己“拍晕”了,谁还能再拥有这把锁呢?但是另外情况需要避免,就是两个线程已经各自拥有一把锁了,但是还想得到对方的锁,这个时候两个线程都会被“拍晕”。一旦这种情况发生,就谁都不能获得这个锁了,这种情况还有一个著名的名字——死锁。死锁是永远都要避免的事情,因为这是严重损人不利己的行为。
10.2 条件变量条件变量关键点在“变量”上。与锁的不同之处就是,当线程遇到这个“变量”,并不是类似锁那样的被系统给“拍晕”,而是根据“条件”来选择是否在那里等待。等待什么呢?等待允许通过的“信号”。这个“信号”是系统控制的吗?显然不是!它是由另外一个线程来控制的。
条件变量是一种事件机制。由一类线程来控制“事件”的发生,另外一类线程等待“事件”的发生。为了实现这种机制,条件变量必须是共享于线程之间的全局变量。而且,条件变量也需要与互斥锁同时使用。
初始化和销毁条件变量的接口是pthread_cond_init()和pthread_cond_destory();控制“事件”发生的接口是pthread_cond_signal()或pthread_cond_broadcast();等待“事件”发生的接口是pthead_cond_wait()或pthread_cond_timedwait()。它们的完整定义如下:int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); int pthread_cond_destory(pthread_cond_t *cond); int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const timespec *abstime); int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond);对于等待“事件”的接口从其名称中可以看出,一种是无限期等待,一种是限时等待。后者与互斥锁的pthread_mutex_trylock()有些类似,即当等待的“事件”经过一段时间之后依然没有发生,那就去干点别的有意义的事情去。而对于控制“事件”发生的接口则有“单播”和“广播”之说。所谓单播就是只有一个线程会得到“事件”已经发生了的“通知”,而广播就是所有线程都会得到“通知”。对于广播情况,所有被“通知”到的线程也要经过由互斥锁控制的独木桥。
这里插入一个简单多线程的例子:
#include 要包含头文件pthread.h 还需要链接libpthread.so库,链接阶段应该有类似指令:gcc program.o -o program -lpthread多线程例子:
#include<stdio.h> #include<pthread.h> void *thread(void *arg) { printf("This is a thread and arg=%d.\n",*(int *)arg); *(int*)arg = 0; return arg; } int main(int argc,char *argv[]) { pthread_t th; int ret; int arg = 10; int *thread_ret = NULL; ret = pthread_create(&th,NULL,thread,&arg); if(ret!=0){ printf("create thread error!\n"); return -1; } cloud@zw:~$ gcc thread.c -o thread -lpthread cloud@zw:~$ ./thread This is the main process. This is a thread and arg=10. thread_ret=0.
11.线程的合并与分离
线程的合并就是回收线程资源,以免出现资源泄露的问题。
当一个进程或线程调用了针对其它线程的pthread_join()接口,就是线程合并了。这个接口会阻塞调用进程或线程,直到被合并的线程结束为止。
当被合并线程结束,pthread_join()接口就会回收这个线程的资源,并将这个线程的返回值返回给合并者。
与线程合并相对应的另外一种线程资源回收机制是线程分离,调用接口是pthread_detach()。线程分离是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。因为线程分离是启动系统的自动回收机制,那么程序也就无法获得被分离线程的返回值,这就使得pthread_detach()接口只要拥有一个参数就行了,那就是被分离线程句柄。
线程合并和线程分离都是用于回收线程资源的,可以根据不同的业务场景酌情使用。不管有什么理由,你都必须选择其中一种,否则就会引发资源泄漏的问题,这个问题与内存泄漏同样可怕。
12.两个多线程编程的例子和实现
12.1) Linux里编写成多线程序,输出各线程号。
设计思路:今天看了老师的程序,明白了关于线程号输出函数pthread_self().很简单,我主要把多线程程序的主线程和子线程号进行输出。在过程中碰到问题,主线程号输出了,子线程号没有输出。仔细一想,主线程执行完了,子线程还没来得及执行就结束了。怎么办呢?sleep函数,让主线程等待10s,最终达到预期目的。
程序如下:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> void * thread(void *arg) { printf("thread id is %lu.\n",pthread_self()); return NULL; } int main() { pthread_t id; printf("Main thread id is %lu \n",pthread_self()); if(!pthread_create(&id,NULL,(void *)thread,NULL)) { printf("succeed!\n"); sleep(10); return 0; } else {printf("Fail to Create Thread"); return -1; } }
注:编译的时候要链接库:gcc -o thread1 thread1.c -lpthread
12.2) 利用多线程计算pi值。
设计思路:这里利用中值积分定理来计算π值。我的思路就是把线程的执行任务分成四段,,每个线程执行一段,期间遇到的问题就是,线程传递的参数,有些没计算完,来不及传递过去,最终线程结束,我的做法就是让每个线程创建的时候,等待1s.最终达到我的预期结果。
程序如下:
#include <stdio.h> #include<pthread.h> static long num_steps=100000; const int numThreads = 4; double step, pi; pthread_mutex_t mut; double sum = 0.0; void * thread(void *pArg) { double x; int i ; int temp = *((int *)pArg); int start = temp*(num_steps/4); int end = start + num_steps/4; printf("%d %d %d\n",temp,start,end); //测试程序,可以去掉 for (i=start; i<end; i++){ pthread_mutex_lock(&mut); x = (i+0.5)*step; sum = sum + 4.0/(1.0 + x*x); pthread_mutex_unlock(&mut); } return 0; } void main() { int i; pthread_t hThread[numThreads]; pthread_mutex_init(&mut,NULL); step = 1.0/(double) num_steps; for(i=0;i<numThreads;i++) { hThread[i] = pthread_create(&hThread[i] ,NULL,thread, &i); sleep(1); } // for(i=0;i<numThreads;i++){ // pthread_join(hThread[i],NULL); // } pi = step * sum; printf("Pi = %12.9f\n",pi); }
运行结果:
Linux 多线程编程