现代操作系统特定引入“进程”的概念。
1. 进程地址空间是独立的,
创建一个进程的系统开销比较大,要拷贝整个地址空间,
进程切换开销比较大;
2. 进程间的数据是独立的,分开的,如果进程间需要进行
数据交换,则需要用到进程间通信(IPC)
进程通信开销比较大。
于是,就有人提出能不能在同一个进程内实现“任务(程序,代码)”的
并发执行呢?
线程概念
1. 线程是比进程更小的活动单位,它是进程中的一个执行路径(执行分支)
2. 线程同进程内其他线程共享进程地址空间;
=>
线程特点:
1. 创建一个线程比创建一个进程开销小得多
在进程内部创建一个线程,不需要拷贝进程的地址空间,
新线程和其他的线程共享进程的地址空间。
而进程与进程间地址空间是独立的,创建时需要拷贝整个进程地址空间。
2. 实现线程间通信十分方便,因为一个进程创建的多个线程直接共享整个进程的
内存区域。
3. 线程也是一个动态概念。
4. 进程是操作系统资源分配的最小单位,线程是调度的最小单位。
5. 在进程内创建多线程,可以提高系统的并行处理能力,加快进程的处理速度。
6. 每个进程会自动有一个主线程,就是main函数, 这个主线程如果执行完了
那么进程就结束了。
“并发粒度不一样”
POSIX线程实现接口:(POSIX--Portable Operation System Interface)
POSIX实现了线程,我们叫POSIX thread, pthread
linux中pthread的接口函数:
sudo apt-get install manpages-posix-dev 用于安装pthread相关的manual手册(因为man中查不到pthread_mutex_init,pthread_mutex_destory等函数)
1.创建一个线程
2.线程退出
3.线程资源回收
4. 线程间同步机制
(1) 互斥
(2) 条件变量
1.创建一个线程
pthread_create
NAME
pthread_create - create a new thread
线程用来并发执行一个任务,"任务"就是代码,
在C语言,代码(指令)是以函数形式组织。
在pthread中,每个线程都有一个唯一的ID,用类型pthread_t来表示。
而且我们可以通过函数:pthread_t pthread_self(void);来获取自身线程的ID
而且每个线程都有自己的属性,用类型pthread_attr_t来表示
SYNOPSIS
#include <pthread.h>
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg);
thread: 指向pthread_t,用来保存新创建的线程的ID的
attr:指向线程属性结构体pthread_attr_t, 一般为NULL,
表示采用默认的属性。
start_routine: 函数指针。指向的函数的类型为返回一个
void*,带一个void*参数,start_routine指向的函数,就是
线程函数,新创建的线程要执行的任务。
arg: 该参数,将作为线程函数的实际参数传给线程函数
返回值:
成功返回0,
失败返回-1, errno被设置。
Compile and link with -pthread.编译时需要指定,如:gcc main.c -o main -l pthread
2.线程退出
三种方式可以使线程结束:
(1) 线程函数返回,任务完成。
(2)在线程函数中调用pthread_exit()
(3) It is cancelled (别的线程调用pthread_cancell,让线程"取消了")
2.2 pthread_exit
NAME
pthread_exit - terminate calling thread
SYNOPSIS
#include <pthread.h>
pthread_exit用来结束调用线程,
void pthread_exit(void *retval);
retval:返回一个指针。
Compile and link with -pthread.
2.3
NAME
pthread_cancel - send a cancellation request to a thread
SYNOPSIS
#include <pthread.h>
pthread_cancel用来发送一个"取消"请求给thread指定的线程,
但是接收到"取消"请求的线程,不一定会成功取消并退出
这得取决于,接收线程的属性(cancel state)
我们可以调用pthread_setcancelstate来 enable/disable线程这个属性(
可"取消"属性,cancel state)
int pthread_cancel(pthread_t thread)
SYNOPSIS
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
state: 要设置的取消状态
PTHREAD_CANCEL_ENABLE :可被别人取消
PTHREAD_CANCEL_DISABLE: 不可被别人取消
oldstate:保存上次的状态值
返回值:
成功返回0,
失败返回-1, errno被设置
3.线程资源回收
pthread_join等待一个指定的线程退出,该函数会阻塞调用线程,
直到被等待的线程退出,它有两个作用:
(1) 等待线程退出;
(2) 回收被等待线程的资源的
线程退出了,不代表其资源释放完全了,(这个时候需要调用
pthread_join去回收其资源),
线程退出了是否代表其资料完全释放,这个得取决于一个
线程属性: detach state(分离属性)
PTHREAD_CREATE_DETACHED -> detach state : 分离状态,线程结束,资源就自动释放
PTHREAD_CREATE_JOINABLE -> joinable state : joinable state , 这种状态,就必须要另外线程调用
pthread_join来回收其资源。
我们可以调用pthread提供的API来设置线程的detach state:
NAME
pthread_detach - detach a thread
SYNOPSIS
#include <pthread.h>
int pthread_detach(pthread_t thread);
Compile and link with -pthread.
DESCRIPTION
pthread_detach这个函数标记thread代表的那个线程
为分离状态。
当一个处于分离状态的线程退出时,它的资源
会自动回收给系统,没有必要让另外的线程手动
回收它的资源。
试图去分离一个已经处于分离状态的线程将导致不确定
的情况。
NAME
pthread_join - join with a terminated thread
SYNOPSIS
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
thread:要等待哪个线程退出
retval:二级指针,用来保存退出线程的返回值的。
此处为什么一定要用二级指针呢? 因为一个线程的返回值是void*类型
返回值:
成功返回0,
失败返回-1, errno被设置
4. 线程间同步机制
(1) 互斥
多个线程同时访问一个共享资源时,我们必须要
"避免竞争"
a. 信号量机制
SYSTEM V 信号量
POSIX信号量(有名/无名)
b. 线程互斥锁(pthread mutex)
线程互斥锁专门用来实现在线程间互斥用的,
它的原理和功能,作用等都和信号量一样。
pthread_mutex_t这种类型就是用来表示线程互斥锁的。
pthread mutex的操作:
(1) 初始化一个互斥锁; 销毁一个互斥锁
pthread_mutex_init用特定的线程互斥锁属性(pthread_mutexattr_t)
来初始化mutex
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
mutex:指向要初始化的线程互斥锁
attr:指向线程互斥锁的属性,一般为NULL,
表示采用默认属性。
返回值:
成功返回0
失败返回-1, errno被设置
pthread_mutex_destroy用来销毁mutex指向的线程互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
(2) lock; //P操作
pthread_mutex_lock用来对互斥锁进行P操作的
会阻塞到获取该互斥锁,或出错。
返回0表示,获取了互换锁
返回-1 表示出错了,errno被设置
int pthread_mutex_lock(pthread_mutex_t *mutex); //block
pthread_mutex_trylock用来对互斥锁进行P操作,只不过
它是非阻塞的版本,能获取则获取,不能获取就返回。
返回0表示,获取了互斥锁
返回-1表示没获取到,(可能是出错了,errno)
int pthread_mutex_trylock(pthread_mutex_t *mutex);
(3) unlock; //V操作
线程互斥锁的V操作
int pthread_mutex_unlock(pthread_mutex_t *mutex);
(2) 条件变量
(2.1)初始化/销毁一个条件变量
pthread_cond_init用来初始化cond指向条件变量
int pthread_cond_init(pthread_cond_t * cond,
const pthread_condattr_t * attr);
cond: 指向要初始化的条件变量
attr:指向条件变量的属性,一般为NULL,表示采用默认属性
返回值:
成功返回0
失败返回-1, errno被设置
pthread_cond_destroy用来销毁cond指向的条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
(2.2)等待一个条件变量
条件变量本身就是一个共享对象(多个线程都可以访问它),
它本身也需要保护。MC(Mutex Condition)
//死等,直到被唤醒(返回0, 表示条件产生,
返回-1,表示失败了)
int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
//有限等待。
//返回0表示条件产生,
//返回-1 表示失败,errno
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
struct timespec {
time_t tv_sec; // Seconds
long tv_nsec; // Nanoseconds [0 .. 999999999]
};
例子:
struct timespce ts;
clock_getime(CLOCK_REALTIME, &ts);
//ts.tv_sec +=5;
//ts.tv_nsec
ts.tv_nsec += 1000000;
if (ts.tv_nsec >=1000000000L)
{
ts.tv_sec++;
ts.tv_nsec -= 1000000000L;
}
NOTE:调用上述函数时,需要把锁住的互斥锁传入,上述等待函数
在让线程休眠(让出CPU)前,会释放该互斥锁,然后阻塞,直到被
唤醒,再次锁住该互斥锁,并从等待函数中返回。
(2.3)唤醒一个条件变量
int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒条件变量上等待的所有线程
int pthread_cond_signal(pthread_cond_t *cond); //唤醒条件变量上等待的一个线程