跟我学Linux编程-13-多线程编程-性能
关于Linux多线程编程

关于Linux多线程编程Linux线程分为两类,一是核心级支持线程,在核心级实现线程时,线程的实现依赖于内核,无论是在用户进程中的线程还是系统进程中的线程,他们的创建、撤消、切换都由内核实现。
核心只有单线程进程概念,而多线程进程由与应用程序连接的过程库实现。
另一类线程是用户级线程,在Linux众多的线程库中,大部分实现的是用户级线程。
系统创建线程的顺序如下:当一个线程启动后,它会自动创建一个线程即主线程(main thread)或者初始化线程(initial thread),然后就利用pthread_initialize()初始化系统管理线程并且启动线程机制。
Linux线程编程基础要创建一个多线程程序,必须加载pthread.h头文件。
要掌握多线程编程常用的几个函数:1、创建新线程函数:pthread_create()2、挂起当前线程函数:pthread_join()3、线程注册的清除处理函数:pthread_exit()4、取消一个线程函数:pthread_cancel()5、挂起当前线程,直到满足某种条件:pthread_cond_init多线程的同步1、互斥锁互斥锁用来保证一段时间内只有一个线程在执行一段代码。
当在同一内存空间运行多个线程时,为保证多个线程之间不相互破坏,要创建互斥量,如果一个线程已经锁定一个互斥量,第二个线程又试图去锁定这个互斥量,则第二个线程被挂起(不占用任何CPU资源),直到第一个线程解除对这个互斥量的锁定为止。
第二个线程将被唤醒并继续执行,同时锁定这个互斥量。
创建互斥量时,必须首先声明一个类型为pthread_mutex_t的变量,然后对其进行初始化,结构pthread_mutex_t为不公开的数据类型,其中包含一个系统分配的属性对象。
函数pthread_mutex_init用来生成一个互斥锁。
锁定一个互斥量时使用函数pthread_mutex_lock(),它尝试锁定一个互斥量,如果该互斥量已经被其它线程锁定,该函数就把调用自己的线程挂起,一旦该互斥量解锁,它将恢复运行并锁定该互斥量。
linux多线程编程详解教程(线程通过信号量实现通信代码)

linux多线程编程详解教程(线程通过信号量实现通信代码)线程按照其调度者可以分为⽤户级线程和核⼼级线程两种。
(1)⽤户级线程主要解决的是上下⽂切换的问题,它的调度算法和调度过程全部由⽤户⾃⾏选择决定,在运⾏时不需要特定的内核⽀持。
在这⾥,操作系统往往会提供⼀个⽤户空间的线程库,该线程库提供了线程的创建、调度、撤销等功能,⽽内核仍然仅对进程进⾏管理。
如果⼀个进程中的某⼀个线程调⽤了⼀个阻塞的系统调⽤,那么该进程包括该进程中的其他所有线程也同时被阻塞。
这种⽤户级线程的主要缺点是在⼀个进程中的多个线程的调度中⽆法发挥多处理器的优势。
(2)这种线程允许不同进程中的线程按照同⼀相对优先调度⽅法进⾏调度,这样就可以发挥多处理器的并发优势。
现在⼤多数系统都采⽤⽤户级线程与核⼼级线程并存的⽅法。
⼀个⽤户级线程可以对应⼀个或⼏个核⼼级线程,也就是“⼀对⼀”或“多对⼀”模型。
这样既可满⾜多处理机系统的需要,也可以最⼤限度地减少调度开销。
Linux的线程实现是在核外进⾏的,核内提供的是创建进程的接⼝do_fork()。
内核提供了两个系统调⽤clone()和fork(),最终都⽤不同的参数调⽤do_fork()核内API。
当然,要想实现线程,没有核⼼对多进程(其实是轻量级进程)共享数据段的⽀持是不⾏的,因此,do_fork()提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享⽂件系统信息)、CLONE_FILES(共享⽂件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。
当使⽤fork系统调⽤时,内核调⽤do_fork()不使⽤任何共享属性,进程拥有独⽴的运⾏环境,⽽使⽤pthread_create()来创建线程时,则最终设置了所有这些属性来调⽤__clone(),⽽这些参数⼜全部传给核内的do_fork(),从⽽创建的“进程”拥有共享的运⾏环境,只有栈是独⽴的,由__clone()传⼊。
linux多线程编程

Linux多线程编程小结1.Linux进程与线程Linux进程创建一个新线程时,线程将拥有自己的栈(因为线程有自己的局部变量),但与它的创建者共享全局变量、文件描述符、信号句柄和当前目录状态。
Linux通过fork创建子进程与创建线程之间是有区别的:fork创建出该进程的一份拷贝,这个新进程拥有自己的变量和自己的PID,它的时间调度是独立的,它的执行几乎完全独立于父进程。
进程可以看成一个资源的基本单位,而线程是程序调度的基本单位,一个进程内部的线程之间共享进程获得的时间片。
2._REENTRANT宏在一个多线程程序里,默认情况下,只有一个errno变量供所有的线程共享。
在一个线程准备获取刚才的错误代码时,该变量很容易被另一个线程中的函数调用所改变。
类似的问题还存在于fputs之类的函数中,这些函数通常用一个单独的全局性区域来缓存输出数据。
为解决这个问题,需要使用可重入的例程。
可重入代码可以被多次调用而仍然工作正常。
编写的多线程程序,通过定义宏_REENTRANT来告诉编译器我们需要可重入功能,这个宏的定义必须出现于程序中的任何#include语句之前。
_REENTRANT为我们做三件事情,并且做的非常优雅:(1)它会对部分函数重新定义它们的可安全重入的版本,这些函数名字一般不会发生改变,只是会在函数名后面添加_r字符串,如函数名gethostbyname变成gethostbyname_r。
(2)stdio.h中原来以宏的形式实现的一些函数将变成可安全重入函数。
(3)在error.h中定义的变量error现在将成为一个函数调用,它能够以一种安全的多线程方式来获取真正的errno的值。
3.线程的基本函数大多数pthread_XXX系列的函数在失败时,并未遵循UNIX函数的惯例返回-1,这种情况在UNIX函数中属于一少部分。
如果调用成功,则返回值是0,如果失败则返回错误代码。
1.线程创建:#include <pthread.h>int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);参数说明:thread:指向pthread_create类型的指针,用于引用新创建的线程。
Linux多线程编程

POSIX pthreads库
线程的创建 POSIX pthreads 线程库中提供的创建线程的函数是 pthread_create(),函数原型是:
int pthread_create(pthread_t * thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg);
第一个参数是pthread_t 类型的指针,这个指针指向用来存 放建的线程可以拥有的属性 第三个参数是一函数指针,此函数指针指向线程的实现函数 第四个参数arg是void *类型的,此参数指向实际线程处理函 数执行的时候所需要的参数
POSIX pthreads库(续)
第一个参数th是需要等待的线程的标志 如果thread_return不为空,那么thread_return指向th返回 的值
调用pthread_join()函数的目的是释放相关内存资源
POSIX pthreads库(续)
线程的分离 pthread_join() 函数虽然可以等待被创建的线程的结束,且 可以回收被创建的线程的相关内存资源,但是这个函数的主要 缺点是要挂起调用pthread_join() 的线程。 POSIX线程库提供了一个函数pthread_detach(),使得子线程 本身自己有自我回收内存资源的能力。此函数的原型是:
pthread_t pthread_self(void);
线程的撤销
一个线程可以通过向另个线程发送“请求”来结束另一个线程 的执行。pthread_cancel()函数可以完成这个功能。 接收撤销请求的线程并不是“随便”就结束了自己的执行,它 可以忽略、推迟、或者立即响应别的线程发来的结束线程执行 的请求,这取决于线程设置。 这里的“推迟”是指线程执行到撤销点(cancellation point) 的时候,才执行线程的撤销操作。 POSIX pthreads库中关于撤销操作的函数有:
Linux下的多线程编程上机学习材料

Linux下的多线程编程上机准备吴雨龙目录1.我们为什么要练习多线程?(多线程的好处) (3)2.Linux下的多线程编程必备知识 (3)1)VIM的使用 (3)a)指令模式下常用命令 (3)b)插入模式 (4)2)编译器gcc的使用必知 (4)3)调试器gdb的使用 (4)4)Linux下的C编程常用头文件介绍 (4)3.Linux多线程编程相关函数介绍 (5)1)主要线程函数介绍 (5)a)线程创建函数pthread_create() (5)b)线程终止函数pthread_exit() (6)c)控制线程同步的函数pthread_exit() (6)2)信号量 (7)a)sem_init() (7)b)sem_wait()和sem_trywait() (7)c)sem_post() (8)d)sem_getvalue() (8)e)sem_destroy() (8)3)互斥锁 (8)a)互斥锁初始化:pthread_mutex_init() (8)b)互斥锁上锁:pthread_mutex_lock() (9)c)互斥锁解锁:pthread_mutex_unlock() (9)d)撤销互斥锁:pthread_mutex_destroy() (9)4.Linux下的多线程编程引例 (9)1)引例一:基本的线程程序——理解Linux多线程程序的基本结构 (9)a)源代码 (9)b)程序解析 (10)2)引例二:两个线程的同时执行——最简单的线程协调模型 (11)a)源代码 (11)b)程序解析 (12)3)引例三:信号量的使用——借助信号量体现多线程分工合作的思想 (12)a)源代码 (12)b)程序解析 (14)4)引例四:使用互斥量——将引例三改为用互斥量实现 (14)a)源代码 (14)b)程序解析 (16)5)引例五:一个简单的真正多个线程的程序——真正的多线程 (16)a)源代码 (16)b)程序解析 (17)c)思考一下吧... .. (18)1.我们为什么要练习多线程?(多线程的好处)1) 提高应用程序响应。
多线程编程之:Linux线程编程

多线程编程之:Linux线程编程9.2 线程编程9.2.1 线程基本编程这里要讲的线程相关操作都是用户空间中的线程的操作。
在Linux中,普通pthread线程库是一套通用的线程库,是由POSIX提出的,因此具有很好的可移植性。
(1)函数解释。
创建线程事实上就是确定调用该线程函数的入口点,这里通常用法的函数是pthread_create()。
在线程创建以后,就开头运行相关的线程函数,在该函数运行完之后,该线程也就退出了,这也是线程退出一种办法。
另一种退出线程的办法是用法函数pthread_exit(),这是线程的主动行为。
这里要注重的是,在用法线程函数时,不能任意用法exit()退出函数举行出错处理,因为exit()的作用是使调用进程终止,往往一个进程包含多个线程,因此,在用法exit()之后,该进程中的全部线程都终止了。
因此,在线程中就可以用法pthread_exit()来代替进程中的exit()。
因为一个进程中的多个线程是分享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放。
正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。
pthread_join()可以用于将当前线程挂起来等待线程的结束。
这个函数是一个线程堵塞的函数,调用它的函数将向来等待到被等待的线程结束为止,当函数返回时,被等待线程的资源就被收回。
前面已提到线程调用pthread_exit()函数主动终止自身线程。
但是在无数线程应用中,常常会碰到在别的线程中要终止另一个线程的执行的问题。
此时调用pthread_cancel()函数实现这种功能,但在被取消的线程的内部需要调用pthread_setcancel()函数和pthread_setcanceltype()函数设置自己的取消状态,例如被取消的线第1页共9页。
Linux下c语言多线程编程

Linux下c语⾔多线程编程引⾔ 线程(thread)技术早在60年代就被提出,但真正应⽤多线程到中去,是在80年代中期,solaris是这⽅⾯的佼佼者。
传统的Unix也⽀持线程的概念,但是在⼀个进程(process)中只允许有⼀个线程,这样多线程就意味着多进程。
现在,多 为什么有了进程的概念后,还要再引⼊线程呢?使⽤多线程到底有哪些好处?什么的系统应该选⽤多线程?我们⾸先必须回答这些问题。
使⽤多线程的理由之⼀是和进程相⽐,它是⼀种⾮常"节俭"的多任务操作⽅式。
我们知道,在Linux系统下,启动⼀个新的进程必须分配给它独⽴的地址空间,建⽴众多的数据表来维护它的代码段、堆栈段和数据段,这是⼀种"昂贵"的多任务⼯作⽅式。
⽽运⾏于⼀个进程中的多个线程,它们彼此之间使⽤相同的地址空间,共享⼤部分数据,启动⼀个线程所花费的空间远远⼩于启动⼀个进程所花费的空间,⽽且,线程间彼此切换所需的时间也远远⼩于进程间切换所需要的时间。
使⽤多线程的理由之⼆是线程间⽅便的机制。
对不同进程来说,它们具有独⽴的数据空间,要进⾏数据的传递只能通过通信的⽅式进⾏,这种⽅式不仅费时,⽽且很不⽅便。
线程则不然,由于同⼀进程下的线程之间共享数据空间,所以⼀个线程的数据可以直接为其它线程所⽤,这不仅快捷,⽽且⽅便。
当然,数据的共享也带来其他⼀些问题,有的变量不能同时被两个线程所修改,有的⼦程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地⽅。
除了以上所说的优点外,不和进程⽐较,多线程程序作为⼀种多任务、并发的⼯作⽅式,当然有以下的优点: 1) 提⾼应⽤程序响应。
这对图形界⾯的程序尤其有意义,当⼀个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应、、菜单的操作,⽽使⽤多线程技术,将耗时长的操作(time consuming)置于⼀个新的线程,可以避免这种尴尬的情况。
Linux线程基础讲解学习

L i n u x线程基础Linux系统下的多线程编程1.1 引言线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者。
传统的Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程。
现在,多线程技术已经被许多操作系统所支持,包括Windows/NT,当然,也包括Linux。
为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。
1.2使用多线程的理由一是和进程相比,它是一种非常"节俭"的多任务操作方式。
进程是系统中程序执行和资源分配的基本单位。
我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这就导致了进程在进行切换等操作起到了现场保护作用, 这是一种"昂贵"的多任务工作方式。
但是为了进一步减少处理机的空转时间支持多处理器和减少上下文切换开销,进程演化中出现了另外一个概念,这就是线程,也被人称为轻量级的进程。
它是一个进程内的基本调度单位。
线程是在共享的内存空间中并发的多道执行路径,它们共享一个进程的资源,比如文件描述符和信号处理等。
因此,大大减少了上下文切换的开销。
而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
二是线程间方便的通信机制。
对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。
线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
}
void main_thread_task()
{
int i;
int cnt = 0;
while (cnt++ < 100)
{
for (i = 0; i < COUNT_CNT; i++)
{
count_t *c = g_counts + i;
pthread_mutex_lock(&c->m_mutex);
{
count_t *c = g_counts + i;
c->m_cnt[0] = 0;
c->m_cnt[1] = 0;
c->m_cnt[2] = 0;
pthread_mutex_init(&c->m_mutex, NULL);//线程锁一定要初始化
}
//创建两个不同的线程
pthread_create(&thr, NULL, thread_task1, NULL);
for (i = 0; i < COUNT_CNT; i++)
{
count_t *c = g_counts + i;
//当cnt成员累加到CYC_CNT值时,不再对其进行处理
if (c->m_cnt[0] >= CYC_CNT)
{
if (c->m_cnt[0] == CYC_CNT) cnt++;//标识已处理完一个
int pthread_mutex_trylock( pthread_mutex_t *mutex);
可以看到,这两个函数的原型非常想像,功能也比较类似,都是尝试对给定的锁对像进行加锁,如果成功,则线程获得该锁,并返回0;不同点在于当锁已被其他线程占有的情况下,pthread_mutex_lock会阻塞,直至锁被其它线程释放并且本线程获得锁;如pthread_mutex_trylock则不然,如果锁当前已被其他线程占有,则立刻返回失败(非0值),我们的程序可判读返回值并进行下一步的处理(如处理另一个任务),避免线程被阻塞,从而提高了线程并发度,提升程序性能。我们接下来看例子:
如果程序每次都运行结果都是输出两行thread_task1的信息,最后再输出thread_task2的信息,则结论非常有说服力了,时差根本不是问题,thread_task1与效率就是比thread_task2的高。
那我们来编译和执行程序吧:
gcc thread4.c -o thread4 –lpthread
今天我们重点介绍pthread_mutex_trylock的使用,并通过实例的来展现其用法,探究其提高程序效果的原理。
首先,我们来看pthread_lock与pthread_mutex_trylock的函数原型:
int pthread_mutex_lock(pthread_mutex_t *mutex);
return NULL;
}
void *thread_task2(void *arg)
{
int i;
int cnt = 0;
//保证每个count_t对像的m_cnt[1]值都被累加到指定值CYC_CNT
while (cnt < COUNT_CNT)
{
//依次处理每一个count_t对像
for (i = 0; i < COUNT_CNT; i++)
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
typedef struct
{
int m_cnt[3];
pthread_mutex_t m_mutex;
} count_t;
#define COUNT_CNT20
#define CYC_CNT10000
}
为了展示pthread_mutex_lock与pthread_mutex_trylock的区别,我们写了两个线程执行函数:thread_task1与thread_task2,这两个线程要完成的工作一样,都是处理COUNT_CNT个count_t对像,将每个count_t的计数器m_cnt累加至CYC_CNT,任务完成后,在屏幕上打印一行信息提示我们使命完成,然后线程退出。不同点在于,thread_task1加锁使用的是pthead_mutex_trylock而thread_task2为pthread_mutex_lock。为了制造冲突,我们在主程序中执行了main_thread_task()循环,该循环中,依次对每个count_t对像进行加锁解锁,并在加锁解锁之间使用usleep()函数模拟了一个耗时操作(注意,这里是为了模块耗时操作,在实际编程程中,加锁与解锁之间使用sleep一类的操作,就是自己找麻烦,这在文章的一开头就说过),加大锁冲突的概率。
通过理论分析,我们预期thread_task1会先结束,因此运行程序的结果总是thread_task1先输出,thread_task2后输出。但认真的同学可能认为这个结果不能说明什么原因,因为thread_task1先创建,而thread_task2后创建,这还有个时间差呢,说不定输出信息的先后就是因为这个时间差导致的,与线程效率没有半分钱关系。考虑到这点,我们上两行程序的后面,再用thread_task1创建了第3个线程,代码如下:
c->m_cnt[1]++;
pthread_mutex_unlock(&c->m_mutex);
}
"task 2 done.\n");
return NULL;
}
void count_proc(count_t *c)
{
//模拟一个比较消耗时间的处理操作
c->m_cnt[2] = c->m_cnt[0] * c->m_cnt[1];
count_proc(c);
pthread_mutex_unlock(&c->m_mutex);
}
sleep(10);
}
}
int main(int argc, char *argv[])
{
int i;
pthread_t thr;
//初始化变量
for (i = 0; i < COUNT_CNT; i++)
多线程编程-性能
在上一章节中,我们介绍了线程互斥锁的使用。通过互斥锁,使得每个线程只能串行地运行临界区代码,从而有效地避免了多线程冲突。串行的代码运行方式削弱了多线程并发运行的特性,因此线程锁也潜在地降低了程序性能,如何杜绝线程冲突,又尽可能不影响程序效率,是我们每一个线程从员需要认真考虑的事情。
减少线程性能下降的方法有如下几点:
//创建两个不同的线程
pthread_create(&thr, NULL, thread_task1, NULL);
pthread_create(&thr, NULL, thread_task2, NULL);
//创建另一个thread_task1线程做对比
pthread_create(&thr, NULL, thread_task1, NULL);
{
count_t *c = g_counts + i;
//当cnt成员累加到CYC_CNT值时,不再对其进行处理
if (c->m_cnt[1] >= CYC_CNT)
{
if (c->m_cnt[1] == CYC_CNT) cnt++;//标识已处理完一个
continue;
}
//加锁
pthread_mutex_lock(&c->m_mutex);
continue;
}
//尝试加锁
if (pthread_mutex_trylock(&c->m_mutex))
{
//获取锁失败,跳过处理下一个对像
continue;
}
c->m_cnt[0]++;
pthread_mutex_unlock(&c->m_mutex);
}
usleep(10);
}
printf("task 1 done.\n");
1尽可能减小互斥锁的颗粒,使串行代码的比例减小从而提高效率,这在上一章节末已提过。
2加锁解锁之间的代码,运行时间尽可能减少,避免有sleep或死循环一类的超时等待。
3对不同的冲突资源使用不同的线程锁,避免不相关的线线之间因锁反而产生关联。
4使用pthread_mutex_trylock代替pthread_lock在检测和处理锁冲突,实现单线程对多个对像的非阻塞处理。
在主线程中,我们为thread_task1与thread_task2各创建了一个线程,代码如下:
pthread_create(&thr, NULL, thread_task1, NULL);
pthread_create(&thr, NULL, thread_task2, NULL);
如果两个线程的效率相近,则其标识结束的打印信息应该在同一时间输出(当然,总会有先后,但间隔足够小,多次运行可能这次是你先输出,再另一次是我先输出),如果效率差别较大,则输出结束信息会有一个较为固定的先后顺序,总是效率高的在前输出,效率低的在后输出。
count_t g_counts[COUNT_CNT];
void *thread_task1(void *arg)
{
int i;
int cnt = 0;