一 线程的基本概念
线程是在共享内存空间中并发的多道执行路径,它们共享一个进程的资源,如文件描述符和信号处理。操作系统在两个进程间进行切换时,要对前一个进程进行保护现场操作,对后一个进程进行还原现场操作。反复进行上下文切换会带来极大的开销。线程则无需进行上下文切换,因为多个线程共享同一个进程的上下文。多个线程也共享同一个进程的CPU时钟周期,进程的状态并未因线程切换而改变。
二线程的实现
1.用户态线程
用户态线程由进程负责调度管理、高度抽象化的、与硬件平台无关的线程机制。其最为显著的标志是,进程在创建多个线程时不需要LInux内核支持,也不直接对CPU标志寄存器进行操作。用户态的优势在于下面两方面:
- 减少多线程的系统开销:同一个进程可创建的线程数没有限制
- 用户态实现方式灵活:可根据实际需要设计相应的用户态线程机制,对于实时性要求高的程序格外重要
但是,如果某进程的其中一个线程被阻塞,则进程会进入睡眠态,其他线程同时也被阻塞。造成该现象的原因是Linux内核使用异步输入输出机制。
用户态的缺陷是,无法发挥多路处理器和多核处理器的优势。
2.内核态线程
由Linux操作系统根据CPU硬件的特点,以硬件底层模式实现的线程机制。内核态将所有线程按照同一调度算法调度,更有利于发挥多路处理器和多核处理器所支持的并发处理特性。
内核态线程可自有访问内存空间,并且在某一线程阻塞时,其他线程还能正常运行。
相对于用户态线程,内核态线程的系统开销稍大,并且必须通过系统调用实现,对硬件和Linux内核版本的依赖性较高,不利于程序移植。
三 POSIX线程库
libpthread线程库是目前Linux系统上最常用的线程库。它支持NPTL线程模式,以用户态线程实现。该函数库的接口被定义在pthread.h
头文件中。
1.创建线程
int pthread_create(pthread_t* thread,pthread_attr_t* attr,void* (*start_routine)(void*),void* arg);
参数说明:
- *thread:一个pthread_t结构的指针,该结构用于保存线程信息,函数创建成功时,将线程的标识符等信息写入*thread指针指向的内存空间
- *attr:一个pthread_attr_t结构指针,结构中的元素分别对应着新线程的运行属性 ,属性的定义如下表,在没有特殊的属性要求时,可将NULL作为参数传递。
- (*start_routine)(void*):表示需要传递的是
start_routine()
函数的地址,该函数以一个指向void的指针为参数,返回的也是指向void的指针,这种方式使任何类型的数据都能作为参数,也能返回给任何类型的数据结构。start_routine()
函数的作用是启动线程 - arg:
start_routine()
函数的参数
创建线程成功时返回0,失败时返回一个错误代码。需要注意的是,与Pthread相关的函数在错误时都不会返回-1。
2.结束线程
线程结束时可调用函数pthread_exit()
,该函数的原理与结束进程的exit系统调用相似。它的作用是结束调用了这个函数的线程,返回一个指向某个变量的指针。这个指针绝对不能是局部变量的指针,因为局部变量会在线程出现严重问题时消失。函数的一般形式为:
void pthread_exit(void* retval);
如果进程需要在线程结束后与其归并到一起,可使用函数pthread_join()实现,该函数的原理与进程同步的wait系统调用相似。pthread_join()
函数的一般形式如下:
int pthread_join(pthread_t th,void** thread_return);
参数说明:
- th:指定要等待的线程,即是
pthread_create()
定义的标识符 - thread_return:一个指针,指向另一个指针,后者指向线程的返回值
当等待的线程成功结束时,返回0,否则返回一个错误代码。
下例将演示从创建线程和结束线程的操作方法,新线程与原有线程共享变量:
#include<iostream>
#include<unistd.h>
#include<cstdlib>
#include<pthread.h>
#include<string>
using namespace std;
void* thread_function(void* arg);
string message="THREAD_TEST";
string* m=&message;
int main()
{
int res;
pthread_t a_thread;
void* thread_result;
res=pthread_create(&a_thread,NULL,thread_function,(void*)m);
if(res!=0)
{
cerr<<"线程创建失败"<<endl;
exit(EXIT_FAILURE);
}
cout<<"等待线程结束..."<<endl;
res=pthread_join(a_thread,&thread_result);
if(res!=0)
{
cerr<<"等待线程结束失败"<<endl;
exit(EXIT_FAILURE);
}
cout<<"线程已结束,返回值:"<<(char*)thread_result<<endl;
cout<<"Message的值为:"<<message<<endl;
exit(EXIT_SUCCESS);
return 0;
}
void* thread_function(void* arg)
{
cout<<"线程在运行,参数为:"<<*((string*)arg)<<endl;
sleep(3);
*((string*)arg)="线程修改";
char* retval= "线程执行完毕";
pthread_exit(static_cast<void*>(retval));
}
运行结果如下:
说明:如果线程创建成功,那么就会有两个线程在同时执行。原有线程将继续执行pthread_create()后的代码,新线程执行线程函数体内的代码。在验证新线程启动成功后,原有线程调用pthread_join()
函数等待新线程结束。
新线程执行完毕后,pthread_join()函数将控制权还给主函数,主函数输出结束信息并退出。
四 同步
1.用信号量进行同步
与信号量相关的函数名字都以sem_
作为前缀,线程里使用的基本信号量函数有4个,被包含在头文件semaphore.h
中。
(1)初始化信号量:
int sem_init(sem_t* sem,int pshared,unsigned int value);
参数说明:
- sem:sem_t结构指针,该结构用于保存信号量的信息
- phshared:控制信号量的类型。如果参数为0,表明信号量是局部的,否则其他程序就能共享这个信号量
- value:信号量初值
(2) 修改信号量
int sem_wait(sem_t* sem);
int sem_post(sem_t* sem);
这两个函数都是原子操作。sem_wait()函数的作用是使信号量减1,如果信号量的值为0(表明无可用资源,有其他线程正在访问),则sem_wait()函数会保留控制权,等待信号量变为非0后进行操作,再将控制权还给调用者。
sem_post()的作用是使信号量加1。
(3)信号量清理
int sem_destory(sem_t* sem);
以信号量指针作为参数,归还信号量所占用的资源,如果还有其他线程使用已清理的信号量,那么线程会收到一个错误。
下例将演示信号量操作方法:
#include<iostream>
#include<unistd.h>
#include<cstdlib>
#include<string>
#include<pthread.h>
#include<semaphore.h>
using namespace std;
void* thread_function(void* arg);
sem_t bin_sem;
const int WORK_SIZE=1024;
string work_area;
int main()
{
int res;
pthread_t a_thread;
void* thread_result;
res=sem_init(&bin_sem,0,0);
if(res!=0)
{
cerr<<"初始化信号量失败"<<endl;
exit(EXIT_FAILURE);
}
res=pthread_create(&a_thread,NULL,thread_function,NULL);
if(res!=0)
{
cerr<<"线程创建失败"<<endl;
exit(EXIT_FAILURE);
}
while(work_area!="end")
{
cout<<"请输入要传送的信息,输入'end'退出"<<endl;
getline(cin,work_area);
cout<<"输入内容为:"<<work_area<<"\n";
sem_post(&bin_sem);
sleep(1);
}
cout<<"\n等待线程结束"<<endl;
res=pthread_join(a_thread,&thread_result); //等待线程结束
if(res!=0)
{
cerr<<"线程结束失败"<<endl;
exit(EXIT_FAILURE);
}
cout<<"线程结束"<<endl;
sem_destroy(&bin_sem);
exit(EXIT_SUCCESS);
}
void* thread_function(void* arg)
{
sem_wait(&bin_sem);
while(work_area!="end")
{
cout<<"收到内容:";
cout<<work_area<<endl;
sem_wait(&bin_sem);
}
pthread_exit(NULL);
}
输出结果如下:
线程被创建,两个线程属于同时运行,如果主线程未休眠,则会照成两个线程共用终端,故将主线程设置休眠。