线程
前言
多线程编程已经是大多数应用软件的必备技能,下面将学习一些线程编程的基础知识
提示:以下是本篇文章正文内容
一、线程是什么?
线程是参与系统调度的最小单位。 它被包含在进程之中, 是进程中的实际运行单位。一个线程指的是进
程中一个单一顺序的控制流(或者说是执行路线、执行流), 一个进程中可以创建多个线程, 多个线程实现并发运行, 每个线程执行不同的任务。 譬如某应用程序设计了两个需要并发运行的任务 task1 和 task2,可将两个不同的任务分别放置在两个线程中。
二、线程与进程的区别
多进程编程的劣势:
- 进程间切换开销大。多个进程同时运行(指宏观上同时运行,无特别说明,均指宏观上),微观上
依然是轮流切换运行,进程间切换开销远大于同一进程的多个线程间切换的开销,通常对于一些中
小型应用程序来说不划算。 - 进程间通信较为麻烦。 每个进程都在各自的地址空间中、相互独立、隔离,处在于不同的地址空间
中,因此相互通信较为麻烦
多线程编程的优势:
- 同一进程的多个线程间切换开销比较小。
- 同一进程的多个线程间通信容易。 它们共享了进程的地址空间,所以它们都是在同一个地址空间
中,通信容易。 - 线程创建的速度远大于进程创建的速度。
三、线程怎么使用
1.线程ID
如每个进程都有一个进程ID一样,每个线程也有其对应的线程ID,线程ID使用pthread_t数据类型表示
#include<pthread.h>
//获取自己的线程ID
pthread_t pthread_self(void);
//检查两个线程是否相等
int pthread_equal(pthread_t t1, pthread_t t2);
return:如果两个线程ID相等,则返回一个非0值,否则返回0.
2.创建线程
代码如下(示例):
#inlcude<pthread.h>
int pthread_create(pthread_t thread, const pthread_attr_t *attr, void*(*start_routine)(void*), void *arg);
args:
thread:pthread_t类型指针,线程的唯一标识,线程ID会保存在此指针指向的内存中。
attr:pthread_attr_t类型指针,设置了线程的各种属性,如果设置为NULL,则所有属性设置为默认值
start_routine:是一个函数指针,新创建的线程从此函数运行,如果此函数的参数需要一个以上,需要把参数放到结构体中,从arg传入
arg:传递给start_routine的参数,设置为NULL表示不需要参数
return:成功返回0,失败返回一个错误号。
3.终止线程
#include <pthread.h>
void pthread_exit(void *retval);
4.回收线程
在线程当中,通过调用pthread_join()来阻塞等待线程的终止,并获取线程的退出码,回收线程资源
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
args:
thread:指定需要等待的线程ID
retval:将线程的退出状态复制到*retval指向的内存中,如果目标被pthread_cancel()取消,则将PTHREAD_CANCELED放在*retval中,如果不获取退出状态,则设置为NULL
return:成功返回0,失败返回错误码
5.取消线程
向一个线程发送请求,要求它立刻退出
#include <pthread.h>
int pthread_cancel(pthread_t thread);
return:成功返回0,失败返回错误码
6.取消状态以及类型
线程接收到其他线程发送的请求,可以选择不被取消,或者控制如何被取消
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);
args:
state: PTHREAD_CANCEL_ENABLE:线程可以取消,这是新创建线程取消状态的默认值
PTHREAD_CANCEL_DISABLE:线程不可被取消,如果线程接收到取消请求,则会将线程挂起,直到线程的取消状态变为PTHREAD_CANCEL_ENABLE
return:成功返回0,失败返回非0的错误码
int pthread_setcanceltype(int type, int *oldtype);
args:
type: PTHREAD_CANCEL_DEFERRED:取消请求到来时,线程还是会继续运行,取消请求被挂起,直到线程到达某个取消点
PTHREAD_CANCEL_ASYNCHRONOUS:可能会在任何时间点取消线程
return:成功返回0,失败返回非0的错误码
7.线程可取消性检测
#include<pthread.h>
//产生一个取消点
void pthread_testcancel(void);
8.分离线程
有时候我们不想关心线程的返回状态,希望它终止时可以自动回收线程资源并将其移除。
#include <pthread.h>
int pthread_detach(pthread_t thread);
9.线程清理函数
每个线程都有一个清理函数栈,栈是一种先进后出的数据结构,也就是说它们的执行顺序与注册顺序相反,执行完清理函数时,线程终止。
#inlcude <pthread.h>
#向清理函数栈中添加一个清理函数
void pthread_cleanup_push(void(*routine)(void*), void *arg);
#将清理函数栈中最顶层的函数移除
void pthread_cleanup_pop(int excute);
10.线程属性
调用pthread_create()创建线程,可以对新线程设置各种属性。可以使用pthread_attr_t数据类型定义线程的所有属性。
#include <pthread.h>
#初始化pthread_attr_t对象
int pthread_attr_init(pthread_attr_t *attr);
#销毁pthread_attr_t对象
int pthread_attr_destroy(pthread_attr_t *attr);
#对栈起始地址和栈大小进行设置
int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);
#获取栈起始地址和栈大小
int pthread_attr_getstack(const pthread_attr_t *attr, coid **stackaddr, size_t *stacksize);
#单独获取或设置栈大小、栈起始地址
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
int pthread_attr_getstackaddr(const pthread_attr_t *attr, void **stackaddr);
#设置detachstate线程属性
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
#获取detachstate线程属性
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int detachstate);
args:
detachstate:PTHREAD_CREATE_DETACHED 分离属性
PTHREAD_CREATE_JOINABLE 默认属性
四、线程同步
主要是为了数据一致性的问题
1.互斥锁
同一时间只有一个线程能够获得互斥锁,其他线程陷入阻塞等待
#include <pthread.h>
#初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
#加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
#解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
#加锁线程不被阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex);
#销毁锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
#互斥锁属性对象的创建和销毁
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
#互斥锁类型属性的查询与修改
int pthread_mutexattr_gettype(const pthread_mutexattr_t *attr, int type);
int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);
2.条件变量
等待某个条件满足而被阻塞,搭配互斥锁使用
#include <pthread.h>
#条件变量初始化
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
#通知和等待条件变量
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
#将线程设置为等待状态
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
3.自旋锁
适用于等待时间短的场景
#include <pthread.h>
#自旋锁初始化
int pthread_spin_destroy(pthread_spinlock_t *lock);
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);
args:
pshared:进程共享属性
PTHREAD_PROCESS_SHARED: 共享自旋锁。该自旋锁可以在多个进程中的线程之间共享
PTHREAD_PROCESS_PRIVATE: 私有自旋锁。只有本进程内的线程才能够使用该自旋锁
#加锁与解锁
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
4.读写锁
有三种模式:
读加锁状态
写加锁状态
不加锁状态
#include <pthread.h>
#读写锁的初始化
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
#加锁与解锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
#非阻塞加锁
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
#读写锁属性
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
#获取与设置属性
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared);
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);
args:
pshared:PTHREAD_PROCESS_SHARED: 共享读写锁。该读写锁可以在多个进程中的线程之间共享
PTHREAD_PROCESS_PRIVATE: 私有读写锁。只有本进程内的线程才能够使用该读写锁