先上图:
分析下这个问题的考察点,主要考察点有二个:
1.主线程创建子线程并传入一个指向变量地址的指针作参数,由于线程启动须要花费一定的时间,所以在子线程根据这个指针访问并保存数据前,主线程应等待子线程保存完毕后才能改动该参数并启动下一个线程。这涉及到主线程与子线程之间的同步。
2.子线程之间会互斥的改动和输出全局变量。要求全局变量的输出必须递增。这涉及到各子线程间的互斥。
直接上代码:
//经典线程同步互斥问题
#include <stdio.h>
#include <process.h>
#include <windows.h>
long g_nNum; //全局资源
unsigned int __stdcall Fun(void *pPM); //线程函数
const int THREAD_NUM = 10; //子线程个数
int main()
{
g_nNum = 0;
HANDLE handle[THREAD_NUM];
int i = 0;
while (i < THREAD_NUM)
{
handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
i++;//等子线程接收到参数时主线程可能改变了这个i的值
}
//保证子线程已全部运行结束
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
return 0;
}
unsigned int __stdcall Fun(void *pPM)
{
//由于创建线程是要一定的开销的,所以新线程并不能第一时间执行到这来
int nThreadNum = *(int *)pPM; //子线程获取参数
Sleep(50);//some work should to do
g_nNum++; //处理全局资源
Sleep(0);//some work should to do
printf("线程编号为%d 全局资源值为%d\n", nThreadNum, g_nNum);
return 0;
}
这是一个既不互斥也不同步的程序,现在要求如下:
1.子线程输出的线程序号不能重复
2.全局变量的输出必须递增
下面这段代码是我通过原理进行修改的,实现了上面的要求,但是并没有实现同步,可以加深理解:
#include <stdio.h>
#include <process.h>
#include <windows.h>
#include <iostream>
using namespace std;
volatile long g_nNum; //全局资源
unsigned int __stdcall Fun(void *pPM);
const int THREAD_NUM = 10; //子线程个数
int main()
{
g_nNum = 0;
HANDLE handle[THREAD_NUM];
int cas = 10;
while (cas--){
int i = 0;
while (i < THREAD_NUM)
{
Sleep(1);//***延迟下一个子进程创建,就可以保证前一子进程完成保存和输出操作
handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
i++;//***等子线程接收到参数时主线程可能改变了这个i的值
}
//保证子线程已全部运行结束
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
cout << "case: " << cas << "全局: " << g_nNum << endl;
}
return 0;
}
unsigned int __stdcall Fun(void *pPM)
{
//由于创建线程是要一定的开销的,所以新线程并不能第一时间执行到这来
long nThreadNum = *(long *)pPM; //子线程获取参数
Sleep(5);
g_nNum++; //处理全局资源
//InterlockedIncrement((LPLONG)&g_nNum);//这里采用原子操作但似乎不影响结果
Sleep(0);//some work should to do
printf("线程编号为%d 全局资源值为%d\n", nThreadNum, g_nNum);//第一个元素是i,第二个元素是全局变量
return 0;
}
实现多线程同步的方法有:关键段(临界区),互斥量,信号量,事件等
更详细的可以去看:http://blog.csdn.net/morewindows/article/details/7442333
值得注意的是:
- 当在同一进程中的多线程同步时,临界区是效率最最高,基本不需要什么开销。而内核对象由于要进行用户态和内核态的切换,开销较大,但是内核对象由于可以命名,因此它们同时可以用于进程间的同步。另外,值得一提的是,信号量可以设置允许访问资源的线程或进程个数,而不仅仅是只允许单个线程或进程访问资源。
- 信号量的使用特点使其更适用于对Socket(套接字)程序中线程的同步。例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这时可以为每一个用户对服务器的页面请求设置一个线程,而页面则是待保护的共享资源,通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。
- 虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
- 尽量避免用户态与内核态的切换
同步方法 | 效率 | 功能 | 属于哪种模式 |
---|---|---|---|
信号量 | 低 | 控制线程访问数量 | 内核 |
临界区 | 高 | 进程内线程同步 | 用户 |
互斥量 | 低 | 进程间资源安全共享,“遗弃”特性 | 内核 |
事件 | 低 | 实现进程间的线程同步 | 内核 |