一、线程的概念
windows系统和linux系统都是多任务的操作系统,任务(task)就是系统调度器的调度实体,而任务是存在在进程中的,只不过有些进程中只有一个任务,有些进程中则可能会存在多个任务。
操作系统是以进程为单位分配资源的,如果一个进程中只有一个任务,则该任务拥有进程的全部资源,如果进程中有多个任务,则这些任务共享进程的资源。为了可以让进程中的多个任务得到运行,可以把任务放在线程(thread)中,然后由调度器进行调度,而线程是系统调度资源的最小单位。
线程和进程之间的关系,类似于工厂和工人之间的关系,进程好比是工厂,线程就如同工厂中的工人。一个工厂可以容纳多个工人,工厂负责为所有工人提供必要的资源(电力、产品原料、食堂、厕所等),所有工人共享这些资源,每个工人负责完成一项具体的任务,他们相互配合,共同保证整个工厂的平稳运行。
每个进程执行前,操作系统都会为其分配所需的资源,包括要执行的程序代码、数据、内存空间、文件资源等。一个进程至少包含 1个主线程,可以包含n个子线程,所有线程共享进程的资源,各个线程也可以拥有属于自己的私有资源。
注意:由于系统分配资源是以进程为单位的,而进程至少会执行一个任务,如果进程没有创建其他线程,则程序中的main函数就是一条线程,也被称为主线程。
二、线程的创建
注意:线程使用的是进程的资源,所以进程退出之后线程也会退出,而进程结束的条件之一就是程序的main函数退出,所以也就是当程序中的main函数调用return语句则进程会被终止,所以进程的其他的线程也会一起退出。
三、线程的结束
Linux系统提供多种方式让线程终止,其中比较常用的方式是调用pthread_exit()函数,利用该接口可以实现让线程终止的目的。
注意:主线程如果调用该函数也可以被终止,但是不会影响进程中其他线程的运行,但是如果主线程中调用exit()或者return语句,就会导致进程结束,则所有的线程就会终止。
四、线程的接合
与进程类似,线程退出之后不会立即释放其所占有的系统资源,而会成为一个僵尸线程。为了防止线程退出之后还占用进程资源,所以Linux系统中提供了一个名称叫做pthread_join()的函数接口,其他线程可使用 pthread_join() 来释放僵尸线程的资源,并可获得其退出时返回的退出值,该函数被称为线程的接合函数。
五、线程的属性
一般情况下线程的属性都是在线程创建的过程中进行设置,Linux系统提供了专门的结构来存储线程的属性,只不过必须先调用pthread_attr_init()函数对线程属性进行初始化,函数使用规则如下图:
在众多的线程属性中,有一个可以让线程在终止之后自动释放资源的属性,那就是线程的分离属性。
当线程终止时如果不需要把退出值传递给其他线程,则可以把线程设置为分离属性,设置为分离属性下的线程在退出之后,会自动释放其占用的系统资源。
Linux系统提供了一个名称叫做pthread_attr_setdetachstate()的函数,用户利用该函数可以在设置线程的属性为分离属性,注意:设置为分离属性的线程是无法被接合的。
可以在线程的任务函数中强制把自身的属性设置为分离属性,Linux系统中提供了一个名称叫做pthread_detach()的函数,可以强制分离。
Linux系统提供了一个名称叫做pthread_self()的函数接口,线程的TID仅限于进程内部的线程间有效。当需要要对某条线程执行诸如发送信号、取消、阻塞接合等操作时,都需要用到线程的ID。
通过之前的学习,可以知道一个进程中可以存在多条线程,每条线程可以执行一个任务,而线程是并发执行的,这样可以提高程序的运行效率,但是系统分配资源是以进程为单位的,而进程中的所有线程会共享这些资源。
六、互斥锁
可以利用线程间的同步和互斥机制,来达到线程对资源的有序访问,同步指的是控制两个进度使之有先有后,次序可控,而互斥指的是控制两个进度使之互相排斥,不同时运行。
为了实现线程对资源的合理访问,并且在访问的过程中不会出现线程竞争的情况,Linux系统就提供了一种实现互斥机制的方案,就是互斥锁。
互斥锁的本质是一个二值信号量,任何一条线程要开始运行互斥区间的代码,都必须先获取互斥锁,如果其中一条线程抢先获取了互斥锁之后,其余线程就无法再次获取了,相当于给相关的资源加了把锁,直到使用者主动解锁,其余线程方可有机会获取这把锁。
(1)初始化互斥锁
可以看到,Linux系统中提供了一个叫做pthread_mutex_init函数接口,用户利用该函数就可以对互斥量进行初始化,注意:没有经过初始化的互斥量是不能直接使用的!
互斥量的本质就是一个变量,变量的类型是pthread_mutex_t,要注意对该变量的赋值,需要赋值为PTHREAD_MUTEX_INITIALIZER,再去调用pthread_mutex_init()即可。
当然,除了调用函数接口实现互斥锁的初始化之外,用户也可以直接定义一个全局变量,这样其他线程都可以使用互斥锁,但是记得对变量进行初始化为PTHREAD_MUTEX_INITIALIZER。
(2)上锁解锁操作
注意:不要在同一个线程中对已经上锁的互斥量重新上锁,这样会导致死锁出现,应该避免该情况出现,上锁之后应该在使用完资源后及时进行解锁。
提示:如果多条线程访问临界资源时,可以使用互斥锁,注意线程应该在访问资源前上锁,访问完成后进行解锁。
七、练习
1.
编写一个程序,主线程中创建一个子线程,容纳后让主线程先退出并返回一个值,子线程接合主线程后输出主线程的退出值,然后子线程退出。提示:获取线程自身的ID可以调用pthread_self()函数。
/*
* Copyright (c)
*
* date: 2025-8-11
*
* author: Charles
*
* function name : pthread_self_test.c
*
* function: 编写一个程序,主线程中创建一个子线程,容纳后让主线程先退出并返回一个值,
* 子线程接合主线程后输出主线程的退出值,然后子线程退出。提示:获取线程自身的ID可以调用pthread_self()函数。
*
*/
#include <stdio.h>
#include <pthread.h>
pthread_t pthread_main;
void* task(void*arg)
{
int* state;
pthread_join(pthread_main, (void**)&state);
printf("Main thread exit state: %d\n", *state);
pthread_exit(NULL); // 子线程退出
}
int main()
{
pthread_t pthread_child;
pthread_create(&pthread_child, NULL, task, NULL);
int state = 10;
pthread_main = pthread_self();
pthread_exit(&state);
return 0;
}
2.
设计一个程序,要求在程序中创建两条子线程,主线程创建一个线程的属性对象,并对线程的属性对象进行初始化之后,要求创建的子线程的属性设置为分离属性,子线程A输出”hello”,子线程B输出”world”,主线程创建子线程之后就终止。
/*
* Copyright (c)
*
* date: 2025-8-2
*
* author: Charles
*
* function name : pthread_detach_test.c
*
* function: 设计一个程序,要求在程序中创建两条子线程,主线程创建一个线程的属性对象,
* 并对线程的属性对象进行初始化之后,要求创建的子线程的属性设置为分离属性,
* 子线程A输出”hello”,子线程B输出”world”,主线程创建子线程之后就终止。
*
*/
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
void* taskA(void* arg){
pthread_detach(pthread_self());
while(1){
printf("hello \n");
pthread_exit(NULL);
sleep(1);
}
}
void* taskB(void* arg){
pthread_detach(pthread_self());
while(1){
printf("world \n");
pthread_exit(NULL);
sleep(1);
}
}
int main(int argc, char const *argv[])
{
pthread_t thread1;
pthread_t thread2;
// pthread_attr_t thread_addr;
// pthread_attr_init(&thread_addr);
// pthread_attr_setdetachstate(&thread_addr, PTHREAD_CREATE_DETACHED);
pthread_create(&thread1, NULL, taskA, NULL);
pthread_create(&thread2, NULL, taskB, NULL);
pthread_exit(NULL);
return 0;
}
3.
创建一个进程,在主线程中创建一个子线程,主线程和子线程都会使用相同的临界资源(全局变量),要求线程之前使用互斥锁实现资源的互斥访问。
/*
* Copyright (c)
*
* date: 2025-8-2
*
* author: Charles
*
* function name : pthread_mutex_test.c
*
* function: 创建一个进程,在主线程中创建一个子线程,主线程和子线程都会使用相同的临界资源(全局变量),
* 要求线程之前使用互斥锁实现资源的互斥访问。
*
*/
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
volatile int data = 6;
pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
void* task(void* arg){
while(1){
pthread_mutex_lock(&mtx);
data += 10;
printf("task : %d\n", data);
pthread_mutex_unlock(&mtx);
sleep(1);
}
}
int main(int argc, char const *argv[])
{
pthread_t thread;
pthread_create(&thread, NULL, task, NULL);
while(1){
pthread_mutex_lock(&mtx);
data += 10;
printf("main : %d\n", data);
pthread_mutex_unlock(&mtx);
sleep(1);
}
return 0;
}
希望各位靓仔靓女点赞,收藏,关注多多支持,我们共同进步,后续我会更新更多的面试真题,你们的支持将是我前进最大的动力