selenium自動化操作

在前面爬蟲的相關介紹中,我們介紹了如何抓取靜態頁面信息。但是,在實際的網頁瀏覽過程中,我們可能會經常碰到各種需要進行交互的操作,典型的如輸入信息、點擊按鈕之類。

對於這種場景,之前的靜態頁面操作方式已經不能滿足需求,這時我們需要藉助新的工具,比如selenium或者PhantomJS。由於後者已經停止維護,推薦使用前者。

1.selenium是什麼

如果大家有做過web的自動化測試,相信對於selenium一定不陌生,測試人員經常使用它來進行自動化測試。

selenium最初是一個自動化web測試工具,通過代碼模擬人使用瀏覽器自動訪問目標站點並操作,比如跳轉、輸入、點擊、下拉等。

由於開發者的不斷完善,目前的功能越來越強大,基本支持各種交互操作。同時,不止支持有界瀏覽,還支持無界瀏覽。

2.selenium有什麼用

正如我們前面講過的,爬蟲的本質過程就是模擬人對瀏覽器的操作過程。在爬蟲中使用,selenium主要是為了解決requests無法執行javaScript代碼的問題。

本質上是通過驅動瀏覽器,完全模擬瀏覽的操作,比如跳轉、輸入、點擊、下拉等…進而進行跳轉。

當然,它也有壞處,主要的壞處就是它的速度比較慢。原因是selenium在操作時,需要等瀏覽器對頁面的元素渲染好之後才能操作。而我們知道,由於頁面渲染過程需要加載各種資源,響應速度與網絡帶寬要求非常高。通常情況,它比靜態頁面的響應至少慢一個數量級。

3.如何使用selenium

在知道selenium是什麼以及有什麼用之後,我們來具體學習如何操作這個工具。

由於selenium本質是模擬人對瀏覽器進行輸入、選擇、點擊等操作,因此對於目標標籤的定位非常重要。

在前面的章節,我們對於如何定位目標標籤有過詳細的介紹,這裏就不再贅述。selenium對於目標標籤定位的方式本質與靜態的頁面一樣,只不過因為使用的包不同,因此在beautifulSoup中使用的是find和findAll,而在selenium中使用的接口有所變化。

下圖中已對各種定位方式進行了歸納總結:

在找到目標標籤之後,最重要的是對這些標籤進行模擬操作。Selenium庫下webdriver模塊常用方法主要分類兩類:一類是模擬瀏覽器、鍵盤操作,另一類是模擬鼠標操作。

3.1模擬瀏覽器、鍵盤操作

模擬瀏覽器、鍵盤操作的方法歸納如下:

3.2 模擬鼠標操作

模擬鼠標操作的方法歸納如下:

4.示例演示

在介紹了selenium相關的使用方法之後,我們來進行操作。這裏介紹兩個例子:第一個例子是模擬百度搜索,第二個例子是模擬自動登錄網易163郵箱發送郵件。

在開始示例之前我們需要安裝selinum插件包,同時還需要下載webdriver。在我們的示例中,需要是使用chrome瀏覽器進行操作,需要使用瀏覽器的驅動webdriver。

關於下載什麼版本的webdriver,可以在瀏覽器的屬性中查看,並在http://npm.taobao.org/mirrors/chromedriver/下載對應的版本就好,如果是其他的瀏覽器,則需要下載對應的瀏覽器驅動程序,這種不再做進一步介紹。

4.1模擬百度搜索

第一步還是需要打開目標的地址“w w w.baidu.com”,分析目標網頁中目標元素的特點,如下圖所示:

通過分析,我們很容易就找到搜索框的id為kw,點擊按鈕的id為su,餘下的就是使用方法進行模擬。

實現的代碼如下所示:

from selenium import webdriver

#get 方法 打開指定網址
driver=webdriver.Chrome()
driver.get('http://www.baidu.com')

#選擇網頁元素
element_keyword = driver.find_element_by_id('kw')

#輸入字符
element_keyword.send_keys('python 爬蟲')

#找到搜索按鈕
element_search_button = driver.find_element_by_id('su')
element_search_button.click()

driver.close()

4.2模擬自動登錄網易163郵箱發送郵件

操作過程跟上面相似,第一步也是分析目標網頁http://mail.163.com。

如下圖所示:

找到了目標標籤然後就是模擬登錄。

實現代碼如下:

# coding:UTF-8
import time
from selenium.webdriver.common.keys import Keys
from selenium import webdriver

driver = webdriver.Chrome()
driver.implicitly_wait(5)  
driver.get('http://mail.163.com/')
driver.switch_to_frame(driver.find_element_by_tag_name('iframe'))
# driver.switch_to_frame('x-URS-iframe')  
driver.find_element_by_name('email').clear()
driver.find_element_by_name('email').send_keys('郵箱地址')
driver.find_element_by_name('password').send_keys('郵箱密碼', Keys.ENTER)
# 跳轉頁面時,強制等待6s
time.sleep(6)   
# 點擊寫信按鈕
driver.find_element_by_xpath("//div[@id='dvNavTop']/ul/li[2]/span[2]").click()   
time.sleep(2)
# 收件人
driver.find_element_by_class_name('nui-editableAddr-ipt').send_keys('目標的郵箱')  
driver.find_element_by_xpath("//input[@class='nui-ipt-input' and @type='text' and @maxlength='256']").send_keys(
    u'測試')   # 主題
xpath = driver.find_element_by_xpath("//div[@class='APP-editor-edtr']/iframe")
# 文本內容在iframe中
driver.switch_to_frame(xpath)   
driver.find_element_by_xpath("//body[@class='nui-scroll' and @contenteditable='true']").send_keys(u'這是一個自動化測試郵件')
# 發送按鈕在iframe外,所以需要跳出
driver.switch_to_default_content()
# 發送
driver.find_element_by_xpath("//div[@class='nui-toolbar-item']/div/span[2]").click()  
driver.close()

當然,在實際過程中,可能往往還有驗證碼的驗證。因為現在的驗證碼難度越來越大,形式也多種多樣,使用常規的方法很難解決,必須藉助機器學習或者第三方接口進行實現,將在後續單獨列一個章節進行介紹如何破解驗證碼。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

※教你寫出一流的銷售文案?

11.DRF-權限

Django rest framework源碼分析(2)—-權限

添加權限

(1)API/utils文件夾下新建premission.py文件,代碼如下:

  • message是當沒有權限時,提示的信息
# utils/permission.py

class SVIPPremission(object):
    message = "必須是SVIP才能訪問"
    def has_permission(self,request,view):
        if request.user.user_type != 3:
            return False
        return True


class MyPremission(object):
    def has_permission(self,request,view):
        if request.user.user_type == 3:
            return False
        return True

(2)settings.py全局配置權限

#全局
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',],
    "DEFAULT_PERMISSION_CLASSES":['API.utils.permission.SVIPPremission'],
}

(3)views.py添加權限

  • 默認所有的業務都需要SVIP權限才能訪問
  • OrderView類裏面沒寫表示使用全局配置的SVIPPremission
  • UserInfoView類,因為是普通用戶和VIP用戶可以訪問,不使用全局的,要想局部使用的話,裏面就寫上自己的權限類
  • permission_classes = [MyPremission,] #局部使用權限方法
from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from API import models
from rest_framework.request import Request
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from API.utils.permission import SVIPPremission,MyPremission

ORDER_DICT = {
    1:{
        'name':'apple',
        'price':15
    },
    2:{
        'name':'dog',
        'price':100
    }
}

def md5(user):
    import hashlib
    import time
    #當前時間,相當於生成一個隨機的字符串
    ctime = str(time.time())
    m = hashlib.md5(bytes(user,encoding='utf-8'))
    m.update(bytes(ctime,encoding='utf-8'))
    return m.hexdigest()

class AuthView(APIView):
    '''用於用戶登錄驗證'''

    authentication_classes = []      #裏面為空,代表不需要認證
    permission_classes = []          #不裏面為空,代表不需要權限
    def post(self,request,*args,**kwargs):
        ret = {'code':1000,'msg':None}
        try:
            user = request._request.POST.get('username')
            pwd = request._request.POST.get('password')
            obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
            if not obj:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            #為用戶創建token
            token = md5(user)
            #存在就更新,不存在就創建
            models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
            ret['token'] = token
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '請求異常'
        return JsonResponse(ret)


class OrderView(APIView):
    '''
    訂單相關業務(只有SVIP用戶才能看)
    '''

    def get(self,request,*args,**kwargs):
        self.dispatch
        #request.user
        #request.auth
        ret = {'code':1000,'msg':None,'data':None}
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            pass
        return JsonResponse(ret)


class UserInfoView(APIView):
    '''
       訂單相關業務(普通用戶和VIP用戶可以看)
       '''
    permission_classes = [MyPremission,]    #不用全局的權限配置的話,這裏就要寫自己的局部權限
    def get(self,request,*args,**kwargs):

        print(request.user)
        return HttpResponse('用戶信息')
# urls.py
from django.contrib import admin
from django.urls import path
from API.views import AuthView,OrderView,UserInfoView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/auth/',AuthView.as_view()),
    path('api/v1/order/',OrderView.as_view()),
    path('api/v1/info/',UserInfoView.as_view()),
]
# API/utils/auth/py
# auth.py

from rest_framework import exceptions
from API import models
from rest_framework.authentication import BaseAuthentication


class Authentication(BaseAuthentication):
    '''用於用戶登錄驗證'''
    def authenticate(self,request):
        token = request._request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用戶認證失敗')
        #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
        return (token_obj.user,token_obj)

    def authenticate_header(self, request):
        pass

(4)測試

普通用戶訪問OrderView,提示沒有權限

普通用戶訪問UserInfoView,可以返回信息

權限源碼流程

(1)dispatch

def dispatch(self, request, *args, **kwargs):
    """
    `.dispatch()` is pretty much the same as Django's regular dispatch,
    but with extra hooks for startup, finalize, and exception handling.
    """
    self.args = args
    self.kwargs = kwargs
    #對原始request進行加工,豐富了一些功能
    #Request(
    #     request,
    #     parsers=self.get_parsers(),
    #     authenticators=self.get_authenticators(),
    #     negotiator=self.get_content_negotiator(),
    #     parser_context=parser_context
    # )
    #request(原始request,[BasicAuthentications對象,])
    #獲取原生request,request._request
    #獲取認證類的對象,request.authticators
    #1.封裝request
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers  # deprecate?

    try:
        #2.認證
        self.initial(request, *args, **kwargs)

        # Get the appropriate handler method
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed

        response = handler(request, *args, **kwargs)

    except Exception as exc:
        response = self.handle_exception(exc)

    self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response

(2)initial

def initial(self, request, *args, **kwargs):
    """
    Runs anything that needs to occur prior to calling the method handler.
    """
    self.format_kwarg = self.get_format_suffix(**kwargs)

    # Perform content negotiation and store the accepted info on the request
    neg = self.perform_content_negotiation(request)
    request.accepted_renderer, request.accepted_media_type = neg

    # Determine the API version, if versioning is in use.
    version, scheme = self.determine_version(request, *args, **kwargs)
    request.version, request.versioning_scheme = version, scheme

    # Ensure that the incoming request is permitted
    #4.實現認證
    self.perform_authentication(request)
    #5.權限判斷
    self.check_permissions(request)
    self.check_throttles(request)

(3)check_permissions

裏面有個has_permission這個就是我們自己寫的權限判斷

def check_permissions(self, request):
    """
    Check if the request should be permitted.
    Raises an appropriate exception if the request is not permitted.
    """
    #[權限類的對象列表]
    for permission in self.get_permissions():
        if not permission.has_permission(request, self):
            self.permission_denied(
                request, message=getattr(permission, 'message', None)
            )

(4)get_permissions

def get_permissions(self):
    """
    Instantiates and returns the list of permissions that this view requires.
    """
    return [permission() for permission in self.permission_classes]

(5)permission_classes

所以settings全局配置就如下

#全局
REST_FRAMEWORK = {
   "DEFAULT_PERMISSION_CLASSES":['API.utils.permission.SVIPPremission'],
}

內置權限

django-rest-framework內置權限BasePermission

默認是沒有限制權限

class BasePermission(object):
    """
    A base class from which all permission classes should inherit.
    """

    def has_permission(self, request, view):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True

    def has_object_permission(self, request, view, obj):
        """
        Return `True` if permission is granted, `False` otherwise.
        """
        return True

我們自己寫的權限類,應該去繼承BasePermission,修改之前寫的permission.py文件

# utils/permission.py

from rest_framework.permissions import BasePermission

class SVIPPremission(BasePermission):
    message = "必須是SVIP才能訪問"
    def has_permission(self,request,view):
        if request.user.user_type != 3:
            return False
        return True


class MyPremission(BasePermission):
    def has_permission(self,request,view):
        if request.user.user_type == 3:
            return False
        return True

總結:

(1)使用

  • 自己寫的權限類:1.必須繼承BasePermission類; 2.必須實現:has_permission方法

(2)返回值

  • True 有權訪問
  • False 無權訪問

(3)局部

  • permission_classes = [MyPremission,]

(4)全局

REST_FRAMEWORK = {
   #權限
    "DEFAULT_PERMISSION_CLASSES":['API.utils.permission.SVIPPremission'],
}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※幫你省時又省力,新北清潔一流服務好口碑

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

使用DragonFly進行智能鏡像分發

Dragonfly 是一款基於 P2P 的智能鏡像和文件分發工具。它旨在提高文件傳輸的效率和速率,最大限度地利用網絡帶寬,尤其是在分發大量數據時,例如應用分發、緩存分發、日誌分發和鏡像分發。

在阿里巴巴,Dragonfly 每個月會被調用 20 億次,分發的數據量高達 3.4PB。Dragonfly 已成為阿里巴巴基礎設施中的重要一環。

儘管容器技術大部分時候簡化了運維工作,但是它也帶來了一些挑戰:例如鏡像分發的效率問題,尤其是必須在多個主機上複製鏡像分發時。

Dragonfly 在這種場景下能夠完美支持 Docker 和 PouchContainer。它也兼容其他格式的容器。相比原生方式,它能將容器分發速度提高 57 倍,並讓 Registry 網絡出口流量降低 99.5%。
Dragonfly 能讓所有類型的文件、鏡像或數據分發變得簡單而經濟。

更多請通過官方文檔了解。

純Docker部署

這裏採用多機部署,方案如下:

應用 IP
服務端 172.17.100.120
客戶端 172.17.100.121
客戶端 172.17.100.122

部署服務端

以docker方式部署,命令如下:

docker run -d --name supernode --restart=always -p 8001:8001 -p 8002:8002 \
    dragonflyoss/supernode:0.3.0 -Dsupernode.advertiseIp=172.17.100.120

部署客戶端

準備配置文件
Dragonfly 的配置文件默認位於 /etc/dragonfly 目錄下,使用容器部署客戶端時,需要將配置文件掛載到容器內。
為客戶端配置 Dragonfly Supernode 地址:

cat <<EOD > /etc/dragonfly/dfget.yml
nodes:
    - 172.17.100.120
EOD

啟動客戶端
docker run -d --name dfclient --restart=always -p 65001:65001 \
    -v /etc/dragonfly:/etc/dragonfly \
    dragonflyoss/dfclient:v0.3.0 --registry https://index.docker.io

registry是倉庫地址,這裏使用的官方倉庫

修改Docker Daemon配置

我們需要修改 Dragonfly 客戶端機器(dfclient0, dfclient1)上 Docker Daemon 配置,通過 mirror 方式來使用 Dragonfly 進行鏡像的拉取。
在配置文件 /etc/docker/daemon.json 中添加或更新如下配置項:

{
  "registry-mirrors": ["http://127.0.0.1:65001"]
}

然後重啟Docker

systemctl restart docker

拉取鏡像測試

在任意一台客戶端上進行測試,比如:

docker pull tomcat

驗證

查看client端的日誌,如果輸出如下,則表示是通過DragonFly來傳輸的。

docker exec dfclient grep 'downloading piece' /root/.small-dragonfly/logs/dfclient.log
2020-06-20 15:56:49.813 INFO sign:146-1592668602.159 : downloading piece:{"taskID":"4d977359836129ce2eec4b8418a7042c47db547a239e2a577ddc787ee177289c","superNode":"172.17.100.120","dstCid":"cdnnode:172.17.100.120~4d977359836129ce2eec4b8418a7042c47db547a239e2a577ddc787ee177289c","range":"0-4194303","result":503,"status":701,"pieceSize":4194304,"pieceNum":0}

如果需要查看鏡像是否通過其他 peer 節點來完成傳輸,可以執行以下命令:

docker exec dfclient grep 'downloading piece' /root/.small-dragonfly/logs/dfclient.log | grep -v cdnnode

如果以上命令沒有輸出結果,則說明鏡像沒有通過其他peer節點完成傳輸,否則說明通過其他peer節點完成傳輸。

在Kubernetes中部署

服務端以Deployment的形式部署

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: supernode
  name: supernode
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: supernode
  template:
    metadata:
      labels:
        app: supernode
      annotations:
        scheduler.alpha.kubernetes.io/critical-pod: ""
    spec:
      containers:
      - image: dragonflyoss/supernode:0.3.0
        name: supernode
        ports:
        - containerPort: 8080
          hostPort: 8080
          name: tomcat
          protocol: TCP
        - containerPort: 8001
          hostPort: 8001
          name: register
          protocol: TCP
        - containerPort: 8002
          hostPort: 8002
          name: download
          protocol: TCP
        volumeMounts:
        - mountPath: /etc/localtime
          name: ltime
        - mountPath: /home/admin/supernode/logs/
          name: log
        - mountPath: /home/admin/supernode/repo/
          name: data
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      restartPolicy: Always
      tolerations:
      - effect: NoExecute
        operator: Exists
      - effect: NoSchedule
        operator: Exists
      nodeSelector:
        node-role.kubernetes.io/master: ""
      volumes:
      - hostPath:
          path: /etc/localtime
          type: ""
        name: ltime
      - hostPath:
          path: /data/log/supernode
          type: DirectoryOrCreate
        name: log
      - hostPath:
          path: /data/supernode/repo/
          type: DirectoryOrCreate
        name: data

---
kind: Service
apiVersion: v1
metadata:
  name: supernode
  namespace: kube-system
spec:
  selector:
    app: supernode
  ports:
  - name: register
    protocol: TCP
    port: 8001
    targetPort: 8001
  - name: download
    protocol: TCP
    port: 8002
    targetPort: 8002

以hostNetwork的形式部署在master上。

部署過後可以看到supernode已經正常啟動了。

# kubectl get pod -n kube-system | grep supernode
supernode-86dc99f6d5-mblck                 1/1     Running   0          4m1s

客戶端以daemonSet的形式部署,yaml文件如下:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: dfdaemon
  namespace: kube-system
spec:
  selector:
    matchLabels:
      app: dfdaemon
  template:
    metadata:
      annotations:
        scheduler.alpha.kubernetes.io/critical-pod: ""
      labels:
        app: dfdaemon
    spec:
      containers:
      - image: dragonflyoss/dfclient:v0.3.0
        name: dfdaemon
        imagePullPolicy: IfNotPresent
        args:
        - --registry https://index.docker.io
        resources:
          requests:
            cpu: 250m
        volumeMounts:
        - mountPath: /etc/dragonfly/dfget.yml
          subPath: dfget.yml
          name: dragonconf
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      restartPolicy: Always
      tolerations:
      - effect: NoExecute
        operator: Exists
      - effect: NoSchedule
        operator: Exists
      volumes:
      - name: dragonconf
        configMap:
          name: dragonfly-conf

配置文件我們以configMap的形式掛載,所以我們還需要編寫一個configMap的yaml文件,如下:

apiVersion: v1
kind: ConfigMap
metadata:
  name: dragonfly-conf
  namespace: kube-system
data:
  dfget.yml: |
    nodes:
    - 172.17.100.120

部署過後觀察結果

# kubectl get pod -n kube-system | grep dfdaemon
dfdaemon-mj4p6                             1/1     Running   0          3m51s
dfdaemon-wgq5d                             1/1     Running   0          3m51s
dfdaemon-wljt6                             1/1     Running   0          3m51s

然後修改docker daemon的配置,如下:

{
  "registry-mirrors": ["http://127.0.0.1:65001"]
}

重啟docker

systemctl restart docker

現在我們來拉取鏡像測試,並觀察日誌輸出。
下載鏡像(在master上測試的):

docker pull nginx

然後觀察日誌

kubectl exec  -n kube-system dfdaemon-wgq5d  grep 'downloading piece' /root/.small-dragonfly/logs/dfclient.log

看到日誌輸出如下,表示成功

2020-06-20 17:14:54.578 INFO sign:128-1592673287.190 : downloading piece:{"taskID":"089dc52627a346df2a2ff67f6c07497167b35c4bad2bca1e9aad087441116982","superNode":"172.17.100.120","dstCid":"cdnnode:192.168.235.192~089dc52627a346df2a2ff67f6c07497167b35c4bad2bca1e9aad087441116982","range":"0-4194303","result":503,"status":701,"pieceSize":4194304,"pieceNum":0}

今天的測試就到這裏,我這是自己的小集群實驗室,效果其實並不明顯,在大集群效果可能更好。

  • 參考
    • https://d7y.io/zh-cn/docs/userguide/multi_machines_deployment.html

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

新北清潔公司,居家、辦公、裝潢細清專業服務

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

FB行銷專家,教你從零開始的技巧

C#多線程編程(一)進程與線程

一、 進程

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

Q1:在單核下,進程之間如何同時執行?

        首先要區分兩個概念——併發和并行

  • 併發:併發是指在一段微小的時間段中,有多個程序代碼段被CPU執行,宏觀上表現出來就是多個程序能”同時“執行。
  • 并行:并行是指在一個時間點,有多個程序段代碼被CPU執行,它才是真正的同時執行。

        所以應該說進程之間是併發執行。對於CPU來講,它不知道進程的存在,CPU主要與寄存器打交道。有一些常用的寄存器,如程序計數器寄存器,這個寄存器存儲了將要執行的指令的地址,這個寄存器的地址指向哪,CPU就去哪。還有一些堆棧寄存器和通用寄存器等等等,總之,這些數據構成了一個程序的執行環境,這個執行環境就叫做”上下文(Context)“,進程之間切換本質就是保存這些數據到內存,術語叫做”保存現場“,然後恢復某個進程的執行環境,也即是”恢復現場“,整個過程術語叫做“上下文切換”,具體點就是進程上下文切換,這就是進程之間能併發執行的本質——頻繁的切換進程上下文。這個功能是由操作系統提供的,是內核態的,對應用軟件開發人員透明。

二、 線程

        進程雖然支持併發,但是對併發不是很友好,不友好是指每開啟一個進程,都要重新分配一部分資源,而線程相對進程來說,創建線程的代價比創建進程要小,所以引入線程能更好的提高併發性。在現代操作系統中,進程變成了資源分配的基本單位,而線程變成了執行的基本單位,每個線程都有獨立的堆棧空間,同一個進程的所有線程共享代碼段和地址空間等共享資源。相應的上下文切換從進程上下文切換變成了線程上下文切換

三、 為什麼要引入進程和線程

  1. 提高CPU利用率,在早期的單道批處理系統中,如果執行中的代碼需要依賴與外部條件,將會導致CPU空閑,例如文件讀取,等待鍵盤信號輸入,這將浪費大量的CPU時間。引入多進程和線程可以解決CPU利用率低這個問題。
  2. 隔離程序之間的數據(每個進程都有單獨的地址空間),保證系統運行的穩定性。
  3. 提高系統的響應性和交互能力。

四、 在C#中創建託管線程

1. Thread類

在.NET中,託管線程分為:

  • 前台線程
  • 後台線程

一個.Net程序中,至少要有一個前台線程,所有前台線程結束了,所有的後台線程將會被公共語言運行時(CLR)強制銷毀,程序執行結束。

如下將在控制台程序中創建一個後台線程

 1 static void Main(string[] args)
 2 {
 3      var t = new Thread(() =>
 4      {
 5          Thread.Sleep(1000);
 6          Console.WriteLine("執行完畢");
 7      });
 8     t.IsBackground = true;
 9      t.Start();
10 }

View Code

 

主線程(默認是前台線程)執行完畢,程序直接退出。

但IsBackground 屬性改為false時,控制台會打印“執行完畢”。

2. 有什麼問題

直接使用Thread類來進行多線程編程浪費資源(服務器端更加明顯)且不方便,舉個栗子。

假如我寫一個Web服務器程序,每個請求創建一個線程,那麼每一次我都要new一個Thread對象,然後傳入處理HttpRequest的委託,處理完之後,線程將會被銷毀,這將會導致浪費大量CPU時間和內存,在早期CPU性能不行和內存資源珍貴的情況下這個缺點會被放大,在現在這個缺點不是很明顯,原因是硬件上來了。

不方便體現在哪呢?

  • 無法直接獲取另一個線程內未被捕捉的異常
  • 無法直接獲取線程函數的返回值

 

 1 public static void ThrowException()
 2 {
 3      throw new Exception("發生異常");
 4 }
 5 static void Main(string[] args)
 6 {
 7      var t = new Thread(() =>
 8      {
 9          Thread.Sleep(1000);
10          ThrowException();
11      });
12     t.IsBackground = false;
13      try
14      {
15          t.Start();
16      }
17      catch(Exception e)
18      {
19          Console.WriteLine(e.Message);
20      }
21 }

View Code

 

上述代碼將會導致程序奔潰,如下圖。

 

要想直接獲取返回值和可以直接從主線程捕捉線程函數內未捕捉的異常,我們可以這麼做。

新建一個MyTask.cs文件,內容如下

 1 using System;
 2 using System.Threading;
 3 namespace ConsoleApp1
 4 {
 5      public class MyTask
 6      {
 7          private Thread _thread;
 8          private Action _action;
 9          private Exception _innerException;
10         public MyTask()
11          {
12         }
13          public MyTask(Action action)
14          {
15              _action = action;
16          }
17          protected virtual void Excute()
18          {
19              try
20              {
21                  _action();
22              }
23              catch(Exception e)
24              {
25                  _innerException = e;
26              }
27       
28          }
29          public void Start()
30          {
31              if (_thread != null) throw new InvalidOperationException("任務已經開始");
32              _thread = new Thread(() => Excute());
33              _thread.Start();
34          }
35          public void Start(Action action)
36          {
37              _action = action;
38              if (_thread != null) throw new InvalidOperationException("任務已經開始");
39              _thread = new Thread(() => Excute());
40              _thread.Start();
41          }
42         public void Wait()
43          {
44              _thread.Join();
45              if (_innerException != null) throw _innerException;
46          }
47      }
48     public class MyTask<T> : MyTask
49      {
50          private Func<T> _func { get; }
51          private T _result;
52          public T Result {
53              
54              private set => _result = value;
55              get 
56              {
57                  base.Wait();
58                  return _result;
59              }
60          }
61          public MyTask(Func<T> func)
62          {
63              _func = func;
64          }
65         public new void Start() 
66          {
67              base.Start(() =>
68              {
69                  Result = _func();
70              });
71          }
72     }
73 }

View Code

 

簡單的包裝了一下(不要在意細節),我們便可以實現我們想要的效果。

測試代碼如下

 1 public static void ThrowException()
 2 {
 3      throw new Exception("發生異常");
 4 }
 5 public static void Test3()
 6 {
 7      MyTask<string> myTask = new MyTask<string>(() =>
 8      {
 9          Thread.Sleep(1000);
10          return "執行完畢";
11      });
12     myTask.Start();
13     try
14      {
15          Console.WriteLine(myTask.Result);
16      }
17      catch (Exception e)
18      {
19          Console.WriteLine(e.Message);
20      }
21 }
22 public static void Test2()
23 {
24      MyTask<string> myTask = new MyTask<string>(() =>
25      {
26          Thread.Sleep(1000);
27          ThrowException();
28          return "執行完畢";
29      });
30     myTask.Start();
31     try
32      {
33          Console.WriteLine(myTask.Result);
34      }
35      catch(Exception e)
36      {
37          Console.WriteLine(e.Message);
38      }
39 }
40 public static void Test1()
41 {
42      MyTask myTask = new MyTask(() =>
43      {
44          Thread.Sleep(1000);
45          ThrowException();
46      });
47      myTask.Start();
48     try
49      {
50          myTask.Wait();
51      }
52      catch (Exception e)
53      {
54          Console.WriteLine(e.Message);
55      }
56 }
57 static void Main(string[] args)
58 {
59      Test1();
60      Test2();
61      Test3();
62 }

 

可以看到,我們可以通過簡單包裝Thread對象,便可實現如下效果

  • 直接讀取線程函數返回值
  • 直接捕捉線程函數未捕捉的異常(前提是調用了Wait()函數或者Result屬性)

這是理解和運用Task的基礎,Task功能非常完善,但是運用好Task需要掌握許多概念,下面再說。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

咖啡渣做太陽眼鏡 暨時尚又環保

摘錄自2019年12月12日公視報導

根據估計,全球每天喝掉20到25億杯咖啡,產生的咖啡渣非常驚人。烏克蘭有位新創工作者靈機一動,把咖啡渣拿來廢物利用,做出時尚又環保的太陽眼鏡。

來自烏克蘭的嘉利藍科,原本以修眼鏡為業,為了讓鏡框也能符合環保可分解的概念,他不斷尋找可以替代塑膠的材料,從薄荷到小麥和稻米,他通通都試過。最後發現咖啡渣混合植物油和亞麻纖維,經壓縮後裁切成框,效果最好。初步成形的鏡框,再經由手工琢磨拋光,就成為質感和造型出眾的太陽眼鏡,連鏡片也採用類似的植物纖維製成。嘉利藍科說:「這不只是生產商品而已,而是改變人們的認知,讓他們喜歡使用自然材質和自然成分,養成良心消費的習慣。」

咖啡渣眼鏡不但看得出天然質感,還可以聞到咖啡的香氣。嘉利藍科為自行研發的太陽眼鏡冠上新品牌,主要在咖啡廳和網路銷售,結果自去年推出以來,消費者的反應相當不錯,主要客戶來自歐美。消費者波利亞科夫表示,「它很創新也對環境友善,就保護生態環境而言,不只是在我國,而是放眼全世界,往這個方向發展是很重要的。」

咖啡渣材質比一般鏡架更輕,而當它們不再使用,長久放置於水或泥土中,便會自動分解成天然的肥料。嘉利藍科看好咖啡渣再利用的前景,未來計畫將它製成餐盤、牙刷和手機殼等日用品,希望更多人一起加入減塑做環保的行列。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

西門子檢討澳洲煤礦案 瑞典環保少女籲做對的事

摘錄自2020年01月11日中央通訊社澳洲報導

德國西門子公司(Siemens AG)即將公布是否參與協助一項澳洲煤礦開採計畫,瑞典環保少女童貝里今天(11日)呼籲西門子做出正確的決定。

童貝里(Greta Thunberg)今天在推特(Twitter)發文:「星期一(13日),他們(西門子)將宣布決定。請幫助他們做出唯一正確的決定。」環保人士憂心,繼續使用煤炭會導致二氧化碳排放量增加,二氧化碳則是造成全球暖化的相關氣體。

西門子公司表示,他們將於13日前決定,是否參與印度阿達尼電力公司(Adani Power)興建的煤礦場計畫。

澳洲相當依賴燃煤電廠,是全球人均碳排放量最高的國家之一。澳洲政府去年批准阿達尼電力公司在昆士蘭州(Queensland)興建新的煤礦場,這座煤礦場預計每年生產800萬至1000萬噸燃料用煤。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※台北網頁設計公司全省服務真心推薦

※想知道最厲害的網頁設計公司"嚨底家"!

新北清潔公司,居家、辦公、裝潢細清專業服務

※推薦評價好的iphone維修中心

南極「頰紋企鵝」 半世紀數量減少七成七

摘錄自2020年2月12日公視報導

美國生物學家最近在南極調查發現,當地常見的一種「頰紋企鵝」,數量比50年前大幅銳減了七成七。科學家指出,南極溫度在過去2、30年間上升了5°C。而冰棚快速融解崩塌,讓企鵝失去原本的棲息地,食物供應也大受影響。

保育生物學家佛瑞斯特表示,「我們認為這裡基礎食物鏈的建構出了問題,有可能是浮游生物或是磷蝦減少,食物不如以往充足,導致企鵝數量不斷減少。問題在於這種情況會不會持續惡化。」

頰紋企鵝、皇帝企鵝和國王企鵝,數量都在急劇減少,這也意味著生態的多樣性與豐富性正遭遇巨大衝擊。環保團體呼籲聯合國在2030年之前,將全球30%的海域列為保護區,因為目前只有4%受到保護實在太少。而從目前企鵝數量銳減的情況看來,將南極海域列為保護區已經刻不容緩。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

蝗蟲肆虐 巴基斯坦進入緊急狀態 或向死敵印度買殺蟲劑

摘錄自2020年2月17日香港蘋果日報報導

數以千億隻蝗蟲近日肆虐東非、西亞多國,巴基斯坦恐防今夏蝗蟲來襲,可能暫時少理與敵國印度的貿易禁令,從印度進口殺蟲劑做好準備。

據報巴基斯坦周二內閣會議議程上,列出了從印度進口殺蟲劑的選項,而正爆發武漢肺炎疫情的中國,則是巴基斯坦可選擇採購殺蟲劑的另一地方。

國家糧食安全與研究部植物保護處總幹事納茲(Dr Falak Naz)表示:「絕對是的,我們擔心6月至7月會有蝗蟲侵襲,因此我們正提早計劃做好準備。」糧食部向國會進行簡報後,巴基斯坦本月較早時就蝗禍宣佈全國進入緊急狀態,警告國家正面對20年來最嚴重的蝗災。

當地英文報章《論壇快報》(The Express Tribune)引述國家糧食安全部長巴赫蒂亞爾(Khusro Bakhtiar)說,蝗蟲目前身處印巴邊境,巴國已對超過12.14萬公頃土地上的蝗蟲採取行動,並從空中對2萬公頃的土地噴灑了殺蟲劑。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

Uber等即時叫車碳排更高 首份量化研究:車隊應電動化、提升共乘比例

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

蒙特婁公約奏效 臭氧層破洞縮小 南半球大氣樣態漸修復

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

新北清潔公司,居家、辦公、裝潢細清專業服務

※教你寫出一流的銷售文案?