Linux 스레드는 어떻게 생성되나요?
스레드의 개념과 구현
스레드는 프로세스 내의 실행 순서 또는 실행 경로입니다. 프로세스에는 여러 스레드가 포함될 수 있습니다.
자원 할당의 관점에서 프로세스는 운영체제에 의한 자원 할당의 기본 단위입니다.
리소스 스케줄링의 관점에서 스레드는 리소스 스케줄링의 최소 단위이자 프로그램 실행의 최소 단위입니다.
실행 순서는 순서가 지정된 명령어 집합, 즉 함수입니다.
스레드는 프로세스 내의 실행 시퀀스입니다. 프로세스에는 메인 스레드라고 불리는 하나 이상의 스레드가 있습니다(메인 메소드로 표시되는 실행 시퀀스) 스레드 라이브러리를 통해 다른 스레드를 생성할 수 있습니다. 실행하려는 작업) 함수), 생성된 스레드를 함수 스레드라고 합니다.
스레드 구현 방법
커널 수준 스레드(스레드는 커널에서 직접 생성하고 관리합니다. 생성 오버헤드가 높지만 멀티 프로세서 리소스를 활용할 수 있음)
사용자 수준 스레드(스레드는 커널에 의해 직접 생성 및 관리됨) 스레드의 구현은 사용자 모드에 있으며 생성 오버헤드가 적고 다중 프로세서의 리소스를 감지할 수 없습니다. 사용됨)
혼합 수준 스레드(위의 두 가지 방법을 결합하여 구현됨) 다중 프로세서의 리소스를 활용하여 사용자 공간에 더 많은 스레드를 생성하여 커널 공간의 스레드에 다대다 매핑 가능 , N:M (N>>M))
Linux 시스템이 멀티스레딩을 구현하는 방식
Linux의 스레드 구현 메커니즘은 매우 독특합니다. 커널 관점에서는 스레드 개념이 없습니다.
Linux는 모든 스레드를 프로세스로 구현합니다. 커널은 특별한 스케줄링 알고리즘을 준비하거나 스레드를 나타내기 위해 특별한 데이터 구조를 정의하지 않습니다.
대신 스레드는 단순히 특정 리소스를 다른 프로세스와 공유하는 프로세스로 간주됩니다.
각 스레드에는 고유한 task_struct가 있으므로 커널에서는 일반 프로세스처럼 보입니다. (스레드가 주소 공간과 같은 특정 리소스를 다른 프로세스와 공유한다는 점만 다를 뿐입니다.)
스레드와 프로세스의 차이점
프로세스는 리소스 할당의 최소 단위이고 스레드는 프로그램 실행의 최소 단위입니다.
스레드 간 전환의 효율성은 프로세스 간 전환의 효율성보다 높습니다.
프로세스에는 고유한 특성이 있습니다. 프로세스가 시작될 때마다 시스템은 해당 프로세스에 대한 주소 공간을 할당하고 코드 세그먼트, 스택 세그먼트 및 데이터 세그먼트를 유지하기 위해 데이터 테이블을 설정합니다. 데이터를 공유하기 위한 동일한 주소 공간
스레드 생성 프로세스보다 오버헤드가 적습니다.
스레드는 프로세스보다 훨씬 적은 리소스를 차지합니다.
스레드 간 통신이 더 편리합니다. 동일한 프로세스에서 스레드는 전역 변수, 정적 변수 및 기타 데이터를 공유합니다. 프로세스 간 통신은 통신(IPC)을 통해 수행되어야 합니다. 상호 배제는 어려운 점입니다)
다중 프로세스 프로그램은 더 안전하고 중요합니다(독립 주소 공간으로 인해). . 하나의 스레드 프로세스가 종료되면 전체 프로세스가 종료됩니다(공유 주소 공간으로 인해).
이 프로세스는 리소스 보호 요구 사항이 높고 오버헤드가 높으며 스레드 리소스 보호 요구 사항은 상대적으로 낮습니다. 오버헤드가 적고 효율성이 높습니다.
멀티 스레드 개발의 세 가지 기본 개념
스레드 [생성, 종료, 대기]
뮤텍스 잠금 [생성, 파괴, 잠금] , 잠금 해제]
조건 [생성, 파괴, 트리거, 브로드캐스트, 대기]
스레드 라이브러리 사용
1. 스레드 만들기
#include<phread.h> int pthread_create(pthread_t *id , pthread_attr_t *attr, void(*fun)(void*), void *arg);
id
: pthread_t 유형의 변수 주소를 전달합니다. 생성이 성공한 후 새로 생성된 스레드의 TID를 얻는 데 사용됩니다id
:传递一个pthread_t类型的变量的地址,创建成功后,用来获取新创建的线程的TIDattr
:指定线程的属性 默认使用NULLfun
:线程函数的地址arg
:传递给线程函数的参数返回值,成功返回0,失败返回错误码
多线程代码示例
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h> #include<pthread.h> //声明一个线程函数 void *fun(void *); int main() { printf("main start\n"); pthread_t id; //创建函数线程,并且指定函数线程要执行的函数 int res = pthread_create(&id,NULL,fun,NULL); assert(res == 0); //之后并发运行 int i = 0; for(; i < 5; i++) { printf("main running\n"); sleep(1); } printf("main over\n"); exit(0); } //定义线程函数 void* fun(void *arg) { printf("fun start\n"); int i = 0; for(; i < 3;i++) { printf("fun running\n"); sleep(1); } printf("fun over\n"); }
gcc编译代码时报`undifined reference to xxxxx错误,都是因为程序中调用了一些方法,但是没有连接该方法所在的文件,例如下面的情况:
连接库文件编译成功并执行,这一点在帮助手册中也有提示:Compile and link with -pthread
attr
: 지정된 스레드의 속성은 다음과 같이 NULL을 사용합니다. default🎜🎜🎜🎜fun
: 스레드 함수의 주소 🎜🎜🎜🎜arg
: 스레드 함수에 전달된 매개변수 🎜🎜🎜🎜🎜 반환 값 🎜, 성공 반환 0, 실패하면 오류 코드 🎜🎜🎜🎜🎜멀티 스레드 코드 예제 🎜🎜#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h> #include<pthread.h> void *fun(void *); int main() { printf("main start\n"); int a = 10; pthread_t id; int res = pthread_create(&id,NULL,fun,(void*)a); assert(res == 0); int i = 0; for(; i < 5; i++) { printf("main running\n"); sleep(1); } printf("main over\n"); exit(0); } void* fun(void *arg) { int b = (int)arg; printf("b == %d\n",b); }

-pthread를 사용하여 컴파일 및 링크
🎜比较两次运行的结果发现前三条执行语句时一样的
结论
创建线程并执行线程函数,和调用函数是完全不同的概念。
主线程和函数线程是并发执行的。
线程提前于主线程结束时,不会影响主线程的运行
主线程提前于线程结束时,整个进程都会结束,其他线程也会结束
创建函数线程后,哪个线程先被执行是有操作系统的调度算法和机器环境决定。
函数线程在主线程结束后也随之退出,原因:主线程结束时使用的是exit方法,这个方法结束的是进程。
然而修改代码为:pthread_exit(NULL);
此时主线程结束,函数线程会继续执行直至完成。即便如此,我们还是不推荐大家手动结束主线程,我们更喜欢让主线程等待一会。
给线程函数传参
①值传递
将变量的值直接转成void*类型进行传递
因为线程函数接受的是一个void*类型的指针,只要是指针,32位系统上都是4个字节,值传递就只能传递小于或等于4字节的值。
代码示例
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h> #include<pthread.h> void *fun(void *); int main() { printf("main start\n"); int a = 10; pthread_t id; int res = pthread_create(&id,NULL,fun,(void*)a); assert(res == 0); int i = 0; for(; i < 5; i++) { printf("main running\n"); sleep(1); } printf("main over\n"); exit(0); } void* fun(void *arg) { int b = (int)arg; printf("b == %d\n",b); }
②地址传递
将变量(所有类型)的地址强转成void*
类型进行传递,就和在普通函数调用传递变量的地址相似。
主线程和函数线程通过这个地址就可以共享地址所指向的空间。
一个进程内的所有线程是共享这个进程的地址空间。
多线程下进程的4G虚拟地址空间
一个进程内的所有线程对于全局数据,静态数据,堆区空间都是共享的。
线程之间传递数据很简单,但是随之带来的问题就是线程并发运行时无法保证线程安全。
代码示例
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h> #include<pthread.h> int gdata = 10; //.data void *fun(void *); int main() { int *ptr = (int *)malloc(4);//.heap *ptr = 10; pthread_t id; int res = pthread_create(&id,NULL,fun,(void*)ptr); assert(res == 0); sleep(2);//等待两秒,保证函数线程已经讲数据修改 printf("main : gdata == %d\n",gdata); printf("main : *ptr = %d\n",*ptr); exit(0); } void *fun(void *arg) { int *p = (int*)arg; gdata = 20000; *p = 20; printf("fun over\n"); }
线程库中的其他方法
线程退出的三种方式:
线程从执行函数返回,返回值是线程的退出码;
线程被同一进程的其他线程取消;
调用pthread_exit()函数退出;
等待线程终止
int pthread_join(pthread_t thread, void **retval); args: pthread_t thread: 被连接线程的线程号,该线程必须位于当前进程中,而且不得是分离线程 void **retval :该参数不为NULL时,指向某个位置 在该函数返回时,将该位置设置为已终止线程的退出状态 return: 线程连接的状态,0是成功,非0是失败
当A线程调用线程B并 pthread_join() 时,A线程会处于阻塞状态,直到B线程结束后,A线程才会继续执行下去。当 pthread_join() 函数返回后,被调用线程才算真正意义上的结束,它的内存空间也会被释放(如果被调用线程是非分离的)。
这里有三点需要注意:
系统仅释放系统空间,你需要手动清除程序分配的空间,例如由 malloc() 分配的空间。
2.一个线程只能被一个线程所连接。
3.被连接的线程必须是非分离的,否则连接会出错。所以可以看出pthread_join()有两种作用:1-用于等待其他线程结束:当调用 pthread_join() 时,当前线程会处于阻塞状态,直到被调用的线程结束后,当前线程才会重新开始执行。2-对线程的资源进行回收:如果一个线程是非分离的(默认情况下创建的线程都是非分离)并且没有对该线程使用 pthread_join() 的话,该线程结束后并不会释放其内存空间,这会导致该线程变成了“僵尸线程”。
等待指定的子线程结束
等待thread()指定的线程退出,线程未退出时,该方法阻塞
result接收thread线程退出时,指定退出信息
int pthread_join(pthread_t id,void **result)//调用这个方法的线程会阻塞,直到等待线程结束
代码演示:
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h> #include<pthread.h> int main() { printf("main start\n"); pthread_t id; int res = pthread_create(&id,NULL,fun,NULL); assert(res == 0); //之后并发运行 int i = 0; for(; i < 5; i++) { printf("main running\n"); sleep(1); } char *s = NULL; pthread_join(id,(void **)&s); printf("join : s = %s\n",s); exit(0); } //定义线程函数 void* fun(void *arg) { printf("fun start\n"); int i = 0; for(; i < 10;i++) { printf("fun running\n"); sleep(1); } printf("fun over\n"); pthread_exit("fun over");//将该字符常量返回给主线程 }
此时,主线程完成五次输出,就会等待子线程结束,阻塞等待,子线程结束后,最后,主线程打印join:s = fun over
关于exit和join的一些详细说明:
线程自己运行结束,或者调用pthread_exit结束,线程都会释放自己独有的空间资源;
若线程是非分离的,线程会保留线程ID号,直到其他线程通过joining这个线程确认其已经死亡,join的结果是joining线程得到已终止线程的退出状态,已终止线程将消失;
若线程是分离的,不需要使用pthread_exit(),线程自己运行结束,线程结束就会自己释放所有空间资源(包括线程ID号);
子线程最终一定要使用pthread_join()或者设置为分离线程来结束线程,否则线程的资源不会被完全释放(使用取消线程功能也不能完全释放);
主线程运行pthrea_exit(),会结束主线程,但是不会结束子线程;
主线程结束,则整个程序结束,所以主线程最好使用pthread_join函数等待子线程结束,使用该函数一个线程可以等待多个线程结束;
使用pthread_join函数的线程将会阻塞,直到被join的函数线程结束,该函数返回,但是它对被等待终止的线程运行没有影响;
如果子线程使用exit()则可以结束整个进程;
线程属性
线程具有的属性可以在线程创建的时候指定;
——pthread_create()函数的第二个参数(pthread_attr_t *attr)表示线程的属性,在以前的例子中将其值设为NULL,也就是采用默认属性,线程的多项属性都是可以修改的,这些属性包括绑定属性,分离属性,堆栈属性,堆栈大小,优先级。
系统默认的是非绑定,非分离,缺省1M的堆栈以及父子进程优先级相同
线程结构如下:
typedef struct { int detachstate; //线程的分离状态 int schedpolicy; //线程调度策略 struct sched_param schedparam; //线程的调度参数 int inheritsched; //线程的继承性 int scope; //线程的作用域 size_t guardsize; //线程栈末尾的警戒缓冲区大小 int stackaddr_set; //线程的栈设置 void* stackaddr; //线程栈的位置 size_t stacksize; //线程栈的大小 } pthread_attr_t;
每一个属性都有对应的一些函数,用于对其进行查看和修改,下面分别介绍:
线程属性初始化
初始化和去初始化分别对应于如下的两个函数:
#include <pthread.h> ①int pthread_attr_init(pthread_attr_t *attr); ②it pthread_attr_destroy(pthread_attr_t *attr);
①功能:
初始化线程属性函数,注意:应先初始化线程属性,再pthread_create创建线程
参数:
attr
:线程属性结构体
返回值:
成功:0
失败:-1
②功能:
销毁线程属性所占用的资源函数
参数:
attr
:线程属性结构体
返回值:
成功:0
失败:-1
线程分离
线程的分离状态决定一个线程以什么样的方式来终止自己,这个在之前我们也说过了。
默认状态下,线程是非分离状态,意味着原有的线程会等待所创建的线程结束。只有在pthread_join()函数返回后,才能释放创建的线程占用的系统资源,也才能视作该线程终止。
若线程运行结束且无其他线程阻塞等待,则该线程处于分离状态,此时系统资源将立即被释放。应该根据自己的需要,选择适当的分离状态。
相关API如下:
#include <pthread.h> int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
功能:设置线程分离状态
参数:
attr
:已初始化的线程属性detachstate
: 分离状态
PTHREAD_CREATE_DETACHED(分离线程)
PTHREAD_CREATE_JOINABLE(非分离线程)
返回值:
成功:0
失败:非0
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
功能:获取线程分离状态
参数:
attr
:已初始化的线程属性detachstate: 分离状态
PTHREAD_CREATE_DETACHED(分离线程)
PTHREAD _CREATE_JOINABLE(非分离线程)
返回值:
成功:0
失败:非0
注意:
스레드를 분리된 스레드로 설정하면 이때 스레드의 실행 속도가 매우 빠르다고 가정하면 종료 후 pthread_create가 반환되기 전에 종료될 가능성이 높으며 스레드 번호와 시스템 리소스는 다른 스레드로 넘겨집니다. , create를 호출하면 잘못된 스레드 번호가 얻어지므로 일부 동기화 조치를 취해야 합니다. 생성된 스레드에서 pthread_cond_timedwait 함수를 호출하고 스레드가 잠시 기다리도록 한 다음 pthread_create 함수가 반환될 때까지 충분한 시간을 남겨두십시오. 스레드 프로그래밍에서 일반적으로 사용되는 방법을 설정합니다. wait()와 같은 함수는 전체 프로세스를 절전 모드로 전환하고 스레드 동기화 문제를 해결하지 못하므로 사용하지 마십시오.
위 내용은 Linux 스레드는 어떻게 생성되나요?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

AI Hentai Generator
AI Hentai를 무료로 생성하십시오.

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











DeepSeek은 웹 버전과 공식 웹 사이트의 두 가지 액세스 방법을 제공하는 강력한 지능형 검색 및 분석 도구입니다. 웹 버전은 편리하고 효율적이며 설치없이 사용할 수 있습니다. 개인이든 회사 사용자이든, DeepSeek를 통해 대규모 데이터를 쉽게 얻고 분석하여 업무 효율성을 향상시키고 의사 결정을 지원하며 혁신을 촉진 할 수 있습니다.

Docker 컨테이너를 사용하여 사전 컴파일 된 패키지 (Windows 사용자의 경우)를 사용하여 소스 (숙련 된 개발자)를 컴파일하는 것을 포함하여 DeepSeek를 설치하는 방법에는 여러 가지가 있습니다. 공식 문서는 신중하게 문서를 작성하고 불필요한 문제를 피하기 위해 완전히 준비합니다.

Bitget은 스팟 거래, 계약 거래 및 파생 상품을 포함한 다양한 거래 서비스를 제공하는 Cryptocurrency 교환입니다. 2018 년에 설립 된이 교환은 싱가포르에 본사를두고 있으며 사용자에게 안전하고 안정적인 거래 플랫폼을 제공하기 위해 노력하고 있습니다. Bitget은 BTC/USDT, ETH/USDT 및 XRP/USDT를 포함한 다양한 거래 쌍을 제공합니다. 또한 Exchange는 보안 및 유동성으로 유명하며 프리미엄 주문 유형, 레버리지 거래 및 24/7 고객 지원과 같은 다양한 기능을 제공합니다.

세계 최고의 디지털 자산 거래소 인 Ouyi Okx는 이제 안전하고 편리한 거래 경험을 제공하기 위해 공식 설치 패키지를 시작했습니다. OUYI의 OKX 설치 패키지는 브라우저를 통해 액세스 할 필요가 없습니다. 설치 프로세스는 간단하고 이해하기 쉽습니다. 사용자는 최신 버전의 설치 패키지를 다운로드하고 설치를 단계별로 완료하면됩니다.

Gate.io는 사용자가 설치 패키지를 다운로드하여 장치에 설치하여 사용할 수있는 인기있는 cryptocurrency 교환입니다. 설치 패키지를 얻는 단계는 다음과 같습니다. Gate.io의 공식 웹 사이트를 방문하고 "다운로드"를 클릭하고 해당 운영 체제 (Windows, Mac 또는 Linux)를 선택하고 컴퓨터에 설치 패키지를 다운로드하십시오. 설치 중에 항 바이러스 소프트웨어 또는 방화벽을 일시적으로 비활성화하여 원활한 설치를 보장하는 것이 좋습니다. 완료 후 사용자는 GATE.IO 계정을 만들려면 사용을 시작해야합니다.

OKX라고도하는 Ouyi는 세계 최고의 암호 화폐 거래 플랫폼입니다. 이 기사는 OUYI의 공식 설치 패키지 용 다운로드 포털을 제공하여 사용자가 다른 장치에 OUYI 클라이언트를 설치할 수 있도록합니다. 이 설치 패키지는 Windows, Mac, Android 및 iOS 시스템을 지원합니다. 설치가 완료되면 사용자는 OUYI 계정에 등록하거나 로그인하고 암호 화폐 거래를 시작하며 플랫폼에서 제공하는 기타 서비스를 즐길 수 있습니다.

Gate.io는 광범위한 토큰 선택, 낮은 거래 수수료 및 사용자 친화적 인 인터페이스로 유명한 호평을받는 암호 화폐 거래 플랫폼입니다. Gate.io는 고급 보안 기능과 우수한 고객 서비스를 통해 트레이더에게 신뢰할 수 있고 편리한 암호 화폐 거래 환경을 제공합니다. Gate.io에 가입하려면 제공된 링크를 클릭하여 공식 등록 설치 패키지를 다운로드하여 Cryptocurrency 거래 여정을 시작하십시오.

이 튜토리얼은 기존 Apache 서버와 함께 Ubuntu 시스템에 Nginx 및 Phpmyadmin을 설치하고 구성하는 것을 안내합니다. 우리는 Nginx 설정, Apache와의 잠재적 포트 충돌 해결, Mariadb 설치를 다루겠습니다.
