一、线程
1、进程和线程的区别和联系?
本质区别:进程是操作系统资源分配的基本单位;而线程是处理器任务调度和执行的基本单位。
包含关系:一个进程至少有一个线程,线程是进程的一部分,所以线程也被称为轻量级进程。
资源开销:进程和线程的切换都是由操作系统控制(用户态→内核态→用户态)。进程的上下文切换不仅包含了栈、堆、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的资源。当两个线程是属于同一个进程,因为大部分内存是共享的,所以在切换时,只需要切换线程的栈、寄存器等不共享的数据。
通信方面:进程间通信需要借助操作系统;线程间可以直接读写进程数据段(如全局变量)来进行通信。
影响关系:一个进程崩溃后,在保护模式下其他进程不会被影响,但是一个线程崩溃可能导致整个进程被操作系统杀掉,所以多进程要比多线程健壮。
2、线程之间共享和非共享资源
二、线程操作
1.创建线程
int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void *(*start_routine)(void*), void* arg)
功能:创建一个子线程
参数:
thread:传出参数,线程创建成功后,子线程的线程ID被写到该变量中;
attr:设置线程的属性,一般使用默认值,NULL;
start_routine:函数指针,这个函数是子线程需要处理的逻辑代码;
arg:传参,给第三个参数使用。
返回值:
成功返回0,失败返回错误号。
类内使用pthread_create函数时,函数指针必须指向一个静态函数。C++类中的成员函数默认在参数中包含有一个this指针,这样成员函数才知道应该对哪个实例作用。而线程函数必须接受一个void指针作为参数,而C++无法发生void*和this的转换,所以产生了矛盾。为了解决矛盾,我们可以使用static函数,它独立于实例,参数中将不会有this指针,所以可以用于打开线程。
如果需要调用调用类内的成员变量、成员函数,可以通过类的对象作为参数传递给该静态函数,然后在静态函数中引用这个对象,并调用其动态方法。
2.终止线程
void pthread_exit(void* retval)
功能:终止一个线程,在哪个线程中调用,就表示终止哪个线程;
参数:
retval:需要传递一个指针作为返回值,可以在pthread_join()中获取;
3.获取线程ID
pthread_t pthread_self(void)
功能:获取当前线程的线程ID;
返回值:返回线程ID;
4.主线程回收子线程资源
int pthread_join(pthread_t thread, void** retval)
功能:一般在主线程中使用,和一个已经终止的线程进行连接,回收子线程的资源。调用时阻塞函数,直到回收资源,调用一次只能回收一个子线程。
参数:
thread:需要回收的子线程的ID;
retval:接收子线程退出时的返回值;
返回值:成功返回0,失败返回错误号。
5.线程分离,子线程释放资源
int pthread_cancel(pthread_t thread)
功能:分离一个线程,被分离线程在终止时会自动释放资源返回给系统。主线程不能多次分离,不能去连接已分离的线程。
参数:
thread:需要分离的线程ID
返回值:成功返回0,失败返回错误号。
6.线程取消
int pthread_cancel(pthread_t thread)
功能:让线程终止,取消某个线程可以终止某个线程运行,但是不是立马终止,而是当子线程执行到一个取消点,线程才会终止(取消点:系统规定的系统调用,粗略理解为从用户区到内核区的系统切换的点)。
参数:
thread:需要终止的线程ID
返回值:成功返回0,失败返回错误号。
回收子线程资源的方法有两种:
第一种:主线程主动回收。调用pthread_join()函数等待子线程退出,回收它的资源,默认采用阻塞的方式,直到成功回收后才会返回,类似于进程中wait/waitpid回收僵尸进程pthread_tryjoin_np()为非阻塞方式。
第二种:设置成分离子线程。在主线程中,在创建子线程前设置子线程为分离属性,pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED),或者主线程调用detach()函数分离子线程,子线程退出时,系统将自动回收资源。分离后的子线程不可join()。
void* callback(void* arg) {
int val = *(int*)arg
printf("tid:%ld, arg val:%d", pthread_self(), val);
pthread_exit((int*)&val);
}
int main() {
pthread_t tid;
int num = 10;
pthread_create(&tid, NULL, callback, (void*)&num);
if(ret != 0) {
char* errrstr = strerror(ret);
printf(“error1:%s\n”, errstr);
}
int *thread_retval;
ret = pthread_join(tid, void(**)&retval); //回收子线程的资源
printf("exit data:%d\n", *thread_retval);
printf("回收子线程资源成功!\n");
pthread_exit(NULL);
}
7.线程属性设置
线程分为可结合和可分离两种,线程默认是PTHREAD_CREATE_JOINABLE(可结合的)。可结合的线程在线程退出后不会立即释放资源,必须要调用pthread_join来显式的结束线程。可以设置子线程为PTHREAD_CREATE_DETACHED(可分离的),分离的线程在线程退出时系统会自动回收资源。
int pthread_attr_init(pthread_attr_t* attr); //功能:初始化线程属性变量
int pthread_attr_destory(pthread_attr_t* attr); //功能:释放线程属性的资源
int pthread_attr_getdetachstate(const pthread_attr_t* attr, int* detachstate); //功能:获取线程分离的状态属性
int pthread_attr_setdetachstate(const pthread_attr_t* attr, int* detachstate); //功能:设置线程分离的状态属性
void* callback(void* arg) {
printf("child thread id:%ld\n", pthread_self());
pthread_exit(NULL);
}
int main() {
pthread_attr_t attr; //创建线程属性变量
pthread_attr_init(&attr); //初始化属性变量
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); //设置线程分离
pthread_t tid;
int ret = pthread_create(&tid, &attr, callback, NULL); //创建子线程
if(ret != 0) {
char *errstr = strerror(ret);
printf("error1:%s\n", errstr); //输出错误号
}
printf("子线程:%ld, 主线程:%ld\n", tid, pthread_self());
pthread_attr_destroy(&attr);
pthread_exit(NULL);
}
三、线程同步
1.并发和并行的区别
并行:在同一时刻,有多条指令在多个处理器(CPU)上同时执行,所以无论从微观还是从宏观来看,二者都是一起执行的。
并发:在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
2.线程同步的四种方法?
临界区、互斥量、信号量、事件。
临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
互斥量:多个线程在同一时刻只有一个进程能进入临界区。
信号量:允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。
事件:事件对象通过通知操作的方式来保持线程的同步,并且可以实现不同进程中的线程同步操作。
3.死锁
死锁是指两个(多个)线程相互等待对方数据的过程。
死锁产生的四个必要条件:资源不共享、请求和保持、不可剥夺(资源不能被其他进程强行夺走,只能自己释放)、循环等待(进程资源循环等待链,链中每个进程已获得的资源同时被链中下一个进程所请求)
4.互斥锁、自旋锁、读写锁、乐观锁和悲观锁
互斥锁加锁失败后,线程会释放CPU,给其他线程,自身处于阻塞状态。
自旋锁加锁失败后,线程会忙等待(while),直到它拿到锁。
读写锁:读者可以同时读,写者之间、写者与读者之间互斥,写者优先读者。
乐观锁:先修改完共享资源,再验证这段时间内有没有发生冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作。
悲观锁:多线程同时修改共享资源的概率比较高,于是很容易出现冲突,所以访问共享资源前,先要上锁。
5.互斥锁pthread_mutex_t的实现
int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutex attr_t* restrict attr)
功能:初始化互斥量
参数:
mutex:需要初始化的互斥量变量
attr:互斥量相关的属性,一般设置为NULL。其中restrict是C语言的修饰符,被修饰的指针不能由另外的一个指针进行操作。
返回值:成功返回0,其他返回值表示错误。
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); //解锁。
//写一个互斥类
class locker {
public:
locker() {
if(pthread_mutxt_init(&m_mutex, NULL) != 0) {
throw exception();
}
}
~locker() {
pthread_mutex_destroy(&m_mutex);
}
bool lock() {
return pthread_mutex_lock(&m_mutex) == 0;
}
bool unlock() {
return pthread_mutex_unlock(&m_mutex) == 0;
}
pthread_mutex_t* get() {
return &m_mutex;
}
private:
pthread_mutex_t m_mutex;
}