网络编程 多线程

LINUX

基础知识

1、线程的概念

上下文切换 : 运行程序前需要将相应进程信息读入内存,如果运行进程A后需要紧接着运行程序B,就应该讲进程A相关信息移出内存,并读入进程B相关信息。

为了保持多进程的优点,同时在一定程度上克服其缺点,人们引入了线程(Thread).这是为了将进程的各种劣势降到最低限度而设计的一种“轻量级进程”。线程相比于进程的优点:
1.线程的创建和上下文切换比进程的创建和上下文切换更快。
2.线程间数据交换时无需特殊技术。

线程和进程的差异
  • 每个进程的内存空间都由保存全局变量的“数据区”向malloc等函数的动态分配提供空间的堆(Heap)函数运行时使用的栈(Stack)构成。每个进程都拥有这种独立空间。
  • 通过只分离栈(函数运行时使用)区域,可以获得如下优势:
    1.上下文切换时不需要切换数据区和堆。
    2.可以利用数据区和堆交换数据。
    线程为了保持多条代码执行流而隔开了栈区域。
  • 多个线程将共享数据区和堆。为了保持这种结构,线程将在进程内创建并运行。
    进程:在操作系统构成单独执行流的单位。
    线程:在进程构成单独执行流的单位。

2、线程创建及运行

  • 线程创建
int pthread_create(
pthread_t * restrict thread,const pthread_attr_t *restrict attr,
void*(*start_routine)(void *),void *restrict arg
);

成功时返回0,失败时返回其他值。
参数解释:
1、thread: 保存新创建线程ID的变量地址值。线程与进程相同,也需要用于区分不同线程的ID。
2、attr:用于传递线程属性的参数,传递NULL时,创建默认属性的线程。
3、start_routine:相当于线程main函数的、在单独执行流中执行的函数地址值(函数指针)。
4、arg:通过第三个参数传递调用函数时包含传递参数信息的变量地址值。

实例:

#include <stdio.h>
#include <pthread.h>
#include <unistcl.h>
void * thread_main(void *arg);

int main(int argc,char * argv[])
{
    pthread_t t_id;//存放新线程id
    int thread_param = 5;

    if(pthread_create(&t_id,NULL,thread_main,(void*)&thread_param) != 0)
    {
        puts("pthread_create() error!");
        return -1;
    }
    sleep(10);
    fputs("end of main");
    return 0;
}
void * thread_main(void *arg)
{
    int i;
    int cnt = *((int *)arg);//传入的参数是(void *)&thread_param
    for(i = 0;i < cnt;i++)
    {
        sleept(1);
        puts("running main");
    }
    return NULL;
}
  • 控制线程的执行流

int pthread_join(pthread_t thread,void ** status);
成功时返回0,失败时返回其他值。
参数解释:
1、thread: 该参数值ID的线程终止后才会从该函数返回。
2、status: 保存线程的main函数返回值的指针变量地址值。

调用该函数的进程(或线程)将进入等待状态,直到第一个参数为ID线程终止为止。

3、线程同步

任何内存空间,只要被同时访问,都可能发生为问题。P296 解释产生问题原因

互斥量(Mutex)【重点】

互斥量表示不允许多个线程同时访问。主要用于解决线程同步访问的问题。在同一个线程中,为了防止死锁,系统不允许连续两次对Mutex加锁。

信号量(Semaphore)【重点】

信号量可以分为:

  1. 二进制信号量(binary semaphore):只允许信号量取0或1值,其同时只能被一个线程获取。
  2. 整型信号量(integer semaphore):信号量取值是整数,它可以被多个线程同时获得,直到信号量的值变为0。
  3. 记录型信号量(record semaphore):每个信号量s除一个整数值value(计数)外,还有一个等待队列List,其中是阻塞在该信号量的各个线程的标识。当信号量被释放一个,值被加一后,系统自动从等待队列中唤醒一个等待中的线程,让其获得信号量,同时信号量再减一。

互斥量和信号量的区别

  1. 互斥量用于线程的互斥,信号线用于线程的同步。
    这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
    互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
    同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
  2. 互斥量值只能为0/1,信号量值可以为非负整数。
    也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。
  3. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

4、线程的销毁

Linux线程并不是在首次调用的线程main函数返回时自动销毁。

  • pthread_join函数不仅会等待线程终止,还会引导线程销毁。但线程终止前,调用该函数的线程将进入阻塞状态。
  • 调用pthread_detach

int pthread_detach(pthread_t thread);
成功时返回0,失败时返回其他值。
参数解释:
thread:终止的同时需要销毁的线程ID

调用上述函数不会引起线程终止或进入阻塞状态,可以通过该函数引导销毁线程创建的内存空间。
调用该函数后不能再针对相应线程调用pthread_join函数。

Windows

基础知识

1、内核对象(Kernel Objects)

  • 操作系统创建的资源(Resource)有很多种,如进程、线程、文件及信号量、互斥量等。它们都是由Windows操作系统创建并管理的资源。
  • 操作系统为了记录相关信息的方式管理各种资源,在其内部生成数据块(亦可视为结构体变量)。
  • 每种资源需要维护的信息不同,所以每种资源拥有的数据块格式也有差异。这类数据块称为”内核对象“。
  • 内核对象归操作系统所有
    内核对象的创建、管理、销毁时机的决定等工作均由操作系统完成。
    内核对象就是为了管理线程、文件等资源而由操作系统创建的数据块,其创建者和拥有者均为操作系统。

2、线程创建

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES IpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_STRART_ROUTINE IpStartAddress,
LPVOID IpParameter,
DWORD dwCreationFlags,
LPDWORD IpThread
);

成功时返回线程句柄(用于区分内核对象的整数型句柄),失败时返回NULL。
参数解释:
1.IpThreadAttributes 线程安全相关信息,使用默认设置时传递NULL
2.dwStartSie 要分配给线程的栈大小,传递0时生成默认大小的栈
3.IpStartAddress 传递线程的main函数信息
4.IpParameter 调用main函数时传递的参数信息
5.dwCreationFlags 用于指定线程创建后的行为,传递0时,线程创建后立即进入可执行状态
6.IpThreadId 用于保存线程ID的变量地址

 ####PS:句柄、内核对象和ID间的关系
> 线程也属于操作系统管理的资源,因此会伴随着内核对象的创建,并为了引用内核对象而返回**句柄**。
可以通过句柄区分内核对象,通过内核对象可以区分线程。线程句柄成为分区线程的工具。
**句柄的整数值在不同的进程中可能出现重复,但线程ID在跨进程范围内不会出现重复。**

 ####创建“使用线程安全标准C函数”的线程【一般使用此函数创建线程】
> uintptr_t _beginthreadex(
void * security,//NULL
unsigned stack_size,//0
unsigned (* start_address)(void *),
void * arglist,
unsigned initflag,
unsigned * thraddr
);




####3.内核对象的两种状态
- 资源类型不同,内核对象也含有不同的信息。应用程序实现过程中需要特别关注的信息被赋予某种“状态”(State)。
- 线程内核对象中需要重点关注线程是否已终止,所以终止状态又称“signaled状态”,未终止状态成为“non-signaled”状态。
- 进程或线程终止时,相应的内核对象会变为signaled状态。
> DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
成功时返回事件信息,失败时返回WAIT_FAILED。(进入signaled状态返回WAIT_OBJECT_0,超时返回WAIT_TIMEOUT)
参数解释:
1.hHandle 查看状态的内核对象句柄
2.dwMilliseconds 以1/1000秒为单位指定超时,传递INFINITE时函数不会返回,知道内核对象变成signaled状态

>**该函数由于发生事件(变为signaled状态)返回时,有时会把相应内核对象再次改为non-signaled状态。这种可以再次进入non-signaled状态的内核对象成为“auto-reset”模式的内核对象,而不会自动跳转到non-signaled状态的内核对象成为“manual-reset模式”的内核对象。**

####4.用户模式(User mode)和内核模式(Kernal mode)
- Windows操作系统的运行方式(程序运行方式)是“双模式操作”(Dual-mode Operation),这意味着Windows在运行过程中存在如下两种模式
1、用户模式:运行应用程序的基本模式,禁止访问物理设备,而且会限制访问的内存区域
2、内核模式:操作系统运行时的模式,不仅不会限制访问的内存区域,而且访问的硬件设备也不会受限
内核是操作系统的核心模块,可以简单定义为如下形式
1、用户模式:应用程序的运行模式
2、内核模式:操作系统的运行模式
Windows操作系统不会一直停留在用户模式,而是在用户模式和内核模式之间切换。
- 用户模式->内核模式->用户模式
从用户模式切换到内核模式是为了创建资源,从内核模式再次切换到用户模式时为了执行应用程序的剩余部分。
#####基于CRITICAL_SECTION的同步(用户模式的同步方法)
#####内核模式的同步方法
######(1)、基于互斥量(Mutual Exclusion)对象的同步
> 创建:

HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES IpMutexAttributes,BOOL bInitialOwner,LPCTSTR IpName);

成功时返回创建的互斥量对象句柄,失败时返回NULL
参数解释:
1、IpMutexAttributes 传递安全相关的配置信息,使用个默认安全设置时可以传递NULL
2、bInitialOwner 如果为TRUE,则创建出的互斥量对象属于调用该函数的线程,同时进入non-signaled状态;如果为false,则创建出的互斥量对象不属于任何线程,此时状态为signaled。
3、IpName 用于命名互斥量对象。传入NULL时创建无名的互斥量对象。
 销毁:
BOOL CloseHandle(HANDLE hObject);
成功时返回TRUE,失败时返回FALSE

- 获取和释放互斥量:
获取为WaitForSingleObject函数,释放为ReleaseMutex(HANDLE hMutex);
WaitForSingleObject(hMutex,INFINITE);
...
ReleaseMutex(hMutex);

######(2)、基于信号量对象的同步
>创建:
HANDLE CreateSemaphore(
LPSECURITY _ATTRIBUTES IpSemaphoereAttributes,LONG lInitialCount,
LONG lMaximumCount,LPCTSTR IpName);
成功时返回创建的信号量对象的句柄,失败时返回NULL。

WaitForSingleObject(hSemaphore,INFINITE);
//临界区开始
...
//临界区结束
ReleaseSemaphore(hSemaphore,1,NULL);

######(3)、基于事件对象的同步
- 事件同步对象方法创建对象时,可以在自动以non-signaled状态运行的auto-reset模式和与之相法的manual-reset模式中任选其一。
- 事件对象的主要特点是可以创建manual-reset模式的对象。
> HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES IpEventAttributes,BOOL bManualReset,
BOOL bInitialState,LPCTSTR IpName);
成功时返回创建的事件对象句柄,失败时返回NULL。
参数解释:
1、IpEventAttributes 安全配置相关参数,采用默认安全配置时传入NULL
2、bManualReset 传入TRUE时创建manual-reset模式的事件对象,传入FALSE时创建auto-reset模式的事件对象。
3、bInitialState 传入TRUE时创建signaled状态的事件对象,传入FALSE时创建non-signaled状态的事件对象。
4、IpName 用于命名事件对象。出入NULL时创建无名的事件对象。

BOOL ResetEvent(HANDLE hEvent);//to the non-signaled
BOOL SetEvent(HANDLE hEvent);//to the signaled
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,378评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,356评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,702评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,259评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,263评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,036评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,349评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,979评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,469评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,938评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,059评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,703评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,257评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,262评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,501评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,792评论 2 345

推荐阅读更多精彩内容