1. 程式人生 > >多執行緒程式設計(一)——共享資料同步&執行緒鎖

多執行緒程式設計(一)——共享資料同步&執行緒鎖

多執行緒共享變數會涉及到資料的安全問題。

驗證測試程式如下,兩個執行緒共同對一個(非全域性)變數操作,根據初始列印可知地址一樣,非全域性。

#include <stdio.h>
#include <pthread.h>
static pthread_t thread_a_id;
static pthread_t thread_b_id;
void *printA(void *pA)
{
        printf("%p!\n",pA);
        int *p = pA;
        while(1)
        {
                *p = 2;
                if(5 == *p)
                {
                        printf("=============\n");
                }
        }
        return NULL;
}
void *changeA(void *pA)
{
        printf("%p!\n",pA);
        int *p = pA;
        while(1){
                *p = 5;
        }      
        return NULL;
}
int main()
{

        int a;
        a = 1;
        int iRet = 0;
        iRet = pthread_create(&thread_a_id,NULL,printA,&a);
        if(iRet != 0)
        {
                printf("create failed!\n");
        }

        iRet = pthread_create(&thread_b_id,NULL,changeA,&a);
        if(iRet != 0)
        {
                printf("create failed!\n");
        }

        while(1)
{
        sleep(5);
}


}

因為while(1)運轉過快,難以人工分辨,所以設定遇到變數值不符合預期時列印即可。

執行結果:

=============
=============
=============
=============
=============
=============

證明執行緒間資料被互相篡改,不安全。

執行緒b就不列印了,肯定也會遇到a被改成2的情況。(為了演示更全面,下邊的例子會把a和b執行緒改對等)

解決思路

我目前所用的,通常是資料加鎖和變數不共享兩種思路,但是資料不共享很侷限,不能滿足很多需求,比如多執行緒需要共同維護一個使用者雜湊表。

所以就需要給資料加鎖。

手動加(實際上簡陋、不可用的爛)鎖:

#include <stdio.h>
#include <pthread.h>
static pthread_t thread_a_id;
static pthread_t thread_b_id;
static int i = 0;

void *printA(void *pA)
{
        printf("%p!\n",pA);
        int *p = pA;
        while(1)
        {
                if(i == 0)
                {
                        i = 1;//lock
                        *p = 2;
                        if(5 == *p)
                        {
                                printf("=============\n");
                        }
                        i = 0;//unlock

                }
        }
        return NULL;
}
void *changeA(void *pA)
{
        printf("%p!\n",pA);
        int *p = pA;
        while(1){
                if(i == 0)
                {
                        i = 1;//lock
                        *p = 5;
                        if(2 == *p)
                        {
                                printf("*************\n");
                        }
                        i = 0;//unlock
                }
        }
        return NULL;
}
int main()
{
        int a;
        a = 1;
        int iRet = 0;
        iRet = pthread_create(&thread_a_id,NULL,printA,&a);
        if(iRet != 0)
        {
                printf("create failed!\n");
        }

        iRet = pthread_create(&thread_b_id,NULL,changeA,&a);
        if(iRet != 0)
        {
                printf("create failed!\n");
        }

        while(1)
        {
                sleep(5);
        }


}

結果列印:
=============
*************
=============
*************
*************
*************
=============
*************
*************
*************
*************
=============
*************
=============
=============
*************
*************
=============
=============
*************
*************
=============
=============
*************
=============
=============
一塌糊塗,不管用。

這個程式證明了C語言的語句非原子操作,是可以分解成更多更細的指令的,不是一個真正意義上的原語,所以程式連死鎖的機會都沒有,各種亂跑。
PS:在所謂的lock操作i = 1;後加一個i等於0的判斷,就可以知道i也是發生變化的。

        while(1)
        {
                if(i == 0)
                {
                        i = 1;//lock
                        if(0 == i)
                        {
                                printf("..............\n");
                        }
                        *p = 2;
                        if(5 == *p)
                        {
                                printf("=============\n");
                        }
                        i = 0;//unlock

                }
        }

最終方法

手動設的普通變數解決不了安全性問題,怎麼辦?其實,pthread庫裡是自帶方法了。應該是執行緒鎖吧,有指定的變數pthread_mutex_t和指定操作pthread_mutex_lock()、pthread_mutex_unlock()

#include <stdio.h>
#include <pthread.h>
static pthread_t thread_a_id;
static pthread_t thread_b_id;
static int i = 0;
static pthread_mutex_t sMux;
void *printA(void *pA)
{
        printf("%p!\n",pA);
        int *p = pA;
        while(1)
        {
                pthread_mutex_lock(&(sMux));//lock
                *p = 2;
                if(5 == *p)
                {
                        printf("=============\n");
                }

                pthread_mutex_unlock(&(sMux));//unlock
        }
        return NULL;
}
void *changeA(void *pA)
{
        printf("%p!\n",pA);
        int *p = pA;
        while(1){
                pthread_mutex_lock(&(sMux));//lock
                *p = 5;
                if(2 == *p)
                {
                        printf("*************\n");
                }

                pthread_mutex_unlock(&(sMux));//unlock
        }
        return NULL;
}
int main()
{
        int a;
        a = 1;
        int iRet = 0;
        iRet = pthread_create(&thread_a_id,NULL,printA,&a);
        if(iRet != 0)
        {
                printf("create failed!\n");
        }

        iRet = pthread_create(&thread_b_id,NULL,changeA,&a);
        if(iRet != 0)
        {
                printf("create failed!\n");
        }

        while(1)
        {
                sleep(5);
        }


}

執行結果
[[email protected] multi_thread]# ./a.out
0xbfa82be8!
0xbfa82be8!

不再列印多餘資訊,說明兩個執行緒互相不干擾,不再有安全性問題。

PS:一個執行緒鎖只能互斥保護一個臨界資源,這個臨界資源其實也不是固定的,全看lock在哪用,其實是一個鎖同時只能鎖一處資源。

很多不互斥使用的資源,如果用同一個鎖,肯定就尷尬了。這就好比你上鎖,把自己的鎖鎖到別人自行車上了,別人拿不了車。這是不想要的結果。

所以要給不同的臨界資源陪不同的鎖——多初始化一個鎖變數,用那個變數去加鎖解鎖就好了

測試程式碼:

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/syscall.h>
#define gettid() syscall(__NR_gettid)
pthread_t thread1;
pthread_t thread2;
pthread_mutex_t lock1;
pthread_mutex_t lock2;

void* pThreadFunc(void * pMutex)
{
//先鎖
        pthread_mutex_lock((pthread_mutex_t *)pMutex);
        printf("the thread's id is %lu\n",gettid());
//再睡
        printf("lock,sleep\n");
        sleep(10);
        printf("sleep over,unlock,exit!\n");

        pthread_mutex_unlock((pthread_mutex_t *)pMutex);
        return NULL;
}
int main(){
        int iRet = 0;
        void * status;
//初始化執行緒鎖
        pthread_mutex_init(&lock1,NULL);
//先多執行緒
        iRet = pthread_create(&thread1, NULL, pThreadFunc, &lock1); //建立服務執行緒
        if(iRet != 0)
        {
                return iRet;
        }
        iRet = pthread_create(&thread2, NULL, pThreadFunc, &lock2); //建立服務執行緒
        if(iRet != 0)
        {
                return iRet;
        }

        pthread_join(thread1,&status);
        pthread_join(thread2,&status);
}
結果:
# ./a.out
the thread's id is 9475
lock,sleep
the thread's id is 9476
lock,sleep
sleep over,unlock,exit!
sleep over,unlock,exit!
# ps -efL | grep ./a.out
root      9474  8507  9474  0    3 22:28 pts/1    00:00:00 ./a.out
root      9474  8507  9475  0    3 22:28 pts/1    00:00:00 ./a.out
root      9474  8507  9476  0    3 22:28 pts/1    00:00:00 ./a.out
root      9479  9447  9479  2    1 22:28 pts/2    00:00:00 grep

如果兩個執行緒傳同一個執行緒鎖lock1,就會順序執行:
# ./a.out
the thread's id is 9534
lock,sleep
sleep over,unlock,exit!
the thread's id is 9533
lock,sleep
sleep over,unlock,exit!
所以,只要區分好那把鎖鎖哪輛車就行了。

相關推薦

執行程式設計——共享資料同步&執行

多執行緒共享變數會涉及到資料的安全問題。 驗證測試程式如下,兩個執行緒共同對一個(非全域性)變數操作,根據初始列印可知地址一樣,非全域性。 #include <stdio.h> #include <pthread.h> static pthre

執行程式設計——寫一個簡單的死

(整個九月忙著找工作,好多收穫,好多遺憾,最終結局還可以接受,技術路還很遠,再接再厲!面去哪兒網時,寫慣了演算法的我突然讓寫了幾個多執行緒程式設計,有點矇蔽,最近好好整理一下) 死鎖發生的原因: 1、

Linux 執行程式設計

Linux 多執行緒程式設計 執行緒(Thread)已被許多作業系統所支援,包括Windows/NT ,Linux 以前的多執行緒其實是多程序,而現在意味著一個程序中有多個執行緒 使用多執行緒的原因(多執行緒的優點): 1.“節省”,啟動一個新的程序需要分配給它獨立的地

使用POSIX Threads進行執行程式設計——pthread基本知識

使用POSIX Threads進行多執行緒程式設計(一) ——pthread基本知識 說明: 本文預計翻譯三章,主要涉及pthread基本知識、互斥量(鎖)和條件變數,一是因為這已經能夠引導讀者入門,二是因為本人在工作之餘翻譯,實在時間捉急。

Linux執行程式設計

什麼是執行緒 先來舉一個我們生活中的例項,我們都使用過一個強大的軟體—迅雷。那你必然知道迅雷有一個邊下邊播的功能,我們在下載的時候還能同時進行觀看。這就是一個多執行緒例項。 執行緒是程序內部的執行分支。 開啟迅雷軟體—–向系統核心索要資源,啟動“迅雷”程

c++11執行程式設計資料共享和競爭條件

在多執行緒環境中,執行緒間的資料共享很簡單,但是在程式中這種簡單的資料共享可能會引起問題,其中一種便是競爭條件。什麼是競爭條件? 競賽條件是發生在多執行緒應用程式中的一種bug 當兩個或多個執行緒並行執行一組操作,訪問相同的記憶體位置,此時,它們中的一個或多個執行緒會修改記

c++11執行程式設計:建立執行的三種方法

c++11執行緒庫原始的c++標準僅支援單執行緒程式設計,新的c++標準(c++11或c++0x)於2011年釋出,引入了新的執行緒庫。 編譯器要求 Linux: gcc 4.8.1 (完全併發支援) Windows: Visual Studio 2012 and Min

C#執行程式設計程序與執行

一、 程序         簡單來說,程序是對資源的抽象,是資源的容器,在傳統作業系統中,程序是資源分配的基本單位,而且是執行的基本單位,程序支援併發執行,因為每個程序有獨立的資料,獨立的堆疊空間。一個程式想要併發執行,開多個程序即可。

Docker在Windows上執行NetCore系列使用命令控制檯執行.NetCore控制檯應用 Docker在Linux/Windows上執行NetCore文章系列

原文: Docker在Windows上執行NetCore系列(一)使用命令控制檯執行.NetCore控制檯應用   系列文章:https://www.cnblogs.com/alunchen/p/10121379.html 本篇文章作業系統資訊     

併發程式設計執行基礎、執行之間的共享與協作

一、基礎概念 1.1 CPU核心數、執行緒數 **兩者的關係:**cpu的核心數與執行緒數是1:1的關係,例如一個8核的cpu,支援8個執行緒同時執行。但在intel引入超執行緒技術以後,cpu與執行緒數的關係就變成了1:2。此外在開發過程中並沒感覺到執行緒的限制,那是因為cpu

執行程式設計——面試題,每個執行只打印種字元,執行協同順序列印n次字串求大神的其他實現方案

(這個是歡聚時刻(YY)的筆試題,在筆試的時候沒有寫出來,後來自己寫了出來,希望大神能給出更優秀的解決方案……ps:現在面試官總有面試時問筆試程式設計題思路的習慣,呵呵) 題目簡述: 輸入一個字串以

Python執行程序程式設計 就這麼簡單

""" <axiner>宣告:(錯了另刂扌丁我) (如若有誤,請記得指出喲,謝謝了!!!) """ 先來了解一個概念,GIL? GIL的全稱為Global Interpreter Lock, 全域性直譯器鎖。 Python程式碼的執行由Python 虛擬機器(也叫直譯器主

併發程式設計執行基礎

目錄 併發程式設計(一)多執行緒基礎 1、程序和執行緒的概念 2、為什麼要使用多執行緒 3、多執行緒使用的場景 4、多執行緒建立方式 4.1、繼承Thread類 4.2、實現Runable介面 4.3、匿名內部類 4.4、執行緒池(後面細說)

C++11併發程式設計——初始C++11執行

1 前言   C++11標準在標準庫中為多執行緒提供了元件,這意味著使用C++編寫與平臺無關的多執行緒程式成為可能,而C++程式的可移植性也得到了有力的保證。   在之前我們主要使用的多執行緒庫要麼

java執行系列:Thread、Runnable、Callable實現執行的區別

實現多執行緒 java實現多執行緒的方法有三種,分別是繼承thread類,實現runnable介面,實現callable介面(call方法有返回值) /** * 繼承Thread */ public class MyThread extends Thread{ int a = 0;

java執行-初探

啥是多執行緒?跟程序又是啥關係?   比方說:我去洗手,洗完手去吃飯。 程序(Processor) 洗手跟吃飯是兩個程序。 執行緒(Thread) 在洗手的程序裡,我同時聽歌,還唱歌。那這裡洗手是一個程序,聽歌跟唱歌是兩個執行緒。 在吃飯的程序裡,我同時聽歌,還

Linux學習之執行程式設計

言之者無罪,聞之者足以戒。 ——《詩序》 (二)、執行緒的基本控制 1、終止程序: 如果程序中的任意一個程序呼叫了exit、_exit、_Exit,那麼整個程序就會終止 普通的單個程序有以下3種退出方式,這樣不會終止程序: (1)從啟動例程中返回,返回值是執行緒的退

Linux學習之執行程式設計

言之者無罪,聞之者足以戒。 ——《詩序》 三、Linux執行緒的高階控制 1、一次性初始化 有些事需要且只能執行一次(比如互斥量初始化)。通常當初始化應用程式時,可以比較容易地將其放在main函式中。但當你寫一個庫函式時,就不能在main裡面初始化了,你可以用靜態初始化

Linux學習之執行程式設計

言之者無罪,聞之者足以戒。 ——《詩序》 4、執行緒私有屬性 應用程式設計中有必要提供一種變數,使得多個函式多個執行緒都可以訪問這個變數(看起來是個全域性變數),但是執行緒對這個變數的訪問都不會彼此產生影響(貌似不是全域性變數哦),但是你需要這樣的資料,比如errno。那

Java執行程式設計-1-執行安全和Synchronized概念

一、程序與執行緒的概念 (1)在傳統的作業系統中,程式並不能獨立執行,作為資源分配和獨立執行的基本單位都是程序。 在未配置 OS 的系統中,程式的執行方式