目录
2.1 线程创建函数pthread_create()原型和头文件:
2.2 线程创建函数pthread_create()用法案例:
2.3 线程退出函数pthread_exit()原型和头文件:
2.4 线程等待函数pthread_join()原型和头文件:
2.5 线程脱离函数pthread_detach()原型和头文件:
2.6 获取线程ID函数pthread_self()原型和头文件:
2.7 线程ID比较函数pthread_equal()原型和头文件:
3.1 创建互斥锁函数pthread_mutex_init()原型和头文件:
3.2 销毁互斥锁函数pthread_mutex_destroy()原型和头文件:
3.3 加锁pthread_mutex_lock()和解锁pthread_mutex_unlock()函数原型和头文件:
4.1 创建条件变量函数pthread_cond_init()原型和头文件:
4.2 销毁条件变量函数pthread_cond_destroy()原型和头文件:
4.3 条件变量等待函数pthread_cond_wait()原型和头文件(无条件等待):
4.4 条件变量等待函数pthread_cond_timedwait()原型和头文件(计时等待):
4.5 条件变量触发函数pthread_cond_signal和pthread_cond_broadcast原型和头文件:
一、线程基本概念
1.1 进程与线程的区别:
典型的UNIX/linux进程可以看成是只有一个控制线程,一个进程在同一时刻只做一件事情,有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。
-
进程 进程是程序执行时的一个实例,即它是程序已经执行到何种程度的数据结构的汇集。从内核的观点看,进程的目的就是担当分配系统资源(CPU时间、内存等)的基本单位。
-
线程 线程是进程的一个执行流,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个进程由几个线程组成(拥有很多相对独立的执行流的用户程序共享应用程序的大部分数据结构),线程与同属一个进程的其他的线程共享进程所拥有的全部资源。
-
进程线程的区别 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,最好用线程。
1.2 使用线程的好处:
-
使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
-
使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
-
提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
-
使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
-
改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
二、线程相关函数
-
多线程开发在linux平台上已经有成熟的 pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。
-
线程操作有:创建,退出,等待。
-
互斥锁操作有:创建,销毁,加锁和解锁。
-
条件操作有 :创建,销毁,触发,广播和等待。
其他的一些线程扩展概念,如信号灯等,都可以通过上面的三个基本元素的基本操作封装出来。详细请见下表:
2.1 线程创建函数pthread_create()原型和头文件:
/*
Linux下 man pthread_create查看手册
*/
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr,
void *(*start_rtn)(void *), void *restrict arg);
int 函数返回值,成功pthread_create()返回0;当出现错误时,它返回一个错误编号,并且*thread的内容是未定义的
tidp 指向线程标识符的指针
attr 用来设置线程属性,如果没有则填NULL。
start_rtn 表示线程运行函数的地址
arg 线程运行函数的参数,如果没有参数则填NULL
-
函数详解:
当 pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程。 新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。
2.2 线程创建函数pthread_create()用法案例:
#include <stdio.h>
#include <pthread.h>
void* func1(void *arg)
{
printf("th1:%ld是新线程ID\n",(unsigned long)pthread_self()); //获取新线程的ID
printf("th1:data = %d\n",*((int *)arg));
}
int main()
{
int ret;
pthread_t th1; //线程标识符
int data = 100;
//int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
ret = pthread_create(&th1, NULL, func1, &data); //创建了一个th1线程
if(ret == 0){
printf("main:创建th1线程成功\n");
}
printf("main:线程ID = %ld\n",(unsigned long)pthread_self()); //获取主线程的ID
//int pthread_join(pthread_t thread, void **retval);
pthread_join(th1,NULL); //等待th1线程终止,但是不获取th1线程终止状态
return 0;
}
2.3 线程退出函数pthread_exit()原型和头文件:
单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:
-
线程只是从启动例程中返回,返回值是线程的退出码。
-
线程可以被同一进程中的其他线程取消。
-
线程调用pthread_exit。
/*
Linux下 man pthread_exit查看手册
*/
#include <pthread.h>
int pthread_exit(void *retval);
int 函数返回值,成功pthread_exit()返回0;当出现错误时,它返回一个错误编号
void *retval 表示线程退出状态,是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函 数访问到这个指针。
-
函数详解:
在线程中禁止调用exit函数,否则会导致整个进程退出,取而代之的是调用pthread_exit函数,这个函数是使一个线程退出,如果主线程调用pthread_exit函数也不会使整个进程退出,不影响其他线程的执行。 需要注意一点,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了,栈空间就会被回收。 如果在主线程中使用pthread_exit函数,会导致子线程还在,内存无法被回收,成为僵尸进程。引入pthread_join函数。
2.4 线程等待函数pthread_join()原型和头文件:
/*
Linux下 man pthread_join查看手册
*/
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
int 函数返回值,如果执行成功pthread_join返回0,当出现错误时,它返回一个错误编号
pthread_t thread 线程ID
void **retval 存储线程结束状态,整个指针和pthread_exit的参数是同一块内存地址
-
函数详解:
调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。
可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL。
如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。
2.5 线程脱离函数pthread_detach()原型和头文件:
/*
Linux下 man pthread_detach查看手册
*/
#include <pthread.h>
int pthread_detach(pthread_t thread);
int 函数返回值,如果成功pthread_detach返回0,当出现错误时,它返回一个错误编号
pthread_t thread 需要脱离的线程(标识符)
-
函数详解:
一个线程或者是可汇合(joinable,默认值)