线程介绍
Thread
- 是操作系统能够进行运算调度的最小单位。
- 它被包含在进程中,是进程中的实际运作单位。
- 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
线程的内容
栈
- 我们从主线程的入口main函数,不不断进行函数调用,每次调用的时候,会把所有的参数和返回地址压入到栈中
PC(程序计数器)
- PC指向当前的指令,而这些指令是放在内存中的。也就是说,每个线程都有一串自己的指针,指向自己所在的内存。
TLS(线程本地存储)
- 每个进程都有自己独立的内存,线程拥有的自己独立的内存就是TLS,用来储存线程独有的数据。
多线程的好处
创建一个新线程花费时间少(结束一个线程花费的时间也少)
两个线程的切换花费时间少
同一进程内的线程共享文件和内存,因此他们之间相互通信无序调用内核
线程间通信的方式
全局变量
通过全局变量进行通信,就需要对该变量添加volatile关键字
volatile:每次从内存中去读这个值,而不是因编译器优化从缓存的地方读取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <stdio.h> #include <windows.h> volatile int signalNum = 0; DWORD WINAPI threadFuncA(LPVOID lpParamter) { Sleep(2000); if(signalNum == 0) printf("signalNum is not changed"); else printf("signalNum has changed"); return 0; } DWORD WINAPI threadFuncB(LPVOID lpParamter) { signalNum = 2; return 0; } int main() { HANDLE threadA = CreateThread(NULL,0,threadFuncA,NULL,0,NULL); HANDLE threadB = CreateThread(NULL,0,threadFuncB,NULL,0,NULL); WaitForSingleObject(threadA,INFINITE); CloseHandle(threadA); CloseHandle(threadB); return 0; } |
DWORD:
- 在windows.h里面
- #define DWORD unsigned long;
- Double Word,每个word为2个字节的长度,DWORD就是双字节,也就是4个字节的长度,每个字节是8位,也就是说DWORD是32位。
- 经常被用来保存地址或存放指针
WINAPI:
- #define WINAPI _stdcall;
- 函数调用的时候,函数入栈的方式是从最右边先入栈
- 然后_stdcall还规定,被调用函数负责自己使用的栈的回收,也就是说调用者函数只负责压入自己栈中,这就是手工请栈。
CreateThread(成功返回新线程的句柄,失败返回NULL)(参数如下):
- 线程内核对象的安全属性,NULL表示默认值
- 线程栈空间大小,0表示默认大小,也就是1MB
- 新线程所执行的线程函数地址(多个线程可以使用同一个函数地址)
- 传给线程函数的参数
- 指定额外的标志来控制线程的创建,0表示线程创建之后立即就可以进行调度;CREATE_SUSPENDED表示线程创建后暂停运行,这样它就无法调度,直到调用了ResumeThread();
- 将返回线程的ID号,NULL表示不需要返回该线程ID
WaitForSingleObject:
- 指明一个内核对象的句柄
- 等待时间
INFINITE: - #define INFINITE 0xFFFFFFFF;
- 表示该数值的最大值
互斥量
互斥量的功能和临界区很相似。
区别是,Mutex所花费的时间比Critical Section多的多,但是Mutex是核心对象,可以跨进程使用。而且等待一个被锁住的Mutex可以设定为TIMEOUT,不会像Critical Section那样无法得知临界区域的情况,而一直死等。
win32函数:
- CreateMutex()-创建互斥量
- OpenMutex()-打开互斥量
- ReleaseMutex()-释放互斥量
互斥量的拥有权并非是创建它的那个线程,而是最后对此互斥量进行操作并且尚未进行ReleaseMutex()操作的线程。
如果一个用于Mutex的线程在返回之前没有进行ReleaseMutex(),那么这个Mutex就被舍弃了,但是当其他线程等待这个Mutex时,仍能返回,并得到一个WAIT_ABANDONED_0返回值。
能够知道一个Mutex被舍弃是Mutex特有的。
12345678910111213141516171819202122232425262728293031#include <stdio.h>#include <Windows.h>//通过信号量实现线程间的同步,初始化为没有加索的状态HANDLE mutex = CreateMutex(NULL,FALSE,NULL);DWORD WINAPI threadFuncA(LPVOID lpParamter){//对互斥量加速,如果已经加锁了则等待其解锁,等待时间为永久-INFINITEWaitForSingleObject(mutex,INFINITE);printf("threadFuncA lock mutex,please wait...\n");Sleep(5000);//互斥量解锁ReleaseMutex(mutex);return 0;}DWORD WINAPI threadFuncB(LPVOID lpParamter){Sleep(2000);WaitForSingleObject(mutex,INFINITE);printf("this is threadFuncB");ReleaseMutex(mutex);return 0;}int main(){HANDLE threadA = CreateThread(NULL,0,threadFuncA,NULL,0,NULL);HANDLE threadB = CreateThread(NULL,0,threadFuncB,NULL,0,NULL);WaitForSingleObject(threadB,INFINITE);CloseHandle(threadA);CloseHandle(threadB);return 0;}
信号量
信号量是解决生产者/消费者问题的关键要素。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <stdio.h> #include <Windows.h> HANDLE signalSemaphore = CreateSemaphore(NULL,1,1,NULL); DWORD WINAPI threadFuncA(LPVOID lpParamter) { Sleep(2000); WaitForSingleObject(signalSemaphore,INFINITE); printf("threadFuncA 使用了一个信号量\n"); Sleep(2000); printf("threadFuncA 释放了一个信号量\n"); ReleaseSemaphore(signalSemaphore,1,NULL); return 0; } DWORD WINAPI threadFuncB(LPVOID lpParamter) { WaitForSingleObject(signalSemaphore,INFINITE); printf("threadFuncB 使用了一个信号量\n"); Sleep(5000); printf("threadFuncB 释放了一个信号量\n"); ReleaseSemaphore(signalSemaphore,1,NULL); return 0; } int main() { HANDLE threadA = CreateThread(NULL,0,threadFuncA,NULL,0,NULL); HANDLE threadB = CreateThread(NULL,0,threadFuncB,NULL,0,NULL); WaitForSingleObject(threadA,INFINITE); CloseHandle(threadA); CloseHandle(threadB); return 0; } |
事件
用事件来同步线程是最具弹性的。
一个事件具有两种状态:激发状态和未激发状态。也称为有信号状态和无信号状态。
事件种类:
- 手动重置事件
被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态。直到程序把它设置为未激发状态。 - 自动重置事件
被设置为激发状态后,会唤醒一个等待中的线程,然后自动恢复为未激发状态。
所以,用自动重置事件来同步两个线程比较理想。
1234567891011121314151617181920212223242526#include <stdio.h>#include <Windows.h>HANDLE threadEvent = CreateEvent(NULL,TRUE,FALSE,NULL);DWORD WINAPI threadFuncA(LPVOID lpParamter){printf("threadFuncA 等待事件有信号\n");WaitForSingleObject(threadEvent,INFINITE);printf("threadFuncA 等待事件信号成功,并把事件自动设置为无信号状态");return 0;}DWORD WINAPI threadFuncB(LPVOID lpParamter){Sleep(5000);SetEvent(threadEvent);printf("已经给事件信号了");return 0;}int main(){HANDLE threadA = CreateThread(NULL,0,threadFuncA,NULL,0,NULL);HANDLE threadB = CreateThread(NULL,0,threadFuncB,NULL,0,NULL);WaitForSingleObject(threadA,INFINITE);CloseHandle(threadA);CLoseHandle(threadB);return 0;}
临界区
CRITICAL_SECTION是最快的。其他的内核锁如互斥量事件,每进一次内核,都需要上千个CPU周期。
使用临界区时,最好不要长时间锁住一份资源。这里所说的长时间是相对的,视不同程序来定。
缺点:
CRITICAL_SECTION不是一个核心对象,无法获知进入临界区的线程是生是死,如果进入临界区的线程挂了,没有释放临界资源,系统无法获知,而且没有办法释放该临界资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#include <stdio.h> #include <afxmt.h> #include <Windows.h> CCriticalSection cs; DWORD WINAPI threadFuncA(LPVOID lpParamter) { Sleep(2000); cs.Lock(); printf("criticalsection unlock success"); cs.Unlock(); return 0; } DWORD WINAPI threadFuncB(LPVOID lpParamter) { cs.Lock(); printf("threadFuncB 锁住了临界区"); Sleep(5000); cs.Unlock(); printf("threadFucnB 解锁了临界区"); return 0; } int main() { HANDLE threadA = CreateThread(NULL,0,threadFuncA,NULL,0,NULL); HANDLE threadB = CreateThread(NULL,0,threadFuncB,NULL,0,NULL); return 0; } |
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Reading 2021 《如何控制自己的情绪》08/02
- ♥ lldb调试04/21
- ♥ C++11_第四篇12/08
- ♥ 行为型:解释器模式09/25
- ♥ Git应用记述一06/06
- ♥ Linux 高性能服务器编程:网络基础编程二11/28