C++拾遺--多線程:C語言多線程的引入

                      C++拾遺--多線程:C語言多線程的引入

前言

    多線程是編程中的一個重要內容。多核時代使多線程成爲一種可能,顯然,一件事情多個人幹,效率一定會提升。下面來看下C語言中是如何使用多線程的。

正文

1.CreateThread

先來看一個實例

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <Windows.h>

DWORD WINAPI run(void *p)
{
	char *mess = (char*)p;
	printf("線程%d,彈窗\n", GetCurrentThreadId());
	char threadId[20];
	sprintf(threadId, "線程%d", GetCurrentThreadId());
	MessageBoxA(0, mess, threadId, 0);
	return 0;
}
int main(void)
{
	printf("******C語言多線程演示***by David***\n");
	char *mess[] = { "123", "456", "789" };
	HANDLE handles[3];
	for (int i = 0; i < sizeof(mess) / sizeof(*mess); i++)
	{
		handles[i] = CreateThread(NULL, 0, run, mess[i], 0, NULL);
	}
	WaitForMultipleObjects(3, handles, 1, INFINITE);
	return 0;
}
運行



異步彈出了三個窗口,並打印了各自的線程號。若是有沒看懂的地方,下面有詳細解釋:

1.handle是句柄,在windows中用句柄來標識對象。本質很簡單 typedef void * HANDLE;

2.CreateThread()用來創建線程。原型

HANDLE WINAPI CreateThread(
    LPSECURITY_ATTRIBUTES lpThreadAttributes,    //內核對象的安全屬性
    SIZE_T dwStackSize,                          //線程棧大小
    LPTHREAD_START_ROUTINE lpStartAddress,       //線程函數地址
    LPVOID lpParameter,                          //傳給線程函數的參數
    DWORD dwCreationFlags,                       //控制位
    LPDWORD lpThreadId                           //獲取線程id
    );

參數解釋:

第一個參數是線程內核對象的安全屬性,一般傳入NULL表示使用默認設置。

第二個參數是線程棧空間的大小。傳入0表示使用默認大小(1MB)

第三個參數是新線程所執行的線程函數地址,多個線程可以使用同一個函數地址。

第四個參數是傳給線程函數的參數。typedef void * LPVOID

第五個參數是用來控制線程的創建,0表示創建後立即執行。

第六個參數是傳出參數,用來獲得線程的id。顯然,傳入NULL,表示調用者並不想知道線程的id。

返回值:線程句柄

3.線程函數的聲明。#define WINAPI __stdcall  (vs2013)typedef unsigned long DWORD。其中,__stdcall是指C/CPP中函數的調用方式。主要有兩點:1.實參從右向左入棧。2.調用者負責清空參數棧。

4.線程等待函數

DWORD WINAPI WaitForMultipleObjects(
    DWORD nCount,             //內核對象的個數
    CONST HANDLE *lpHandles,  //句柄數組的地址
    BOOL bWaitAll,            //是否等待所有
    DWORD dwMilliseconds      //等待的最大時間,單位毫秒,INFINITE表示無限等待
    );

函數功能:讓線程進入等待轉態,直到條件觸發。內核對象在運行期間處於未觸發的狀態,直到執行結束。

5.線程函數類型是

typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(LPVOID lpThreadParameter);

更簡潔的是

typedef unsigned long (__stdcall *pfun)(void*);


2._beginthreadex

函數原型

uintptr_t __cdecl _beginthreadex(
        void*                    _Security,
        unsigned                 _StackSize,
        _beginthreadex_proc_type _StartAddress,
        void*                    _ArgList,
        unsigned                 _InitFlag,
        unsigned*                _ThrdAddr
        );

它的參數類型和CreateThread基本一致,只是線程函數類型稍有不同。線程函數類型是

typedef unsigned (__stdcall *_beginthreadex_proc_type)(void*);


3._beginthread

CreateThread的調用過於複雜,下面我們玩兒個簡單的,我們用多個線程打印 Hello World

#include <stdio.h>
#include <stdlib.h>
#include <process.h>
#include <Windows.h>
void hello(void *p)
{
	printf("線程%d, say Hello World\n", GetCurrentThreadId());
}
int main()
{
	printf("******C語言多線程演示***by David***\n");
	HANDLE handles[5];
	for (int i = 0; i < 5; i++)
	{
		handles[i] = _beginthread(hello, 0, NULL);
	}
	WaitForMultipleObjects(5, handles, 1, INFINITE);
	getchar();
	return 0;
}
運行



_beginthread的原型

uintptr_t _beginthread(
        _beginthread_proc_type _StartAddress,    //線程函數的地址
        unsigned               _StackSize,       //線程棧的大小
        void*                  _ArgList          //線程函數的參數
        );

函數功能:使用指定線程函數創建線程,並返回線程句柄。

幾點解釋:

1.typedef unsigned int * uintptr_t;

2.typedef void(__cdecl *_beginthread_proc_type)(void*);  _beginthread_proc_type就是一函數指針類型,我們提供的線程函數應該如此設計:只有一個參數,類型爲void*,且返回值類型是void。


CreateThread和_beginthread的使用說明:

  1. 從函數參數可以看出,CreateThread用於對所創建的線程進行精細控制。在很多參數處於默認設置下,建議使用參數簡單的_beginthread。
  2. 兩者所需的線程函數類型不同。


總結

使用多線程,就要先寫好線程函數,然後調用相關函數創建線程即可。由於_beginthread傳參簡單,一般情況下,使用_beginthread創建多線程。


多線程編程面臨的問題

  1. 多個子線程對同一個全局變量進行操作,容易引起線程衝突,也就是子線程的互斥問題。
  2. 主線程與子線程之間的同步問題。
後續文章,會對這兩個問題的解決進行探討。




本專欄目錄

所有內容的目錄



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章