LinuxC_线程

news/2024/11/30 9:39:28/

LinuxC_线程

  • 1. 为什么使用线程
  • 2. 什么是线程
  • 3. 线程的优点、缺点
  • 4. 线程的应用场合
  • 5. 线程的使用
    • 1. 线程的创建
    • 2. 线程的终止
    • 3. 等待指定线程结束
    • 4. 使用线程程序的编译
    • 5. 线程的同步
    • 6. 线程条件变量

1. 为什么使用线程

  • 使用fork创建进程以执行新的任务,该方式的代价很高。
  • 多个进程间不会直接共享内存
  • 线程是进程的基本执行单元,一个进程的所有任务都在线程中执行,进程要想执行任务,必须得有线程,进程至少要有一条线程,程序启动会默认开启一条线程,这条线程被称为主线程或 UI 线程

2. 什么是线程

线程,是进程内部的一个控制序列。
即使不使用线程,进程内部也有一个执行线程。

类比:创建一个进程,类似于“克隆”一个家庭。该“家庭”与原来的家庭完全相同但是新“家庭”和原来的家庭完全独立。进程包含一个或多个线程。类似与一个家庭,包含一个或多个家庭成员。家庭内的各成员同时做各自的事情(父亲工作、母亲持家、小孩上学)而对于家庭外部的人来说,这个家庭同时在做多件事情。家庭内的每个成员,就是一个线程。各个家庭成员有自己的个人资源(线程有自己的局部变量)但是所有家庭成员都能共享这个家庭的资源:房子、汽车、家庭的公共资金。(同一个进程内的各个线程,能够共享整个进程的全局变量,除了线程的局部变量外,其他资源都共享)注意:单核处理器上,同一个时刻只能运行一个线程。但是对于用户而言,感觉如同同时执行了多个线程一样(各线程在单核CPU上切换,在一段时间内,同时执行了多个线程)

3. 线程的优点、缺点

优点: 创建线程比创建进程,开销要小。
缺点: 1)多线程编程,需特别小心,很容易发生错误。2)多线程调试很困难。3)把一个任务划分为两部分,用两个线程在单处理器上运行时,不一定更快。除非能确定这两个部分能同时执行、且运行在多处理器上。

4. 线程的应用场合

1) 需要让用户感觉在同时做多件事情时,比如,处理文档的进程,一个线程处理用户编辑,一个线程同时统计用户的字数。2) 当一个应用程序,需要同时处理输入、计算、输出时,可开3个线程,分别处理输入、计算、输出。让用户感觉不到等待。3) 高并发编程。

5. 线程的使用

1. 线程的创建

pthread_create
原型:
int  pthread_create (pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void*), void *arg
);      
参数:
thread, 指向新线程的标识符。通过该指针返回所创建线程的标识符。
attr, 用来设置新线程的属性。一般取默认属性,即该参数取NULL
start_routine, 该线程的处理函数该函数的返回类型和参数类型都是void*
arg, 线程处理函数start_routine的参数
功能:创建一个新线程,同时指定该线程的属性、执行函数、执行函数的参数通过参数1返回该线程的标识符。

返回值:成功,返回0
失败,返回错误代码
注意:大部分pthread_开头的函数成功时返回0,失败时返回错误码(而不是-1)

注意:使用fork创建进程后,进程马上就启动但是是和父进程同时执行fork后的代码
使用pthread_create创建线程后,新线程马上就启动,执行的是对应的线程处理函数

2. 线程的终止

pthread_exit

原型:void pthread_exit (void *retval)
功能:在线程函数内部调用该函数
终止该线程,并通过参数retval返回一个指针
该指针不能指向该线程的局部变量。

3. 等待指定线程结束

pthread_join

功能:类似与进程中的waitpid
等待指定的线程结束,并使参数指向该线程函数的返回值(用pthread_exit返回的值)
原型:int pthread_join (pthread_t th,void ** thread_return);
参数:th, 指定等待的线程
thread_return, 指向该线程函数的返回值,线程函数的返回值类型void*,故该参数的类型为void**

4. 使用线程程序的编译

(1) 编译时,定义宏_REENTRANT(不一定要使用)
即: gcc -D_REENTRANT (#define REENTRANT)

功能:告诉编译器,编译时需要可重入功能
即使得,在编译时,编译部分函数的可重入版本

注:在单线程程序中,整个程序都是顺序执行的,一个函数在同一时刻只能被一个函数调用,但在多线程中,由于并发性一个函数可能同时被多个函数调用,此时这个函数就成了临界资源,很容易造成调用函数处理结果的相互影响,如果一个函数在多线程并发的环境中每次被调用产生的结果是不确定的,我们就说这个函数是==“不可重入的”/“线程不安全”==的。

总之,-D_REENTRANT 标志来启用线程安全行为的

(2) 编译时,指定线程库
即: gcc -lpthread
功能:使用系统默认的NPTL线程库,
即在默认路径中寻找库文件libpthread.so
默认路径为/usr/lib/usr/local/lib

当系统默认使用的不是NPTL线程库时(系统较老,2003年以前)
指定:gcc -L/usr/lib/nptl -lpthread
补充: -L 指定库文件所在的目录
-l 指定库文件名称(-lpthread ,指库文件名为libpthread.so)

总结:一般使用如下形式即可

gcc   mythread.c    -o   mythread  -D_REENTRANT   -lpthread
eg:
//main1.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>int my_global;void* my_thread_handle(void *arg) 
{int val;val = *((int*)arg);printf("new thread begin, arg=%d\n", val);my_global += val;sleep(3);pthread_exit(&my_global);//  不再执行printf("new thread end\n");
}int main(void)
{pthread_t  mythread;int arg;int ret;void *thread_return;arg = 100;my_global = 1000;printf("my_global=%d\n", my_global);printf("ready create thread...\n");ret = pthread_create(&mythread, 0, my_thread_handle, &arg);if (ret != 0) {printf("create thread failed!\n");exit(1);}printf("wait thread finished...\n");ret = pthread_join(mythread, &thread_return);if (ret != 0) {printf("pthread_join failed!\n");exit(1);}printf("wait thread end, return value is %d\n", *((int*)thread_return));printf("my_global=%d\n", my_global);printf("create thread finished!\n");
}

5. 线程的同步

1)线程的互斥 - 指某一资源同时只允许一个访问者对其进行访问,具有唯一性排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的
2)线程的同步 - 指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。

  1. 问题
    同一个进程内的各个线程,共享该进程内的全局变量
    如果多个线程同时对某个全局变量进行访问时,就可能导致竞态。

    解决办法: 对临界区使用信号量、或互斥量

  2. 信号量和互斥量的选择。
    对于同步互斥,使用信号量互斥量都可以实现。
    使用时,选择更符合语义的手段:
    如果要求最多只允许一个线程进入临界区,则使用互斥量
    如果要求多个线程之间的执行顺序满足某个约束,则使用信号量

  3. 信号量
    1)什么是信号量
    此时所指的“信号量”是指用于同一个进程内多个线程之间的信号量
    POSIX信号量,而不是System V信号量(用于进程之间的同步)
    用于线程的信号量的原理,与用于进程之间的信号量的原理相同
    都有P操作、V操作
    信号量的表示:sem_t 类型

    2)信号量的初始化
    sem_init
    原型:int sem_init (sem_t *sem, int pshared, unsigned int value);
    功能:对信号量进行初始化
    参数:sem, 指向被初始化的信号量
    pshared, 0:表示该信号量是该进程内使用的“局部信号量”, 不再被其它进程共享。非0:该信号量可被其他进程共享,Linux不支持这种信号量
    value, 信号量的初值。>= 0
    返回值:成功,返回0
    失败, 返回错误码

    3)信号量的P操作
    sem_wait
    原型:int sem_wait (sem_t *sem);
    返回值:成功,返回0
    失败, 返回错误码

sem_wait函数的执行主要分为以下几个步骤:

  • 检查传入的参数sem是否为NULL,如果是,则返回错误码EINVAL。
  • 获得sem指向的信号量的锁,以保证线程安全。
  • 如果信号量的值大于0,则将信号量的值减1,表示一个线程正在使用资源。
  • 如果信号量的值为0,则将调用线程加入到等待队列中,并将其置于休眠状态,直到信号量的值大于0才会被唤醒
  • 当信号量的值大于0时,从等待队列中取出一个线程,并将其唤醒。
    释放信号量的锁。

需要注意的是,当多个线程同时调用sem_wait并且信号量的值为0时,它们将被放置在等待队列中,等待信号量的值变为大于0时才会被唤醒。线程被唤醒的顺序是不确定的,可能是先到先服务,也可能是按照优先级等其他因素决定的。

4)信号量的V操作
sem_post
原型:int sem_post (sem_t *sem);
返回值:成功,返回0
失败, 返回错误码

5)信号量的删除
sem_destroy
原型:int sem_destroy (sem_t *sem);
返回值:成功,返回0
失败, 返回错误码

6)实例
主线程循环输入字符串,把字符串存放到一个全局缓存中。
新线程从全局缓存中读取字符串,统计该字符串的长度。
直到用户输入end

//main2.c
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>#define BUFF_SIZE 80char buff[BUFF_SIZE];
sem_t sem;//信号量一定定义为全局的static void* str_thread_handle(void *arg) 
{while(1) {//P(sem)if (sem_wait(&sem) != 0) {//p操作,是1的时候,才会拿数据printf("sem_wait failed!\n");exit(1);}printf("string is: %slen=%u\n", buff, (unsigned int)strlen(buff));if (strncmp(buff, "end", 3) == 0) {break;}}
}int main(void)
{int ret;pthread_t  str_thread;void *thread_return;ret = sem_init(&sem, 0, 0);//初始化信号量if (ret != 0) {printf("sem_init failed!\n");exit(1);}//创建子线程ret = pthread_create(&str_thread, 0, str_thread_handle, 0);if (ret != 0) {printf("pthread_create failed!\n");exit(1);}while (1) {fgets(buff, sizeof(buff), stdin);//V(sem)if (sem_post(&sem) != 0) {//v操作,放数据printf("sem_post failed!\n");exit(1);}if (strncmp(buff, "end", 3) == 0) {break;}}//回收子线程ret = pthread_join(str_thread, &thread_return);if (ret != 0) {printf("pthread_join failed!\n");exit(1);}ret = sem_destroy(&sem);if (ret != 0) {printf("sem_destroy failed!\n");exit(1);}return 0;
}
  1. 互斥量

1)什么是互斥量
效果上等同于初值为1的信号量

互斥量的使用:类型为 pthread_mutex_t

2)互斥量的初始化
pthread_mutex_init
原型:int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr);
参数:mutex, 指向被初始化的互斥量
attr, 指向互斥量的属性
一般取默认属性(当一个线程已获取互斥量后,该线程再次获取该信号量,将导致死锁!)拿两次就直接死锁了(体现到代码中,就是"核心已转储")

3)互斥量的获取(拿到锁,其实就是p操作,-1)
pthread_mutex_lock
原型:int pthread_mutex_lock (pthread_mutex_t *mutex);

4)互斥量的释放
pthread_mutex_unlock
原型:int pthread_mutex_unlock (pthread_mutex_t *mutex);

5)互斥量的删除
pthread_mutex_destroy
int pthread_mutex_destroy (pthread_mutex_t *mutex);

6)实例
最简单的互斥量使用

// main3.c
#include <pthread.h>
#include <semaphore.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>#define BUFF_SIZE 80int global_value = 1000;
pthread_mutex_t lock;static void *str_thread_handle(void *arg)
{int i = 0;for (i = 0; i < 10; i++){pthread_mutex_lock(&lock);if (global_value > 0){// worksleep(1);printf("soled ticket(%d) to ChildStation(%d)\n",global_value, i + 1);}global_value--;pthread_mutex_unlock(&lock);sleep(1);}
}int main(void)
{int ret;pthread_t str_thread;void *thread_return;int i;ret = pthread_mutex_init(&lock, 0); // 创建一个互斥锁if (ret != 0){printf("pthread_mutex_init failed!\n");exit(1);}// 创建一个子线程ret = pthread_create(&str_thread, 0, str_thread_handle, 0);if (ret != 0){printf("pthread_create failed!\n");exit(1);}for (i = 0; i < 10; i++){pthread_mutex_lock(&lock);if (global_value > 0){// worksleep(1);printf("soled ticket(%d) to MainStation(%d)\n",global_value, i + 1);}global_value--;pthread_mutex_unlock(&lock);sleep(1);}// 回收子线程ret = pthread_join(str_thread, &thread_return);if (ret != 0){printf("pthread_join failed!\n");exit(1);}ret = pthread_mutex_destroy(&lock); // 删除互斥锁if (ret != 0){printf("pthread_mutex_destroy failed!\n");exit(1);}return 0;
}
//main4.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>void *thread_function(void *arg);#define WORK_SIZE 1024
char work_area[WORK_SIZE];
int time_to_exit = 0;int main() 
{int res;pthread_t a_thread;void *thread_result;res = pthread_create(&a_thread, NULL, thread_function, NULL);if (res != 0) {perror("Thread creation failed");exit(EXIT_FAILURE);}printf("Input some text. Enter 'end' to finish\n");while(!time_to_exit) {fgets(work_area, WORK_SIZE, stdin);while(1) {if (work_area[0] != '\0') {sleep(1);}else {break;}}}printf("\nWaiting for thread to finish...\n");res = pthread_join(a_thread, &thread_result);if (res != 0) {perror("Thread join failed");exit(EXIT_FAILURE);}printf("Thread joined\n");exit(EXIT_SUCCESS);
}void *thread_function(void *arg) 
{sleep(1);while(strncmp("end", work_area, 3) != 0) {printf("You input %d characters\n", strlen(work_area) -1);work_area[0] = '\0';sleep(1);while (work_area[0] == '\0' ) {sleep(1);}}time_to_exit = 1;work_area[0] = '\0';pthread_exit(0);
}

6. 线程条件变量

1)条件变量的概念

与互斥锁不同,条件变量是用来等待不是用来上锁的。条件变量用来自动阻塞一个线程直到某特殊情况发生为止。通常条件变量互斥锁同时使用。

条件变量使我们可以睡眠等待某种条件出现。条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。
条件的检测是在互斥锁的保护下进行的。如果一个条件为,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步

2)条件变量初始化
pthread_cond_init
原型:int pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr);
参数:cond, 条件变量指针(使用全局变量)
attr 条件变量高级属性
man 安装: apt-get install manpages-posix-dev

3)唤醒一个等待线程
pthread_cond_signal 通知条件变量,唤醒一个等待者
原型: int pthread_cond_signal (pthread_cond_t *cond);
参数:cond, 条件变量指针

4)唤醒所有等待该条件变量的线程
pthread_cond_broadcast 广播条件变量
原型: int pthread_cond_broadcast (pthread_cond_t *cond);
参数:cond, 条件变量指针

5)等待条件变量/超时被唤醒
pthread_cond_timedwait 等待条件变量cond被唤醒,直到由一个信号或广播,或绝对时间abstime到 * 才唤醒该线程
原型: int pthread_cond_timedwait (pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
参数:cond, 条件变量指针
pthread_mutex_t *mutex 互斥量
const struct timespec *abstime 等待被唤醒的绝对超时时间

6)等待条件变量被唤醒(最长用)
pthread_cond_wait 等待条件变量cond被唤醒(由一个信号或者广播)
原型: int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);
参数:cond, 条件变量指针
pthread_mutex_t *mutex 互斥量
常见错误码:
[EINVAL] cond或mutex无效,
[EINVAL] 同时等待不同的互斥量
[EINVAL] 主调线程没有占有互斥量

7)释放/销毁条件变量
pthread_cond_destroy 待销毁的条件变量
原型: int pthread_cond_destroy (pthread_cond_t *cond);
参数:cond, 条件变量指针

8)实例

对下面一句代码进行深度理解:
// 等待条件变量的信号
// 如果条件变量的信号未到来,则将当前线程阻塞在条件变量上,并释放互斥锁mutex
// 如果条件变量的信号已经到来,则重新获取互斥锁mutex,并向下执行
pthread_cond_wait(&cond, &mutex);
//main6.c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>pthread_mutex_t mutex;
pthread_cond_t cond;void *thread1(void *arg)
{while (1) {printf("thread1 is running\n");pthread_mutex_lock(&mutex);//上锁pthread_cond_wait(&cond, &mutex);//条件没来,就阻塞在这并释放锁;条件来了,就上锁,并向下运行printf("thread1 applied the condition\n");pthread_mutex_unlock(&mutex);sleep(4);}
}void *thread2(void *arg)
{while (1) {printf("thread2 is running\n");pthread_mutex_lock(&mutex);pthread_cond_wait(&cond, &mutex);printf("thread2 applied the condition\n");pthread_mutex_unlock(&mutex);sleep(2);}
}int main()
{pthread_t thid1, thid2;printf("condition variable study!\n");pthread_mutex_init(&mutex, NULL);//初始化互斥锁pthread_cond_init(&cond, NULL);//初始化条件变量pthread_create(&thid1, NULL, (void *)thread1, NULL);//创建子线程thid1,(void *)thread1是线程要执行的函数pthread_create(&thid2, NULL, (void *)thread2, NULL);//创建子线程thid2do {pthread_cond_signal(&cond);//不断的唤醒其中一个信号sleep(1);} while (1);return 0;
}
//main7.c  pthread_cond_wait 中释放和加锁机制理解
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>pthread_mutex_t mutex;
pthread_cond_t cond;void *thread1(void *arg)
{while (1) {printf("thread1 is running\n");pthread_mutex_lock(&mutex);printf("thread1 lock..\n");pthread_cond_wait(&cond, &mutex);printf("thread1 applied the condition\n");printf("thread1 unlock..\n");pthread_mutex_unlock(&mutex);sleep(4);}
}void *thread2(void *arg)
{while (1) {printf("thread2 is running\n");pthread_mutex_lock(&mutex);printf("thread2 lock..\n");pthread_cond_wait(&cond, &mutex);printf("thread2 applied the condition\n");printf("thread2 unlock..\n");pthread_mutex_unlock(&mutex);sleep(2);}
}int main()
{pthread_t thid1, thid2;printf("condition variable study!\n");pthread_mutex_init(&mutex, NULL);pthread_cond_init(&cond, NULL);pthread_create(&thid1, NULL, (void *)thread1, NULL);pthread_create(&thid2, NULL, (void *)thread2, NULL);do {sleep(10);pthread_cond_signal(&cond);} while (1);return 0;
}

http://www.ppmy.cn/news/104790.html

相关文章

SpringBoot——原理(自动配置_案例(自定义阿里云文件上starter))

starter定义 starter就是springboot中的起步依赖&#xff0c;虽然springboot已经提供了很多的起步依赖&#xff0c;但是在实际项目开发中可能会用到和第三方的技术&#xff0c;不是所有第三方在springboot中都有收录。 比如之前文章中有用到过的阿里云OSS&#xff0c;阿里云并…

修改element Plus的主题样式

安装element plus 安装icon pnpm install element-plus pnpm install element-plus/icons-vue main.ts配置 icon的使用https://element-plus.gitee.io/zh-CN/component/icon.html#%E7%BB%93%E5%90%88-el-icon-%E4%BD%BF%E7%94%A8 import { createApp } from vue import ./sty…

《深入理解计算机系统(CSAPP)》第6章 存储器层次结构 - 学习笔记

写在前面的话&#xff1a;此系列文章为笔者学习CSAPP时的个人笔记&#xff0c;分享出来与大家学习交流&#xff0c;目录大体与《深入理解计算机系统》书本一致。因是初次预习时写的笔记&#xff0c;在复习回看时发现部分内容存在一些小问题&#xff0c;因时间紧张来不及再次整理…

C++强制类型转换

1. static_cast static_cast 是 C 中的一种显式类型转换运算符。 它可以将一个表达式强制转换为指定的类型&#xff0c;并且是静态类型转换&#xff0c;因此不会执行任何运行时类型检查。如果类型转换不合法&#xff0c;则程序可能出现未定义的行为。因此&#xff0c;使用 stat…

计算机组成原理有关缩写

只是帮助记忆&#xff0c;可能原本的全称不是这个&#xff0c;大家注意一下。 英文简写中文含义帮助记忆CPU中央处理器Central Processing UnitPC程序计数器Program CounterIR指令寄存器Instruction RegisterLA锁存器ALatch ALB锁存器BLatch BALU算术逻辑单元Arithmetic Logic…

Linux 系统编程 :检查文件访问权限与存在性access()函数及其底层原理

背景知识 在Unix和类Unix系统&#xff08;如Linux&#xff09;中&#xff0c;进程的用户ID和组ID分为实际&#xff08;real&#xff09;和有效&#xff08;effective&#xff09;两种&#xff0c;这是为了提供更灵活的权限控制和增强系统的安全性。 实际用户ID和实际组ID&…

Linux Apache 配置与应用 【虚拟主机 连接保持 日志分割 分析系统 优化网页】

--------构建虚拟 Web 主机-------- 虚拟Web主机指的是在同一台服务器中运行多个Web站点&#xff0c;其中每一个站点实际上并不独立占用整个服务器&#xff0c;因此被称为“虚拟”Web 主机。通过虚拟 Web 主机服务可以充分利用服务器的硬件资源&#xff0c;从而大大降低网站构建…

IIS+Django+wFastCGI部署网站解决Not Found的问题

这里写自定义目录标题 问题背景django部署的基本原理就是&#xff1a; 基本步骤Not Found问题解决办法&#xff1a; 关于访问的域名和IP地址关于一个服务器建多个网站 问题背景 网上的部署教程基本没有问题&#xff0c;这里记录一些过程中遇到的问题和理解。 参考&#xff1a…