多线程
一 多线程1 什么是线程要了解线程,首先需要知道进程。一个进程指的是一个正在执行的应用程序。线程对...
要了解线程,首先需要知道进程。一个进程指的是一个正在执行的应用程序。线程对应的英文名称为“thread
”,它的功能是执行应用程序中的某个具体任务,比如一段程序、一个函数等。线程和进程之间的关系,类似于工厂和工人之间的关系,进程好比是工厂,线程就如同工厂中的工人。一个工厂可以容纳多个工人,工厂负责为所有工人提供必要的资源(电力、产品原料、食堂、厕所等),所有工人共享这些资源,每个工人负责完成一项具体的任务,他们相互配合,共同保证整个工厂的平稳运行。每个进程执行前,操作系统都会为其分配所需的资源,包括要执行的程序代码、数据、内存空间、文件资源等。一个进程至少包含 1 个线程,可以包含多个线程,所有线程共享进程的资源,各个线程也可以拥有属于自己的私有资源。进程仅负责为各个线程提供所需的资源,真正执行任务的是线程,而不是进程。
所谓多线程,即一个进程中拥有多(≥2)个线程,线程之间相互协作、共同执行一个应用程序。
当进程中仅包含 1 个执行程序指令的线程时,该线程又称“主线程”,这样的进程称为“单线程进程”。
(资料图)
pthread_create()
函数该函数用来创建线程,pthread_create() 函数声明在
头文件中,或者说我们接下来使用的多线程相关函数都声明在
该函数的详细使用方法可以通过CSDN技能树、菜鸟教程等地方学习,这里主要介绍学习时比较难以理解的地方。
void*
类型。void*
类型又称空指针类型,表明指针所指数据的类型是未知的(如果不理解,类比于结构体指针一样理解)。使用此类型指针时,我们通常需要先对其进行强制类型转换,然后才能正常访问指针指向的数据。pthread_exit()
函数该函数用来终止线程执行。多线程程序中,终止线程执行的方式本来有 3 种,分别是:
线程执行完成后,自行终止;
线程执行过程中遇到了 pthread_exit() 或者 return,也会终止执行;
线程执行过程中,接收到其它线程发送的“终止执行”的信号,然后终止执行。
第一种的理解就是什么也不管,线程执行完会自己终止;第二种就是本部分要用的pthread_exit()函数,return也好理解,返回即终止;第三种方法,本来要使用pthread_cancel()函数,但在使用这个函数时会出现其他一系列的问题,解决起来非常麻烦,所以除非特殊情况,我们一般使用第二种方式。
补充:pthread_exit()和return的区别:首先,return 语句和 pthread_exit() 函数的含义不同,return 的含义是返回,它不仅可以用于线程执行的函数,普通函数也可以使用;pthread_exit() 函数的含义是线程退出,它专门用于结束某个线程的执行。实际使用中,我们终止子线程一般都使用pthread_exit()函数,不建议使用return。
该函数用来获取某个线程执行结束时返回的数据,使用也比较简单,学习一下就会使用,这里不解释。
但需要注意的有一点:一个线程执行结束的返回值只能由一个 pthread_join() 函数获取,当有多个线程调用 pthread_join() 函数获取同一个线程的执行结果时,哪个线程最先执行 pthread_join() 函数,执行结果就由那个线程获得,其它线程的 pthread_join() 函数都将执行失败。
多线程程序中各个线程除了可以使用自己的私有资源(局部变量、函数形参等)外,还可以共享全局变量、静态变量、堆内存、打开的文件等资源。我们通常将“多个线程同时访问某一公共资源”的现象称为“线程间产生了资源竞争”或者“线程间抢夺公共资源”,线程间竞争资源往往会导致程序的运行结果出现异常,我们常常采用同步机制来解决这种问题。
实现线程同步的常用方法有 4 种,分别称为互斥锁、信号量、条件变量和读写锁。
互斥锁(Mutex)又称互斥量或者互斥体,是最简单也最有效地一种线程同步机制。互斥锁的用法和实际生活中的锁非常类似,当一个线程访问公共资源时,会及时地“锁上”该资源,阻止其它线程访问;访问结束后再进行“解锁”操作,将该资源让给其它线程访问。
信号量又称“信号灯”,主要用于控制同时访问公共资源的线程数量,当线程数量控制在 ≤1 时,该信号量又称二元信号量,功能和互斥锁非常类似;当线程数量控制在 N(≥2)个时,该信号量又称多元信号量,指的是同一时刻最多只能有 N 个线程访问该资源。
条件变量的功能类似于实际生活中的门,门有“打开”和“关闭”两种状态,分别对应条件变量的“成立”状态和“不成立”状态。当条件变量“不成立”时,任何线程都无法访问资源,只能等待条件变量成立;一旦条件变量成立,所有等待的线程都会恢复执行,访问目标资源。为了防止各个线程竞争资源,条件变量总是和互斥锁搭配使用。
多线程程序中,如果大多数线程都是对公共资源执行读取操作,仅有少量的线程对公共资源进行修改,这种情况下可以使用读写锁解决线程同步问题。
这里我们使用最简单的也是最常用的方法:互斥锁。
POSIX 标准规定,用 pthread_mutex_t 类型的变量来表示一个互斥锁,该类型以结构体的形式定义在
头文件中。
初始化 pthread_mutex_t 变量的方式有两种,分别为:
//1、使用特定的宏pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;//2、调用初始化的函数pthread_mutex_t myMutex;pthread_mutex_init(&myMutex , NULL);
对于互斥锁的“加锁”和“解锁”操作,常用的函数有以下 3 种:
int pthread_mutex_lock(pthread_mutex_t* mutex); //实现加锁int pthread_mutex_trylock(pthread_mutex_t* mutex); //实现加锁int pthread_mutex_unlock(pthread_mutex_t* mutex); //实现解锁
参数 mutex 表示我们要操控的互斥锁。函数执行成功时返回数字 0,否则返回非零数。
实现线程同步的 4 种方法,分别是互斥锁、信号量、条件变量和读写锁。很多初学者在使用这些方法的过程中,经常会发生“线程一直被阻塞”的情况,我们习惯将这种情况称为“死锁”。线程死锁指的是线程需要使用的公共资源一直被其它线程占用,导致该线程一直处于“阻塞”状态,无法继续执行。
使用互斥锁、信号量、条件变量和读写锁实现线程同步时,要注意以下几点:
注意,函数中可以设置多种结束执行的路径,但无论线程选择哪个路径结束执行,都要保证能够将占用的资源释放掉。
避免线程死锁也有许多方法,比如最经典的银行家算法,后面会写一篇博客单独用代码实现这一算法。
//// Created by 洪泽林 on 2023/4/8.//#include #include //定义线程要执行的函数,arg 为接收线程传递过来的数据void *Thread1(void *arg) { printf("CSDN@终究还是散了\n"); return "Thread1成功执行";}//定义线程要执行的函数,arg 为接收线程传递过来的数据void* Thread2(void* arg){ printf("博客园@挽留岁月挽留你\n"); return "Thread2成功执行";}int main(){ int res; pthread_t mythread1, mythread2; void* thread_result; /*创建线程 &mythread:要创建的线程 NULL:不修改新建线程的任何属性 ThreadFun:新建线程要执行的任务 NULL:不传递给 ThreadFun() 函数任何参数 返回值 res 为 0 表示线程创建成功,反之则创建失败。 */ res = pthread_create(&mythread1, NULL, Thread1, NULL); if (res != 0) { printf("线程创建失败"); return 0; } res = pthread_create(&mythread2, NULL, Thread2, NULL); if (res != 0) { printf("线程创建失败"); return 0; } /* 等待指定线程执行完毕 mtThread:指定等待的线程 &thead_result:接收 ThreadFun() 函数的返回值,或者接收 pthread_exit() 函数指定的值 返回值 res 为 0 表示函数执行成功,反之则执行失败。 */ res = pthread_join(mythread1, &thread_result); //输出线程执行完毕后返回的数据 printf("%s\n", (char*)thread_result); res = pthread_join(mythread2, &thread_result); printf("%s\n", (char*)thread_result); printf("主线程执行完毕"); return 0;}
#include#include#include#includeint ticket_sum = 10;//创建互斥锁pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;//模拟售票员卖票void *sell_ticket(void *arg) { //输出当前执行函数的线程 ID printf("当前线程ID:%u\n", pthread_self()); int i; int islock = 0; for (i = 0; i < 10; i++) { //当前线程“加锁” islock = pthread_mutex_lock(&myMutex); //如果“加锁”成功,执行如下代码 if (islock == 0) { //如果票数 >0 ,开始卖票 if (ticket_sum > 0) { sleep(1); printf("%u 卖第 %d 张票\n", pthread_self(), 10 - ticket_sum + 1); ticket_sum--; } //当前线程模拟完卖票过程,执行“解锁”操作 pthread_mutex_unlock(&myMutex); } } return 0;}int main() { int flag; int i; void *ans; //创建 4 个线程,模拟 4 个售票员 pthread_t tids[4]={1,2,3,4}; for (i = 0; i < 4; i++) { flag = pthread_create(&tids[i], NULL, &sell_ticket, NULL); if (flag != 0) { printf("线程创建失败!"); return 0; } } sleep(10); //等待 4 个线程执行完成 for (i = 0; i < 4; i++) { //阻塞主线程,确认 4 个线程执行完成 flag = pthread_join(tids[i], &ans); if (flag != 0) { printf("tid=%d 等待失败!", tids[i]); return 0; } } return 0;}
标签:
一 多线程1 什么是线程要了解线程,首先需要知道进程。一个进程指的是一个正在执行的应用程序。线程对...
1、枯木,天猫好房首席技术官。2、。本文到此结束,希望对大家有所帮助。
河南经济报记者张建涛通讯员樊帅近日,扶沟法院秉承善意文明执行理念,坚持柔性执法与强制执行并重,因案...
美报告分析中国消费群体代际差异,中国,奢侈品,中产阶层,消费群体
1、功能性消化不良、胃肠炎、消化性溃疡、肠梗阻、胃肠肿瘤、胃下垂等。2、会造成胃肠蠕动缓慢,胃肠动...
智东西编译|徐珊编辑|云鹏智东西4月14日消息,据华尔街日报报道,字节跳动正通过补贴的方式,吸引更多海...
1、汉时的黄香,是历史上公认的“孝亲”的典范。2、黄香小时候,家境困难,10岁失去母亲,父亲多病。3、...
1、浙江有什么医学大专学校基本上每个专科学校都有会计专业的,只是有些学校的主要专业是会计,比如说浙...
1、真三国无双5全功能修改器(凉宫-1 8final)修改内容:能修改等级、状态、无双连舞、武器、马另外还...
吴耀汉1939年12月17日出生于广东省潮州,出道近50年,演过不少经典作品,包括《五福星》系列、《飞越黄...